@solaqua/gji 0.14.0 → 0.15.0

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.
@@ -5386,7 +5386,7 @@ var require_minimist = __commonJS({
5386
5386
  var require_rc = __commonJS({
5387
5387
  "node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
5388
5388
  var cc = require_utils();
5389
- var join16 = __require("path").join;
5389
+ var join17 = __require("path").join;
5390
5390
  var deepExtend = require_deep_extend();
5391
5391
  var etc = "/etc";
5392
5392
  var win = process.platform === "win32";
@@ -5411,15 +5411,15 @@ var require_rc = __commonJS({
5411
5411
  }
5412
5412
  if (!win)
5413
5413
  [
5414
- join16(etc, name, "config"),
5415
- join16(etc, name + "rc")
5414
+ join17(etc, name, "config"),
5415
+ join17(etc, name + "rc")
5416
5416
  ].forEach(addConfigFile);
5417
5417
  if (home)
5418
5418
  [
5419
- join16(home, ".config", name, "config"),
5420
- join16(home, ".config", name),
5421
- join16(home, "." + name, "config"),
5422
- join16(home, "." + name + "rc")
5419
+ join17(home, ".config", name, "config"),
5420
+ join17(home, ".config", name),
5421
+ join17(home, "." + name, "config"),
5422
+ join17(home, "." + name + "rc")
5423
5423
  ].forEach(addConfigFile);
5424
5424
  addConfigFile(cc.find("." + name + "rc"));
5425
5425
  if (env5.config) addConfigFile(env5.config);
@@ -6757,11 +6757,11 @@ var require_util = __commonJS({
6757
6757
  if (files.includes("node_modules") || files.includes("package.json") || files.includes("package.json5") || files.includes("package.yaml") || files.includes("pnpm-workspace.yaml")) {
6758
6758
  return name2;
6759
6759
  }
6760
- const dirname14 = path9.dirname(name2);
6761
- if (dirname14 === name2) {
6760
+ const dirname15 = path9.dirname(name2);
6761
+ if (dirname15 === name2) {
6762
6762
  return original;
6763
6763
  }
6764
- return find(dirname14, original);
6764
+ return find(dirname15, original);
6765
6765
  } catch (error) {
6766
6766
  if (name2 === original) {
6767
6767
  if (error.code === "ENOENT") {
@@ -9519,7 +9519,7 @@ var retryifyAsync = (fn, options) => {
9519
9519
  throw error;
9520
9520
  const delay2 = Math.round(interval * Math.random());
9521
9521
  if (delay2 > 0) {
9522
- const delayPromise = new Promise((resolve12) => setTimeout(resolve12, delay2));
9522
+ const delayPromise = new Promise((resolve13) => setTimeout(resolve13, delay2));
9523
9523
  return delayPromise.then(() => attempt.apply(void 0, args));
9524
9524
  } else {
9525
9525
  return attempt.apply(void 0, args);
@@ -9778,14 +9778,14 @@ var Temp = {
9778
9778
  }
9779
9779
  },
9780
9780
  truncate: (filePath) => {
9781
- const basename13 = path2.basename(filePath);
9782
- if (basename13.length <= LIMIT_BASENAME_LENGTH)
9781
+ const basename14 = path2.basename(filePath);
9782
+ if (basename14.length <= LIMIT_BASENAME_LENGTH)
9783
9783
  return filePath;
9784
- const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename13);
9784
+ const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename14);
9785
9785
  if (!truncable)
9786
9786
  return filePath;
9787
- const truncationLength = basename13.length - LIMIT_BASENAME_LENGTH;
9788
- return `${filePath.slice(0, -basename13.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9787
+ const truncationLength = basename14.length - LIMIT_BASENAME_LENGTH;
9788
+ return `${filePath.slice(0, -basename14.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9789
9789
  }
9790
9790
  };
9791
9791
  node_default(Temp.purgeSyncAll);
@@ -11085,14 +11085,14 @@ var TimeoutError = class extends Error {
11085
11085
 
11086
11086
  // node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/timeout.js
11087
11087
  async function timeout(request, init, abortController, options) {
11088
- return new Promise((resolve12, reject) => {
11088
+ return new Promise((resolve13, reject) => {
11089
11089
  const timeoutId = setTimeout(() => {
11090
11090
  if (abortController) {
11091
11091
  abortController.abort();
11092
11092
  }
11093
11093
  reject(new TimeoutError(request));
11094
11094
  }, options.timeout);
11095
- void options.fetch(request, init).then(resolve12).catch(reject).then(() => {
11095
+ void options.fetch(request, init).then(resolve13).catch(reject).then(() => {
11096
11096
  clearTimeout(timeoutId);
11097
11097
  });
11098
11098
  });
@@ -11100,7 +11100,7 @@ async function timeout(request, init, abortController, options) {
11100
11100
 
11101
11101
  // node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/delay.js
11102
11102
  async function delay(ms, { signal }) {
11103
- return new Promise((resolve12, reject) => {
11103
+ return new Promise((resolve13, reject) => {
11104
11104
  if (signal) {
11105
11105
  signal.throwIfAborted();
11106
11106
  signal.addEventListener("abort", abortHandler, { once: true });
@@ -11111,7 +11111,7 @@ async function delay(ms, { signal }) {
11111
11111
  }
11112
11112
  const timeoutId = setTimeout(() => {
11113
11113
  signal?.removeEventListener("abort", abortHandler);
11114
- resolve12();
11114
+ resolve13();
11115
11115
  }, ms);
11116
11116
  });
11117
11117
  }
@@ -12810,7 +12810,7 @@ function updateNotifier(options) {
12810
12810
  // package.json
12811
12811
  var package_default = {
12812
12812
  name: "@solaqua/gji",
12813
- version: "0.14.0",
12813
+ version: "0.15.0",
12814
12814
  description: "Git worktree CLI for fast context switching.",
12815
12815
  license: "MIT",
12816
12816
  author: "sjquant",
@@ -13093,7 +13093,7 @@ async function ensureDestinationDirectory(root, path9) {
13093
13093
  // src/infrastructure/process/command-runner.ts
13094
13094
  import { spawn as spawn2 } from "node:child_process";
13095
13095
  var runCommand = async (command, cwd, stderr, stdout3 = (chunk) => process.stdout.write(chunk), options) => {
13096
- await new Promise((resolve12, reject) => {
13096
+ await new Promise((resolve13, reject) => {
13097
13097
  const shell = options?.shell ?? true;
13098
13098
  const [executable, args] = shell ? [command, []] : splitCommand(command);
13099
13099
  const child = spawn2(executable, args, {
@@ -13112,7 +13112,7 @@ var runCommand = async (command, cwd, stderr, stdout3 = (chunk) => process.stdou
13112
13112
  if (code !== 0) {
13113
13113
  reject(new Error(`exited with code ${code}`));
13114
13114
  } else {
13115
- resolve12();
13115
+ resolve13();
13116
13116
  }
13117
13117
  });
13118
13118
  child.on("error", reject);
@@ -14019,9 +14019,9 @@ var EDITORS = [
14019
14019
  ];
14020
14020
  async function defaultSpawnEditor(cli, args) {
14021
14021
  const child = spawn3(cli, args, { detached: true, stdio: "ignore" });
14022
- await new Promise((resolve12, reject) => {
14022
+ await new Promise((resolve13, reject) => {
14023
14023
  child.once("error", reject);
14024
- child.once("spawn", resolve12);
14024
+ child.once("spawn", resolve13);
14025
14025
  });
14026
14026
  child.unref();
14027
14027
  }
@@ -14863,7 +14863,7 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout3) {
14863
14863
  stderr("gji: hook argv command must include a non-empty command\n");
14864
14864
  return;
14865
14865
  }
14866
- await new Promise((resolve12) => {
14866
+ await new Promise((resolve13) => {
14867
14867
  const child = spawn4(command, args, {
14868
14868
  cwd,
14869
14869
  shell: false,
@@ -14883,18 +14883,18 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout3) {
14883
14883
  `
14884
14884
  );
14885
14885
  }
14886
- resolve12();
14886
+ resolve13();
14887
14887
  });
14888
14888
  child.on("error", (err) => {
14889
14889
  stderr(`gji: hook failed to start: ${err.message}
14890
14890
  `);
14891
- resolve12();
14891
+ resolve13();
14892
14892
  });
14893
14893
  });
14894
14894
  }
14895
14895
  async function runShellHook(hookCmd, cwd, context, stderr, stdout3) {
14896
14896
  const interpolated = interpolate(hookCmd, context);
14897
- await new Promise((resolve12) => {
14897
+ await new Promise((resolve13) => {
14898
14898
  const child = spawn4(interpolated, {
14899
14899
  cwd,
14900
14900
  shell: true,
@@ -14912,12 +14912,12 @@ async function runShellHook(hookCmd, cwd, context, stderr, stdout3) {
14912
14912
  stderr(`gji: hook exited with code ${code}: ${interpolated}
14913
14913
  `);
14914
14914
  }
14915
- resolve12();
14915
+ resolve13();
14916
14916
  });
14917
14917
  child.on("error", (err) => {
14918
14918
  stderr(`gji: hook failed to start: ${err.message}
14919
14919
  `);
14920
- resolve12();
14920
+ resolve13();
14921
14921
  });
14922
14922
  });
14923
14923
  }
@@ -15082,8 +15082,8 @@ async function removeMissingRegistryEntries(paths, home = homedir4()) {
15082
15082
  async function withRegistryLock(registryPath, operation) {
15083
15083
  const previous = registryLocks.get(registryPath);
15084
15084
  let release;
15085
- const current = new Promise((resolve12) => {
15086
- release = resolve12;
15085
+ const current = new Promise((resolve13) => {
15086
+ release = resolve13;
15087
15087
  });
15088
15088
  registryLocks.set(registryPath, current);
15089
15089
  if (previous) await previous;
@@ -15690,6 +15690,10 @@ function formatAge(timestamp) {
15690
15690
  return `${days}d ago`;
15691
15691
  }
15692
15692
 
15693
+ // src/cli/commands/clean.ts
15694
+ import { realpath as realpath5 } from "node:fs/promises";
15695
+ import { basename as basename8, dirname as dirname10, join as join12, resolve as resolve11 } from "node:path";
15696
+
15693
15697
  // node_modules/.pnpm/@clack+core@0.5.0/node_modules/@clack/core/dist/index.mjs
15694
15698
  var import_sisteransi = __toESM(require_src(), 1);
15695
15699
  var import_picocolors = __toESM(require_picocolors(), 1);
@@ -16480,6 +16484,121 @@ function throwIfAborted2(signal) {
16480
16484
  if (signal?.aborted) throw new Error("Operation cancelled");
16481
16485
  }
16482
16486
 
16487
+ // src/domain/worktree/path-filter.ts
16488
+ function createWorktreePathFilter(patterns) {
16489
+ const matchers = patterns.map(createPathMatcher);
16490
+ if (matchers.length === 0) return () => true;
16491
+ return (worktreePath) => matchers.some((matches) => matches(worktreePath));
16492
+ }
16493
+ function findGlobIndex(pattern) {
16494
+ for (let index = 0; index < pattern.length; index += 1) {
16495
+ const character = pattern[index];
16496
+ if (character === "\\") index += 1;
16497
+ else if (character === "*" || character === "?") return index;
16498
+ }
16499
+ return -1;
16500
+ }
16501
+ function escapePathPattern(path9) {
16502
+ return path9.replace(/[\\*?]/g, "\\$&");
16503
+ }
16504
+ function unescapePathPattern(pattern) {
16505
+ return pattern.replace(/\\(.)/g, "$1");
16506
+ }
16507
+ function createPathMatcher(pattern) {
16508
+ if (findGlobIndex(pattern) !== -1) {
16509
+ const regExp = globToRegExp(pattern);
16510
+ return (path9) => regExp.test(path9);
16511
+ }
16512
+ const literal = unescapePathPattern(pattern);
16513
+ const prefix = literal === "/" ? "/" : `${literal}/`;
16514
+ return (path9) => path9 === literal || path9.startsWith(prefix);
16515
+ }
16516
+ function globToRegExp(pattern) {
16517
+ let source = "";
16518
+ for (let index = 0; index < pattern.length; index += 1) {
16519
+ let character = pattern[index];
16520
+ if (character === "*") {
16521
+ if (pattern[index + 1] === "*") {
16522
+ index += 1;
16523
+ if (pattern[index + 1] === "/") {
16524
+ index += 1;
16525
+ source += "(?:.*/)?";
16526
+ } else {
16527
+ source += ".*";
16528
+ }
16529
+ } else {
16530
+ source += "[^/]*";
16531
+ }
16532
+ } else if (character === "?") {
16533
+ source += "[^/]";
16534
+ } else {
16535
+ if (character === "\\" && index + 1 < pattern.length) {
16536
+ index += 1;
16537
+ character = pattern[index];
16538
+ }
16539
+ source += character.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16540
+ }
16541
+ }
16542
+ return new RegExp(`^${source}$`);
16543
+ }
16544
+
16545
+ // src/domain/worktree/policy.ts
16546
+ import { homedir as homedir5 } from "node:os";
16547
+ import { basename as basename7, dirname as dirname8, join as join10 } from "node:path";
16548
+ function resolveWorktreePath(repoRoot, branch, basePath) {
16549
+ const segments = branch.split("/").filter(Boolean);
16550
+ if (segments.length === 0) {
16551
+ throw new Error("Branch name must not be empty.");
16552
+ }
16553
+ if (segments.some((segment) => segment === "." || segment === "..")) {
16554
+ throw new Error(
16555
+ `Branch name '${branch}' contains an invalid path segment.`
16556
+ );
16557
+ }
16558
+ const base = basePath ? expandTildeInPath(basePath) : join10(dirname8(repoRoot), "worktrees", basename7(repoRoot));
16559
+ return join10(base, ...segments);
16560
+ }
16561
+ function validateBranchName(name) {
16562
+ if (name.length === 0) {
16563
+ return "Branch name must not be empty.";
16564
+ }
16565
+ if (/[\x00-\x1f\x7f ~^:?*[\\\s]/.test(name)) {
16566
+ return `Branch name '${name}' contains an invalid character.`;
16567
+ }
16568
+ if (name.startsWith("-")) {
16569
+ return `Branch name '${name}' must not start with a dash.`;
16570
+ }
16571
+ if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
16572
+ return `Branch name '${name}' has invalid slash placement.`;
16573
+ }
16574
+ if (name.includes("..")) {
16575
+ return `Branch name '${name}' must not contain '..'.`;
16576
+ }
16577
+ if (name.endsWith(".")) {
16578
+ return `Branch name '${name}' must not end with '.'.`;
16579
+ }
16580
+ if (name.includes("@{")) {
16581
+ return `Branch name '${name}' must not contain '@{'.`;
16582
+ }
16583
+ if (name === "@") {
16584
+ return "Branch name cannot be '@'.";
16585
+ }
16586
+ for (const segment of name.split("/")) {
16587
+ if (segment.startsWith(".")) {
16588
+ return `Branch name '${name}' contains a path component starting with '.'.`;
16589
+ }
16590
+ if (segment.endsWith(".lock")) {
16591
+ return `Branch name '${name}' contains a path component ending with '.lock'.`;
16592
+ }
16593
+ }
16594
+ return null;
16595
+ }
16596
+ function expandTildeInPath(path9) {
16597
+ if (path9 === "~") return homedir5();
16598
+ if (path9.startsWith("~/")) return join10(homedir5(), path9.slice(2));
16599
+ return path9;
16600
+ }
16601
+
16483
16602
  // src/presentation/worktree/picker.ts
16484
16603
  import { env as env4, platform as platform2, stdin, stdout } from "node:process";
16485
16604
 
@@ -17489,21 +17608,21 @@ import {
17489
17608
  unlink as unlink2,
17490
17609
  writeFile as writeFile7
17491
17610
  } from "node:fs/promises";
17492
- import { homedir as homedir5 } from "node:os";
17493
- import { dirname as dirname8, join as join10, resolve as resolve10 } from "node:path";
17611
+ import { homedir as homedir6 } from "node:os";
17612
+ import { dirname as dirname9, join as join11, resolve as resolve10 } from "node:path";
17494
17613
  var MAX_UNDO_RECORDS = 20;
17495
17614
  var UNDO_LOCK_STALE_AFTER_MS = 6e4;
17496
17615
  var UNDO_LOCK_RETRY_DELAY_MS = 5;
17497
17616
  var UNDO_LOCK_WAIT_TIMEOUT_MS = UNDO_LOCK_STALE_AFTER_MS + UNDO_LOCK_RETRY_DELAY_MS;
17498
17617
  var undoLogQueues = /* @__PURE__ */ new Map();
17499
- function undoLogPath(home = homedir5(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
17618
+ function undoLogPath(home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
17500
17619
  const configured = process.env.GJI_CONFIG_DIR;
17501
- return join10(
17502
- configured ? resolve10(configured) : join10(home, globalConfigDirectory),
17620
+ return join11(
17621
+ configured ? resolve10(configured) : join11(home, globalConfigDirectory),
17503
17622
  "undo-log.json"
17504
17623
  );
17505
17624
  }
17506
- async function loadUndoRecords(home = homedir5(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
17625
+ async function loadUndoRecords(home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
17507
17626
  try {
17508
17627
  const parsed = JSON.parse(
17509
17628
  await readFile7(undoLogPath(home, globalConfigDirectory), "utf8")
@@ -17513,7 +17632,7 @@ async function loadUndoRecords(home = homedir5(), globalConfigDirectory = defaul
17513
17632
  return [];
17514
17633
  }
17515
17634
  }
17516
- async function recordUndoOperation(op, repoRoot, worktrees, home = homedir5(), runtime = defaultCliDependencies) {
17635
+ async function recordUndoOperation(op, repoRoot, worktrees, home = homedir6(), runtime = defaultCliDependencies) {
17517
17636
  const globalConfigDirectory = runtime.configStore.GLOBAL_CONFIG_DIRECTORY;
17518
17637
  const { isDirtyWorktree: isDirtyWorktree2, runGit: runGit2 } = runtime.git;
17519
17638
  const entries = [];
@@ -17563,7 +17682,7 @@ async function recordUndoOperation(op, repoRoot, worktrees, home = homedir5(), r
17563
17682
  globalConfigDirectory
17564
17683
  );
17565
17684
  }
17566
- async function finalizeUndoOperation(record, removedWorktrees, home = homedir5(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
17685
+ async function finalizeUndoOperation(record, removedWorktrees, home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
17567
17686
  const removedPaths = new Set(
17568
17687
  removedWorktrees.map((worktree) => worktree.path)
17569
17688
  );
@@ -17583,7 +17702,7 @@ async function finalizeUndoOperation(record, removedWorktrees, home = homedir5()
17583
17702
  globalConfigDirectory
17584
17703
  );
17585
17704
  }
17586
- async function restoreUndoRecord(record, home = homedir5(), runtime = defaultCliDependencies) {
17705
+ async function restoreUndoRecord(record, home = homedir6(), runtime = defaultCliDependencies) {
17587
17706
  const globalConfigDirectory = runtime.configStore.GLOBAL_CONFIG_DIRECTORY;
17588
17707
  const restored = [];
17589
17708
  const failed = [];
@@ -17628,7 +17747,7 @@ async function runUndoCommand(options) {
17628
17747
  options.stderr(
17629
17748
  "Warning: undo journal is invalid; starting with an empty journal\n"
17630
17749
  );
17631
- const records = await loadUndoRecords(homedir5(), globalConfigDirectory);
17750
+ const records = await loadUndoRecords(homedir6(), globalConfigDirectory);
17632
17751
  if (options.list) {
17633
17752
  if (options.json) options.stdout(`${JSON.stringify(records, null, 2)}
17634
17753
  `);
@@ -17675,7 +17794,7 @@ async function runUndoCommand(options) {
17675
17794
  }
17676
17795
  if (!record)
17677
17796
  return emitUndoError(options, `undo record not found: ${options.id}`);
17678
- const result = await restoreUndoRecord(record, homedir5(), runtime);
17797
+ const result = await restoreUndoRecord(record, homedir6(), runtime);
17679
17798
  if (options.json)
17680
17799
  options.stdout(
17681
17800
  `${JSON.stringify({ restored: result.restored.map((entry) => ({ branch: entry.branch, path: entry.path })), failed: result.failed.map((entry) => ({ branch: entry.branch, path: entry.path, error: entry.message })) }, null, 2)}
@@ -17703,7 +17822,7 @@ async function runUndoCommand(options) {
17703
17822
  async function hasMalformedUndoLog(globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
17704
17823
  try {
17705
17824
  const parsed = JSON.parse(
17706
- await readFile7(undoLogPath(homedir5(), globalConfigDirectory), "utf8")
17825
+ await readFile7(undoLogPath(homedir6(), globalConfigDirectory), "utf8")
17707
17826
  );
17708
17827
  return !Array.isArray(parsed) || parsed.some((entry) => !isUndoRecord(entry));
17709
17828
  } catch (error) {
@@ -17795,7 +17914,7 @@ async function assertCommitExists(repoRoot, headSha, runGit2) {
17795
17914
  }
17796
17915
  async function enqueueUndoLogWrite(home, operation, globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
17797
17916
  const path9 = undoLogPath(home, globalConfigDirectory);
17798
- await mkdir6(dirname8(path9), { recursive: true });
17917
+ await mkdir6(dirname9(path9), { recursive: true });
17799
17918
  const previous = undoLogQueues.get(path9) ?? Promise.resolve();
17800
17919
  const runLocked = async () => {
17801
17920
  const release = await acquireUndoLogLock(path9);
@@ -17836,7 +17955,7 @@ async function acquireUndoLogLock(path9) {
17836
17955
  const remainingMs = deadline - Date.now();
17837
17956
  if (remainingMs > 0)
17838
17957
  await new Promise(
17839
- (resolve12) => setTimeout(resolve12, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
17958
+ (resolve13) => setTimeout(resolve13, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
17840
17959
  );
17841
17960
  }
17842
17961
  }
@@ -17844,7 +17963,7 @@ async function acquireUndoLogLock(path9) {
17844
17963
  }
17845
17964
  async function writeUndoRecords(home, records, globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
17846
17965
  const path9 = undoLogPath(home, globalConfigDirectory);
17847
- await mkdir6(dirname8(path9), { recursive: true });
17966
+ await mkdir6(dirname9(path9), { recursive: true });
17848
17967
  const temporaryPath = `${path9}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
17849
17968
  try {
17850
17969
  await writeFile7(temporaryPath, `${JSON.stringify(records, null, 2)}
@@ -17936,7 +18055,28 @@ function createCleanCommand(dependencies = {}) {
17936
18055
  options.cwd,
17937
18056
  sourceDependencies
17938
18057
  );
17939
- const currentSources = linkedWorktrees.filter((worktree) => worktree.path !== repository.currentRoot).map((worktree) => ({
18058
+ const pathPatterns = await canonicalizePathPatterns(
18059
+ options.cwd,
18060
+ options.paths ?? []
18061
+ );
18062
+ const matchesPathFilter = createWorktreePathFilter(pathPatterns);
18063
+ const searchAllRepositories = pathPatterns.length > 0;
18064
+ let allSourceCount = 0;
18065
+ const emitNoCandidates = (sourceCount) => {
18066
+ if (pathPatterns.length > 0 && sourceCount === 0) {
18067
+ emitError(options, "No linked worktrees match --path");
18068
+ return 1;
18069
+ }
18070
+ if (options.stale) {
18071
+ emitNoStaleCandidates(options);
18072
+ return 0;
18073
+ }
18074
+ emitError(options, "No linked worktrees to clean");
18075
+ return 1;
18076
+ };
18077
+ const currentSources = linkedWorktrees.filter(
18078
+ (worktree) => worktree.path !== repository.currentRoot && matchesPathFilter(worktree.path)
18079
+ ).map((worktree) => ({
17940
18080
  repoName: repository.repoName,
17941
18081
  repoRoot: repository.repoRoot,
17942
18082
  worktree
@@ -17977,8 +18117,9 @@ function createCleanCommand(dependencies = {}) {
17977
18117
  ...currentSources,
17978
18118
  ...registered.sources
17979
18119
  ]).filter(
17980
- (source) => source.repoRoot !== void 0 && source.worktree.path !== source.repoRoot && source.worktree.path !== repository.currentRoot
18120
+ (source) => source.repoRoot !== void 0 && source.worktree.path !== source.repoRoot && source.worktree.path !== repository.currentRoot && matchesPathFilter(source.worktree.path)
17981
18121
  );
18122
+ allSourceCount = sources.length;
17982
18123
  const otherRepositorySources = sources.filter(
17983
18124
  (source) => source.repoRoot !== repository.repoRoot
17984
18125
  );
@@ -18012,28 +18153,17 @@ function createCleanCommand(dependencies = {}) {
18012
18153
  };
18013
18154
  }
18014
18155
  };
18015
- if (currentCandidates.length === 0 && interactiveSelection) {
18156
+ if (searchAllRepositories || interactiveSelection && currentCandidates.length === 0) {
18016
18157
  const loadedAllCandidates = await loadAllCandidates();
18017
18158
  if (loadedAllCandidates.length === 0) {
18018
- if (options.stale) {
18019
- emitNoStaleCandidates(options);
18020
- return 0;
18021
- }
18022
- emitError(options, "No linked worktrees to clean");
18023
- return 1;
18159
+ return emitNoCandidates(allSourceCount);
18024
18160
  }
18025
18161
  currentScope = false;
18026
18162
  activeCandidates = loadedAllCandidates;
18027
18163
  scope.label = "all repositories";
18028
18164
  scope.toggleLabel = "current repository";
18029
- }
18030
- if (currentCandidates.length === 0 && !interactiveSelection) {
18031
- if (options.stale) {
18032
- emitNoStaleCandidates(options);
18033
- return 0;
18034
- }
18035
- emitError(options, "No linked worktrees to clean");
18036
- return 1;
18165
+ } else if (currentCandidates.length === 0) {
18166
+ return emitNoCandidates(currentSources.length);
18037
18167
  }
18038
18168
  if (!options.dryRun && !options.force && (options.json || isHeadless())) {
18039
18169
  const message = "--force is required";
@@ -18260,6 +18390,31 @@ function createCleanCommand(dependencies = {}) {
18260
18390
  };
18261
18391
  }
18262
18392
  var runCleanCommand = createCleanCommand();
18393
+ async function canonicalizePathPatterns(cwd, patterns) {
18394
+ return Promise.all(
18395
+ patterns.map(
18396
+ (pattern) => canonicalizeLiteralPrefix(resolve11(cwd, expandTildeInPath(pattern)))
18397
+ )
18398
+ );
18399
+ }
18400
+ async function canonicalizeLiteralPrefix(pattern) {
18401
+ const globIndex = findGlobIndex(pattern);
18402
+ let prefix = globIndex === -1 ? pattern : dirname10(`${pattern.slice(0, globIndex)}x`);
18403
+ let suffix = pattern.slice(prefix.length);
18404
+ for (; ; ) {
18405
+ try {
18406
+ const canonicalPrefix = escapePathPattern(
18407
+ await realpath5(unescapePathPattern(prefix))
18408
+ );
18409
+ return `${canonicalPrefix}${suffix}`.replace(/^\/\//, "/");
18410
+ } catch {
18411
+ const parent = dirname10(prefix);
18412
+ if (parent === prefix) return pattern;
18413
+ suffix = join12("/", basename8(prefix), suffix);
18414
+ prefix = parent;
18415
+ }
18416
+ }
18417
+ }
18263
18418
  async function resolveCleanupCandidates(options, dependencies) {
18264
18419
  const { sources, stale, stderr, signal } = options;
18265
18420
  const grouped = /* @__PURE__ */ new Map();
@@ -18761,7 +18916,11 @@ _gji_completion() {
18761
18916
  COMPREPLY=( $(compgen -W "--compact --json --help" -- "$cur") )
18762
18917
  ;;
18763
18918
  clean)
18764
- COMPREPLY=( $(compgen -W "-f --force --stale --dry-run --json --help" -- "$cur") )
18919
+ if [ "\${COMP_WORDS[COMP_CWORD-1]}" = "--path" ]; then
18920
+ COMPREPLY=( $(compgen -d -- "$cur") )
18921
+ else
18922
+ COMPREPLY=( $(compgen -W "-f --force --stale --path --dry-run --json --help" -- "$cur") )
18923
+ fi
18765
18924
  ;;
18766
18925
  remove|rm)
18767
18926
  COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) -f --force --dry-run --json --help" -- "$cur") )
@@ -19004,6 +19163,7 @@ complete -c gji -n '__fish_seen_subcommand_from ls' -l json -d 'print active wor
19004
19163
 
19005
19164
  complete -c gji -n '__fish_seen_subcommand_from clean' -s f -l force -d 'bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches'
19006
19165
  complete -c gji -n '__fish_seen_subcommand_from clean' -l stale -d 'only target clean worktrees whose upstream is gone and branch is merged into the default branch'
19166
+ complete -c gji -n '__fish_seen_subcommand_from clean' -l path -r -F -d 'only target worktrees under a directory or matching a path glob (repeatable)'
19007
19167
  complete -c gji -n '__fish_seen_subcommand_from clean' -l dry-run -d 'show what would be deleted without removing anything'
19008
19168
  complete -c gji -n '__fish_seen_subcommand_from clean' -l json -d 'emit JSON on success or error instead of human-readable output'
19009
19169
 
@@ -19164,7 +19324,7 @@ case "\${words[2]}" in
19164
19324
  _arguments '--compact[show only branch and path columns]' '--json[print active worktrees as JSON]'
19165
19325
  ;;
19166
19326
  clean)
19167
- _arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches]' '--stale[only target clean worktrees whose upstream is gone and branch is merged into the default branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]'
19327
+ _arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches]' '--stale[only target clean worktrees whose upstream is gone and branch is merged into the default branch]' '*--path[only target worktrees under a directory or matching a path glob]:pattern:_files' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]'
19168
19328
  ;;
19169
19329
  remove|rm)
19170
19330
  _arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch:->worktrees'
@@ -19301,20 +19461,20 @@ function writeJson(stdout3, value) {
19301
19461
  import { execFile as execFile8 } from "node:child_process";
19302
19462
  import { constants as constants6 } from "node:fs";
19303
19463
  import { access as access6, readFile as readFile8 } from "node:fs/promises";
19304
- import { homedir as homedir6 } from "node:os";
19305
- import { basename as basename7, dirname as dirname9, join as join12 } from "node:path";
19464
+ import { homedir as homedir7 } from "node:os";
19465
+ import { basename as basename9, dirname as dirname11, join as join14 } from "node:path";
19306
19466
  import { promisify as promisify9 } from "node:util";
19307
19467
 
19308
19468
  // src/presentation/shell/setup.ts
19309
19469
  import { constants as constants5 } from "node:fs";
19310
19470
  import { access as access5 } from "node:fs/promises";
19311
- import { delimiter, join as join11 } from "node:path";
19471
+ import { delimiter, join as join13 } from "node:path";
19312
19472
  var SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
19313
19473
  var SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
19314
19474
  var ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
19315
19475
  var ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
19316
19476
  async function executableExists(command) {
19317
- const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join11(directory, command));
19477
+ const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join13(directory, command));
19318
19478
  for (const path9 of candidates) {
19319
19479
  try {
19320
19480
  await access5(path9, constants5.X_OK);
@@ -19327,17 +19487,17 @@ async function executableExists(command) {
19327
19487
  function resolveShellConfigPath(shell, home) {
19328
19488
  switch (shell) {
19329
19489
  case "bash":
19330
- return join11(home, ".bashrc");
19490
+ return join13(home, ".bashrc");
19331
19491
  case "fish":
19332
- return join11(home, ".config", "fish", "config.fish");
19492
+ return join13(home, ".config", "fish", "config.fish");
19333
19493
  case "zsh":
19334
- return join11(home, ".zshrc");
19494
+ return join13(home, ".zshrc");
19335
19495
  }
19336
19496
  }
19337
19497
  function resolveCompletionPath(shell, home) {
19338
19498
  switch (shell) {
19339
19499
  case "bash":
19340
- return join11(
19500
+ return join13(
19341
19501
  home,
19342
19502
  ".local",
19343
19503
  "share",
@@ -19346,9 +19506,9 @@ function resolveCompletionPath(shell, home) {
19346
19506
  "gji"
19347
19507
  );
19348
19508
  case "fish":
19349
- return join11(home, ".config", "fish", "completions", "gji.fish");
19509
+ return join13(home, ".config", "fish", "completions", "gji.fish");
19350
19510
  case "zsh":
19351
- return join11(home, ".zsh", "completions", "_gji");
19511
+ return join13(home, ".zsh", "completions", "_gji");
19352
19512
  }
19353
19513
  }
19354
19514
  function hasShellIntegration(config, shell) {
@@ -19380,7 +19540,7 @@ async function runDoctorCommand(options) {
19380
19540
  }
19381
19541
  return 1;
19382
19542
  }
19383
- const home = options.home ?? homedir6();
19543
+ const home = options.home ?? homedir7();
19384
19544
  const shell = resolveSupportedShell(
19385
19545
  void 0,
19386
19546
  options.shell ?? process.env.SHELL
@@ -19445,7 +19605,7 @@ async function collectDoctorInspection(cwd, home, shell, runtime) {
19445
19605
  KNOWN_GLOBAL_CONFIG_KEYS2
19446
19606
  );
19447
19607
  const localConfig = repository ? await inspectConfig(
19448
- join12(repository.repoRoot, CONFIG_FILE_NAME2),
19608
+ join14(repository.repoRoot, CONFIG_FILE_NAME2),
19449
19609
  "local",
19450
19610
  KNOWN_CONFIG_KEYS2
19451
19611
  ) : null;
@@ -19624,7 +19784,7 @@ function resolvePerRepoConfig(repos, repoRoot, home) {
19624
19784
  }
19625
19785
  function expandTilde2(path9, home) {
19626
19786
  if (path9 === "~") return home;
19627
- if (path9.startsWith("~/")) return join12(home, path9.slice(2));
19787
+ if (path9.startsWith("~/")) return join14(home, path9.slice(2));
19628
19788
  return path9;
19629
19789
  }
19630
19790
  async function checkGitVersion() {
@@ -19751,7 +19911,7 @@ function resolveWorktreeBase(repoRoot, config, home) {
19751
19911
  if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
19752
19912
  return expandTilde2(configuredPath, home);
19753
19913
  }
19754
- return join12(dirname9(repoRoot), "worktrees", basename7(repoRoot));
19914
+ return join14(dirname11(repoRoot), "worktrees", basename9(repoRoot));
19755
19915
  }
19756
19916
  async function findNearestExistingPath(path9) {
19757
19917
  let candidate = path9;
@@ -19760,7 +19920,7 @@ async function findNearestExistingPath(path9) {
19760
19920
  await access6(candidate, constants6.F_OK);
19761
19921
  return candidate;
19762
19922
  } catch {
19763
- const parent = dirname9(candidate);
19923
+ const parent = dirname11(candidate);
19764
19924
  if (parent === candidate) return null;
19765
19925
  candidate = parent;
19766
19926
  }
@@ -19911,7 +20071,7 @@ function statusSymbol(status) {
19911
20071
  }
19912
20072
 
19913
20073
  // src/cli/commands/done.ts
19914
- import { basename as basename8 } from "node:path";
20074
+ import { basename as basename10 } from "node:path";
19915
20075
  var DONE_OUTPUT_FILE_ENV = "GJI_DONE_OUTPUT_FILE";
19916
20076
  async function runDoneCommand(options) {
19917
20077
  const runtime = options.runtime ?? defaultCliDependencies;
@@ -20019,7 +20179,7 @@ async function runDoneCommand(options) {
20019
20179
  {
20020
20180
  branch: target.branch ?? void 0,
20021
20181
  path: target.path,
20022
- repo: basename8(repository.repoRoot)
20182
+ repo: basename10(repository.repoRoot)
20023
20183
  },
20024
20184
  options.stderr
20025
20185
  );
@@ -20210,7 +20370,7 @@ function toMessage3(error) {
20210
20370
  }
20211
20371
 
20212
20372
  // src/cli/commands/go.ts
20213
- import { basename as basename10 } from "node:path";
20373
+ import { basename as basename11 } from "node:path";
20214
20374
 
20215
20375
  // src/application/worktree/context-card.ts
20216
20376
  async function loadContextCardModel(worktreePath, dependencies) {
@@ -20541,64 +20701,7 @@ function formatLastCommit2(info) {
20541
20701
 
20542
20702
  // src/cli/commands/new.ts
20543
20703
  import { access as access8, mkdir as mkdir7 } from "node:fs/promises";
20544
- import { dirname as dirname11, resolve as resolve11 } from "node:path";
20545
-
20546
- // src/domain/worktree/policy.ts
20547
- import { homedir as homedir7 } from "node:os";
20548
- import { basename as basename9, dirname as dirname10, join as join13 } from "node:path";
20549
- function resolveWorktreePath(repoRoot, branch, basePath) {
20550
- const segments = branch.split("/").filter(Boolean);
20551
- if (segments.length === 0) {
20552
- throw new Error("Branch name must not be empty.");
20553
- }
20554
- if (segments.some((segment) => segment === "." || segment === "..")) {
20555
- throw new Error(
20556
- `Branch name '${branch}' contains an invalid path segment.`
20557
- );
20558
- }
20559
- const base = basePath ? expandTildeInPath(basePath) : join13(dirname10(repoRoot), "worktrees", basename9(repoRoot));
20560
- return join13(base, ...segments);
20561
- }
20562
- function validateBranchName(name) {
20563
- if (name.length === 0) {
20564
- return "Branch name must not be empty.";
20565
- }
20566
- if (/[\x00-\x1f\x7f ~^:?*[\\\s]/.test(name)) {
20567
- return `Branch name '${name}' contains an invalid character.`;
20568
- }
20569
- if (name.startsWith("-")) {
20570
- return `Branch name '${name}' must not start with a dash.`;
20571
- }
20572
- if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
20573
- return `Branch name '${name}' has invalid slash placement.`;
20574
- }
20575
- if (name.includes("..")) {
20576
- return `Branch name '${name}' must not contain '..'.`;
20577
- }
20578
- if (name.endsWith(".")) {
20579
- return `Branch name '${name}' must not end with '.'.`;
20580
- }
20581
- if (name.includes("@{")) {
20582
- return `Branch name '${name}' must not contain '@{'.`;
20583
- }
20584
- if (name === "@") {
20585
- return "Branch name cannot be '@'.";
20586
- }
20587
- for (const segment of name.split("/")) {
20588
- if (segment.startsWith(".")) {
20589
- return `Branch name '${name}' contains a path component starting with '.'.`;
20590
- }
20591
- if (segment.endsWith(".lock")) {
20592
- return `Branch name '${name}' contains a path component ending with '.lock'.`;
20593
- }
20594
- }
20595
- return null;
20596
- }
20597
- function expandTildeInPath(path9) {
20598
- if (path9 === "~") return homedir7();
20599
- if (path9.startsWith("~/")) return join13(homedir7(), path9.slice(2));
20600
- return path9;
20601
- }
20704
+ import { dirname as dirname12, resolve as resolve12 } from "node:path";
20602
20705
 
20603
20706
  // src/presentation/bootstrap/output.ts
20604
20707
  function createBootstrapReporter(write, json) {
@@ -20908,7 +21011,7 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
20908
21011
  }
20909
21012
  return 0;
20910
21013
  }
20911
- await mkdir7(dirname11(worktreePath), { recursive: true });
21014
+ await mkdir7(dirname12(worktreePath), { recursive: true });
20912
21015
  const startPoint = options.fromCurrent && !options.detached ? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit) : void 0;
20913
21016
  const takeStartPoint = options.take ? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit) : void 0;
20914
21017
  const changedTakeFiles = options.take ? await listTakeFiles(options.cwd, runtime.git.runGitRaw) : [];
@@ -21500,7 +21603,7 @@ async function detectInProgressGitState(cwd, runGit2) {
21500
21603
  for (const [marker, name] of markers) {
21501
21604
  try {
21502
21605
  const gitPath = await runGit2(cwd, ["rev-parse", "--git-path", marker]);
21503
- await access8(resolve11(cwd, gitPath));
21606
+ await access8(resolve12(cwd, gitPath));
21504
21607
  return name;
21505
21608
  } catch {
21506
21609
  }
@@ -21652,7 +21755,7 @@ function emitNewError(options, message, details) {
21652
21755
 
21653
21756
  // src/cli/commands/pr.ts
21654
21757
  import { mkdir as mkdir8 } from "node:fs/promises";
21655
- import { dirname as dirname12 } from "node:path";
21758
+ import { dirname as dirname13 } from "node:path";
21656
21759
  var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
21657
21760
  function createPrCommand(dependencies = {}) {
21658
21761
  const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
@@ -21791,7 +21894,7 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
21791
21894
  }
21792
21895
  return 1;
21793
21896
  }
21794
- await mkdir8(dirname12(worktreePath), { recursive: true });
21897
+ await mkdir8(dirname13(worktreePath), { recursive: true });
21795
21898
  const branchAlreadyExists = await localBranchExists2(
21796
21899
  repository.repoRoot,
21797
21900
  branchName,
@@ -22200,7 +22303,7 @@ async function navigateToExistingWorktree(options, path9, worktree, runtime) {
22200
22303
  {
22201
22304
  branch: worktree?.branch ?? void 0,
22202
22305
  path: path9,
22203
- repo: basename10(repository.repoRoot),
22306
+ repo: basename11(repository.repoRoot),
22204
22307
  slot: await runtime.slots.getWorktreeSlot(path9)
22205
22308
  },
22206
22309
  options.stderr
@@ -22340,7 +22443,7 @@ async function runHistoryCommand(options) {
22340
22443
  }
22341
22444
 
22342
22445
  // src/cli/commands/hub.ts
22343
- import { basename as basename11 } from "node:path";
22446
+ import { basename as basename12 } from "node:path";
22344
22447
  import { stdin as stdin2, stdout as stdout2 } from "node:process";
22345
22448
  var MAX_HUB_REPOSITORY_CONCURRENCY = 4;
22346
22449
  async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependencies) {
@@ -22365,7 +22468,7 @@ async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependen
22365
22468
  existing.sources.push(source);
22366
22469
  } else {
22367
22470
  groups.set(repoRoot, {
22368
- name: source.repoName || basename11(repoRoot),
22471
+ name: source.repoName || basename12(repoRoot),
22369
22472
  sources: [source]
22370
22473
  });
22371
22474
  }
@@ -22616,7 +22719,7 @@ function formatHumanText(value, maxLength) {
22616
22719
  import { constants as constants8 } from "node:fs";
22617
22720
  import { access as access9, mkdir as mkdir9, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
22618
22721
  import { homedir as homedir8 } from "node:os";
22619
- import { dirname as dirname13 } from "node:path";
22722
+ import { dirname as dirname14 } from "node:path";
22620
22723
  var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
22621
22724
  var SHELL_WRAPPED_COMMANDS = [
22622
22725
  {
@@ -22741,7 +22844,7 @@ async function runDefaultOnboarding(context, runtime) {
22741
22844
  const installCompletion = await promptForCompletion(shell, completionPath);
22742
22845
  if (installCompletion === null) return abortDefaultOnboarding();
22743
22846
  if (installCompletion) {
22744
- await mkdir9(dirname13(completionPath), { recursive: true });
22847
+ await mkdir9(dirname14(completionPath), { recursive: true });
22745
22848
  await writeFile8(completionPath, renderShellCompletion(shell), "utf8");
22746
22849
  }
22747
22850
  if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
@@ -22847,7 +22950,7 @@ async function applyOnboardingResult(result, home, runtime) {
22847
22950
  runtime
22848
22951
  );
22849
22952
  if (result.installCompletion) {
22850
- await mkdir9(dirname13(completionPath), { recursive: true });
22953
+ await mkdir9(dirname14(completionPath), { recursive: true });
22851
22954
  await writeFile8(
22852
22955
  completionPath,
22853
22956
  renderShellCompletion(result.shell),
@@ -22867,7 +22970,7 @@ async function applyOnboardingResult(result, home, runtime) {
22867
22970
  }
22868
22971
  async function installOnboardingShellIntegration(shell, home) {
22869
22972
  const rcPath = resolveShellConfigPath(shell, home);
22870
- await mkdir9(dirname13(rcPath), { recursive: true });
22973
+ await mkdir9(dirname14(rcPath), { recursive: true });
22871
22974
  const current = await readExistingConfig(rcPath);
22872
22975
  if (shell === "fish") {
22873
22976
  await writeFile8(
@@ -22955,7 +23058,7 @@ async function runLegacyInitCommand(options, runtime) {
22955
23058
  return 0;
22956
23059
  }
22957
23060
  const rcPath = resolveShellConfigPath(shell, home);
22958
- await mkdir9(dirname13(rcPath), { recursive: true });
23061
+ await mkdir9(dirname14(rcPath), { recursive: true });
22959
23062
  const current = await readExistingConfig(rcPath);
22960
23063
  const next = upsertShellIntegration(current, script);
22961
23064
  await writeFile8(rcPath, next, "utf8");
@@ -23313,7 +23416,7 @@ function sortWorktrees(worktrees) {
23313
23416
  // src/cli/commands/open.ts
23314
23417
  import { execFile as execFile9 } from "node:child_process";
23315
23418
  import { access as access10, writeFile as writeFile9 } from "node:fs/promises";
23316
- import { join as join14 } from "node:path";
23419
+ import { join as join15 } from "node:path";
23317
23420
  import { promisify as promisify10 } from "node:util";
23318
23421
  var execFileAsync9 = promisify10(execFile9);
23319
23422
  function createOpenCommand(dependencies = {}) {
@@ -23492,7 +23595,7 @@ async function defaultPromptForEditor(editors) {
23492
23595
  return choice;
23493
23596
  }
23494
23597
  async function ensureWorkspaceFile(worktreePath, repoName) {
23495
- const workspacePath = join14(worktreePath, `${repoName}.code-workspace`);
23598
+ const workspacePath = join15(worktreePath, `${repoName}.code-workspace`);
23496
23599
  try {
23497
23600
  await access10(workspacePath);
23498
23601
  return workspacePath;
@@ -23879,7 +23982,7 @@ function formatError(error) {
23879
23982
  }
23880
23983
 
23881
23984
  // src/cli/commands/remove.ts
23882
- import { basename as basename12 } from "node:path";
23985
+ import { basename as basename13 } from "node:path";
23883
23986
  var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
23884
23987
  function createRemoveCommand(dependencies = {}) {
23885
23988
  const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
@@ -24008,7 +24111,7 @@ function createRemoveCommand(dependencies = {}) {
24008
24111
  {
24009
24112
  branch: worktree.branch ?? void 0,
24010
24113
  path: worktree.path,
24011
- repo: basename12(repository.repoRoot)
24114
+ repo: basename13(repository.repoRoot)
24012
24115
  },
24013
24116
  options.stderr
24014
24117
  );
@@ -24447,7 +24550,7 @@ function resolveConfiguredString2(value) {
24447
24550
 
24448
24551
  // src/cli/commands/sync-files-command.ts
24449
24552
  import { homedir as homedir9 } from "node:os";
24450
- import { join as join15 } from "node:path";
24553
+ import { join as join16 } from "node:path";
24451
24554
  async function runSyncFilesCommand(options) {
24452
24555
  const runtime = options.runtime ?? defaultCliDependencies;
24453
24556
  const { validateSyncFilePattern: validateSyncFilePattern2 } = runtime.filesystem;
@@ -24601,7 +24704,7 @@ function writeError(options, message) {
24601
24704
  }
24602
24705
  function expandTilde3(value, home) {
24603
24706
  if (value === "~") return home;
24604
- if (value.startsWith("~/")) return join15(home, value.slice(2));
24707
+ if (value.startsWith("~/")) return join16(home, value.slice(2));
24605
24708
  return value;
24606
24709
  }
24607
24710
 
@@ -24969,6 +25072,10 @@ function registerCommands(program2) {
24969
25072
  ).option(
24970
25073
  "--stale",
24971
25074
  "only target clean worktrees whose upstream is gone and branch is merged into the default branch"
25075
+ ).option(
25076
+ "--path <pattern>",
25077
+ "only target worktrees under a directory or matching a path glob (repeatable)",
25078
+ collectOption
24972
25079
  ).option("--dry-run", "show what would be deleted without removing anything").option(
24973
25080
  "--json",
24974
25081
  "emit JSON on success or error instead of human-readable output"
@@ -25336,6 +25443,7 @@ function attachCommandActions(program2, options) {
25336
25443
  dryRun: commandOptions2.dryRun,
25337
25444
  force: commandOptions2.force,
25338
25445
  json: commandOptions2.json,
25446
+ paths: commandOptions2.path,
25339
25447
  stale: commandOptions2.stale,
25340
25448
  runtime: options.dependencies,
25341
25449
  stderr: options.stderr,
@@ -25463,6 +25571,9 @@ function requireCommand(parent, name) {
25463
25571
  }
25464
25572
  return command;
25465
25573
  }
25574
+ function collectOption(value, previous = []) {
25575
+ return [...previous, value];
25576
+ }
25466
25577
  function notImplemented(commandName) {
25467
25578
  return () => {
25468
25579
  throw new Error(`'${commandName}' is not implemented yet.`);