@youtyan/code-viewer 0.8.1 → 0.8.3

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.
@@ -666,9 +666,47 @@ var init_annotations = __esm(() => {
666
666
  });
667
667
  });
668
668
 
669
+ // web-src/server/cache.ts
670
+ import { lstatSync } from "node:fs";
671
+ import { join as join2 } from "node:path";
672
+ function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
673
+ return !!cached && now - cached.storedAt <= ttlMs;
674
+ }
675
+ function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MAX_TIMED_CACHE_ENTRIES) {
676
+ cache.set(key, { ...value, storedAt: now });
677
+ while (cache.size > maxEntries) {
678
+ const oldest = cache.keys().next().value;
679
+ if (oldest === undefined)
680
+ break;
681
+ cache.delete(oldest);
682
+ }
683
+ }
684
+ function worktreeFileSignature(path, cwd) {
685
+ try {
686
+ const stats = lstatSync(join2(cwd, path));
687
+ const inode = "ino" in stats ? stats.ino : 0;
688
+ return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
689
+ } catch {
690
+ return "state:missing";
691
+ }
692
+ }
693
+ function fileDiffCacheKey(options) {
694
+ const worktreeTarget = options.range.from === "worktree" || !options.range.to || options.range.to === "worktree";
695
+ if (options.isUntracked && !worktreeTarget) {
696
+ throw new Error("untracked file diffs require a worktree range");
697
+ }
698
+ const signature = worktreeTarget ? `\x00${worktreeFileSignature(options.path, options.cwd)}` : "";
699
+ if (options.isUntracked) {
700
+ return `u\x00${options.path}${signature}\x00${options.extras.join("\x00")}`;
701
+ }
702
+ return `t\x00${options.path}\x00${options.oldPath || ""}${signature}\x00${[...options.extras, ...options.args].join("\x00")}`;
703
+ }
704
+ var CACHE_TTL_MS = 1500, MAX_TIMED_CACHE_ENTRIES = 200;
705
+ var init_cache = () => {};
706
+
669
707
  // web-src/server/command-resolver.ts
670
708
  import { accessSync, constants, realpathSync, statSync } from "node:fs";
671
- import { dirname as dirname2, isAbsolute, join as join2, relative } from "node:path";
709
+ import { dirname as dirname2, isAbsolute, join as join3, relative } from "node:path";
672
710
  function isExternalCommandName(value) {
673
711
  return commandNameSet.has(value);
674
712
  }
@@ -803,7 +841,7 @@ function findGitRootByWalking(start) {
803
841
  let current = start;
804
842
  for (;; ) {
805
843
  try {
806
- statSync(join2(current, ".git"));
844
+ statSync(join3(current, ".git"));
807
845
  return realpathSync(current);
808
846
  } catch {}
809
847
  const parent = dirname2(current);
@@ -835,6 +873,110 @@ var init_command_resolver = __esm(() => {
835
873
  activeOverrides = new Map;
836
874
  });
837
875
 
876
+ // web-src/server/name-pattern.ts
877
+ function parseGlobSegment(pattern) {
878
+ const matchers = [];
879
+ for (let i = 0;i < pattern.length; i++) {
880
+ const ch = pattern[i];
881
+ if (ch === "*") {
882
+ matchers.push({ kind: "star" });
883
+ } else if (ch === "?") {
884
+ matchers.push({ kind: "any" });
885
+ } else if (ch === "[") {
886
+ const close = pattern.indexOf("]", i + 1);
887
+ if (close === -1) {
888
+ matchers.push({ kind: "literal", ch: "[" });
889
+ continue;
890
+ }
891
+ const rawBody = pattern.slice(i + 1, close);
892
+ const negate = rawBody.startsWith("!");
893
+ matchers.push({
894
+ kind: "class",
895
+ body: negate ? rawBody.slice(1) : rawBody,
896
+ negate
897
+ });
898
+ i = close;
899
+ } else {
900
+ matchers.push({ kind: "literal", ch });
901
+ }
902
+ }
903
+ return matchers;
904
+ }
905
+ function charInClassBody(ch, body) {
906
+ for (let i = 0;i < body.length; ) {
907
+ if (body[i + 1] === "-" && i + 2 < body.length) {
908
+ if (ch >= body[i] && ch <= body[i + 2])
909
+ return true;
910
+ i += 3;
911
+ } else {
912
+ if (ch === body[i])
913
+ return true;
914
+ i += 1;
915
+ }
916
+ }
917
+ return false;
918
+ }
919
+ function matchesAt(matcher, ch) {
920
+ switch (matcher.kind) {
921
+ case "literal":
922
+ return matcher.ch === ch;
923
+ case "any":
924
+ return true;
925
+ case "class":
926
+ return charInClassBody(ch, matcher.body) !== matcher.negate;
927
+ case "star":
928
+ return false;
929
+ }
930
+ }
931
+ function matchGlobSegment(matchers, name) {
932
+ let mi = 0;
933
+ let ni = 0;
934
+ let starMi = -1;
935
+ let starNi = -1;
936
+ while (ni < name.length) {
937
+ const matcher = matchers[mi];
938
+ if (matcher && matcher.kind === "star") {
939
+ starMi = mi;
940
+ starNi = ni;
941
+ mi++;
942
+ } else if (matcher && matchesAt(matcher, name[ni])) {
943
+ mi++;
944
+ ni++;
945
+ } else if (starMi !== -1) {
946
+ mi = starMi + 1;
947
+ starNi++;
948
+ ni = starNi;
949
+ } else {
950
+ return false;
951
+ }
952
+ }
953
+ while (matchers[mi]?.kind === "star")
954
+ mi++;
955
+ return mi === matchers.length;
956
+ }
957
+ function compileNamePatterns(patterns) {
958
+ const literals = new Set;
959
+ const globs = [];
960
+ for (const pattern of patterns) {
961
+ const lower = pattern.toLowerCase();
962
+ if (GLOB_CHARS.test(lower)) {
963
+ globs.push(parseGlobSegment(lower));
964
+ } else {
965
+ literals.add(lower);
966
+ }
967
+ }
968
+ return {
969
+ matches(name) {
970
+ const lower = name.toLowerCase();
971
+ return literals.has(lower) || globs.some((matchers) => matchGlobSegment(matchers, lower));
972
+ }
973
+ };
974
+ }
975
+ var GLOB_CHARS;
976
+ var init_name_pattern = __esm(() => {
977
+ GLOB_CHARS = /[*?[\]]/;
978
+ });
979
+
838
980
  // web-src/server/runtime.ts
839
981
  import { spawn, spawnSync } from "node:child_process";
840
982
  import { createReadStream, promises as fs } from "node:fs";
@@ -1113,14 +1255,16 @@ var init_runtime = () => {};
1113
1255
  import {
1114
1256
  closeSync,
1115
1257
  existsSync,
1116
- lstatSync,
1258
+ lstatSync as lstatSync2,
1117
1259
  openSync,
1118
1260
  readdirSync,
1119
1261
  readFileSync,
1262
+ readlinkSync,
1120
1263
  readSync,
1264
+ realpathSync as realpathSync2,
1121
1265
  statSync as statSync2
1122
1266
  } from "node:fs";
1123
- import { join as join3 } from "node:path";
1267
+ import { dirname as dirname3, join as join4, posix, relative as relative2 } from "node:path";
1124
1268
  function normalizeBlameRef(ref, base) {
1125
1269
  const rawRef = ref || "worktree";
1126
1270
  if (base === "worktree" && rawRef !== "worktree") {
@@ -1218,12 +1362,76 @@ async function statusPorcelainForPathAsync(path, cwd) {
1218
1362
  error: gitFailureMessage(res, "git status failed")
1219
1363
  };
1220
1364
  }
1365
+ async function repoStatusMapAsync(cwd, now = Date.now()) {
1366
+ const cached = repoStatusMapCache.get(cwd);
1367
+ if (cacheFresh(cached, now))
1368
+ return cached.map;
1369
+ const map = new Map;
1370
+ const res = await runGitAsync([
1371
+ "git",
1372
+ "-c",
1373
+ "core.quotepath=false",
1374
+ "status",
1375
+ "--porcelain=v1",
1376
+ "-z",
1377
+ "--untracked-files=all"
1378
+ ], cwd);
1379
+ if (res.code !== 0)
1380
+ return map;
1381
+ const records = res.stdout.split("\x00").filter(Boolean);
1382
+ for (let i = 0;i < records.length; i++) {
1383
+ const record = records[i];
1384
+ const xy = record.slice(0, 2);
1385
+ const path = record.slice(3);
1386
+ if (!path)
1387
+ continue;
1388
+ if (xy === "??") {
1389
+ map.set(path, "A");
1390
+ continue;
1391
+ }
1392
+ if (xy[0] === "R" || xy[0] === "C" || xy[1] === "R" || xy[1] === "C") {
1393
+ i++;
1394
+ map.set(path, "R");
1395
+ continue;
1396
+ }
1397
+ const code = xy[0] !== " " ? xy[0] : xy[1];
1398
+ if (code && code !== " ")
1399
+ map.set(path, code);
1400
+ }
1401
+ setTimedCacheEntry(repoStatusMapCache, cwd, { map }, now);
1402
+ return map;
1403
+ }
1221
1404
  function show(ref, path, cwd) {
1222
1405
  return run(["git", "show", `${ref}:${path}`], cwd);
1223
1406
  }
1224
1407
  function showAsync(ref, path, cwd) {
1225
1408
  return runGitAsync(["git", "show", `${ref}:${path}`], cwd);
1226
1409
  }
1410
+ function resolveSymlinkPath(linkPath, target) {
1411
+ if (!target || target.startsWith("/") || target.includes("\x00"))
1412
+ return null;
1413
+ const baseDir = dirname3(linkPath);
1414
+ const combined = baseDir === "." ? target : `${baseDir}/${target}`;
1415
+ const normalized = posix.normalize(combined);
1416
+ if (normalized === "." || normalized === "")
1417
+ return "";
1418
+ if (normalized === ".." || normalized.startsWith("../"))
1419
+ return null;
1420
+ return normalized;
1421
+ }
1422
+ async function gitSymlinkTargetMetadataAsync(ref, path, cwd) {
1423
+ const res = await showAsync(ref, path, cwd);
1424
+ if (res.code !== 0)
1425
+ return { symlink_target_type: "missing" };
1426
+ const target = res.stdout;
1427
+ const resolved = resolveSymlinkPath(path, target);
1428
+ if (resolved === null)
1429
+ return { symlink_target: target, symlink_target_type: "missing" };
1430
+ const type = await runGitAsync(["git", "cat-file", "-t", `${ref}:${resolved}`], cwd);
1431
+ const kind = type.stdout.trim();
1432
+ const symlink_target_type = kind === "tree" ? "tree" : kind === "blob" ? "blob" : "missing";
1433
+ return symlink_target_type === "missing" ? { symlink_target: target, symlink_target_type } : { symlink_target: target, symlink_target_type, resolved_path: resolved };
1434
+ }
1227
1435
  function catFileBlobStream(oid, cwd) {
1228
1436
  return spawnStream(resolveGitArgs(["git", "cat-file", "blob", oid]), cwd);
1229
1437
  }
@@ -1854,7 +2062,7 @@ function isGitInternalPath(path) {
1854
2062
  return pathHasSegment(path, ".git");
1855
2063
  }
1856
2064
  function syntheticUncommittedBlameFromWorktree(cwd, path) {
1857
- const filePath = join3(cwd, path);
2065
+ const filePath = join4(cwd, path);
1858
2066
  try {
1859
2067
  const stat = statSync2(filePath);
1860
2068
  if (!stat.isFile())
@@ -2110,10 +2318,10 @@ function sortTreeEntries(entries) {
2110
2318
  function omittedWorktreeDirectoryReason(name, omitDirNames) {
2111
2319
  if (name === ".git")
2112
2320
  return "internal";
2113
- return omitDirNames.has(name) ? "heavy" : undefined;
2321
+ return omitDirNames.matches(name) ? "heavy" : undefined;
2114
2322
  }
2115
2323
  function worktreeSubmodulePaths(cwd) {
2116
- if (!existsSync(join3(cwd, ".gitmodules")))
2324
+ if (!existsSync(join4(cwd, ".gitmodules")))
2117
2325
  return new Set;
2118
2326
  const res = run(["git", "config", "--file", ".gitmodules", "--get-regexp", "\\.path$"], cwd);
2119
2327
  if (res.code !== 0)
@@ -2125,7 +2333,7 @@ function worktreeSubmodulePaths(cwd) {
2125
2333
  }).filter(Boolean));
2126
2334
  }
2127
2335
  async function worktreeSubmodulePathsAsync(cwd) {
2128
- if (!existsSync(join3(cwd, ".gitmodules")))
2336
+ if (!existsSync(join4(cwd, ".gitmodules")))
2129
2337
  return new Set;
2130
2338
  const res = await runGitAsync(["git", "config", "--file", ".gitmodules", "--get-regexp", "\\.path$"], cwd);
2131
2339
  if (res.code !== 0)
@@ -2136,18 +2344,67 @@ async function worktreeSubmodulePathsAsync(cwd) {
2136
2344
  return split >= 0 ? normalizeTreePath(line.slice(split + 1)) : "";
2137
2345
  }).filter(Boolean));
2138
2346
  }
2139
- function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, excludeNames, submodulePaths) {
2140
- if (excludeNames.has(name.toLowerCase()))
2347
+ function realpathWithinRepo(cwd, full, allowRoot) {
2348
+ try {
2349
+ const realCwd = realpathSync2(cwd);
2350
+ const realFull = realpathSync2(full);
2351
+ const rel = relative2(realCwd, realFull);
2352
+ if (rel.startsWith("..") || rel.startsWith("/") || rel.startsWith("\\"))
2353
+ return null;
2354
+ if (rel === "" && !allowRoot)
2355
+ return null;
2356
+ return realFull;
2357
+ } catch {
2358
+ return null;
2359
+ }
2360
+ }
2361
+ function resolveWorktreeSymlinkTarget(cwd, full) {
2362
+ let symlink_target;
2363
+ try {
2364
+ symlink_target = readlinkSync(full);
2365
+ } catch {
2366
+ symlink_target = undefined;
2367
+ }
2368
+ let symlink_target_type = "missing";
2369
+ if (realpathWithinRepo(cwd, full, false) !== null) {
2370
+ try {
2371
+ const stat = statSync2(full);
2372
+ symlink_target_type = stat.isDirectory() ? "tree" : stat.isFile() ? "blob" : "missing";
2373
+ } catch {
2374
+ symlink_target_type = "missing";
2375
+ }
2376
+ }
2377
+ return symlink_target === undefined ? { symlink_target_type } : { symlink_target, symlink_target_type };
2378
+ }
2379
+ function recursiveWorktreeFileEntry(cwd, full, name, path, isSymlink) {
2380
+ const symlinkInfo = isSymlink ? resolveWorktreeSymlinkTarget(cwd, full) : null;
2381
+ return {
2382
+ name,
2383
+ path,
2384
+ type: "blob",
2385
+ ...symlinkInfo ? { is_symlink: true, ...symlinkInfo } : {}
2386
+ };
2387
+ }
2388
+ function worktreeEntryFromDirent(cwd, base, dir, name, isDirectory, isSymlink, omitDirNames, excludeNames, submodulePaths) {
2389
+ if (excludeNames.matches(name))
2141
2390
  return {
2142
2391
  name,
2143
2392
  path: "",
2144
2393
  type: isDirectory ? "tree" : "blob"
2145
2394
  };
2146
2395
  const entryPath = base ? `${base}/${name}` : name;
2147
- const type = isDirectory ? hasDotGitEntry(join3(dir, name)) ? "commit" : "tree" : "blob";
2396
+ const symlinkInfo = isSymlink ? resolveWorktreeSymlinkTarget(cwd, join4(dir, name)) : null;
2397
+ const resolvedIsDirectory = symlinkInfo ? symlinkInfo.symlink_target_type === "tree" : isDirectory;
2398
+ const type = symlinkInfo && symlinkInfo.symlink_target_type === "missing" ? "blob" : resolvedIsDirectory ? hasDotGitEntry(join4(dir, name)) ? "commit" : "tree" : "blob";
2148
2399
  const omittedReason = type === "tree" ? omittedWorktreeDirectoryReason(name, omitDirNames) : undefined;
2149
2400
  const submodule = type === "commit" && submodulePaths.has(entryPath) ? true : undefined;
2150
- const baseEntry = submodule ? { name, path: entryPath, type, submodule } : { name, path: entryPath, type };
2401
+ const baseEntry = {
2402
+ name,
2403
+ path: entryPath,
2404
+ type,
2405
+ ...submodule ? { submodule } : {},
2406
+ ...symlinkInfo ? { is_symlink: true, ...symlinkInfo } : {}
2407
+ };
2151
2408
  return omittedReason ? {
2152
2409
  ...baseEntry,
2153
2410
  children_omitted: true,
@@ -2156,14 +2413,16 @@ function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, exc
2156
2413
  }
2157
2414
  function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
2158
2415
  const base = normalizeTreePath(path);
2159
- const root = join3(cwd, base);
2160
- const omitDirNameSet = new Set(omitDirNames);
2161
- const excludeNameSet = new Set(excludeNames.map((name) => name.toLowerCase()));
2416
+ const root = join4(cwd, base);
2417
+ if (realpathWithinRepo(cwd, root, true) === null)
2418
+ return [];
2419
+ const omitDirNameSet = compileNamePatterns(omitDirNames);
2420
+ const excludeNameSet = compileNamePatterns(excludeNames);
2162
2421
  const submodulePaths = worktreeSubmodulePaths(cwd);
2163
2422
  let directEntries;
2164
2423
  try {
2165
2424
  const dirents = readdirSync(root, { withFileTypes: true });
2166
- directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(base, root, entry.name, entry.isDirectory(), omitDirNameSet, excludeNameSet, submodulePaths)).filter((entry) => entry.path));
2425
+ directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(cwd, base, root, entry.name, entry.isDirectory(), entry.isSymbolicLink(), omitDirNameSet, excludeNameSet, submodulePaths)).filter((entry) => entry.path));
2167
2426
  } catch {
2168
2427
  return [];
2169
2428
  }
@@ -2200,10 +2459,10 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
2200
2459
  return;
2201
2460
  }
2202
2461
  for (const entry of entries) {
2203
- if (excludeNameSet.has(entry.name.toLowerCase()))
2462
+ if (excludeNameSet.matches(entry.name))
2204
2463
  continue;
2205
2464
  const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
2206
- const full = join3(dir, entry.name);
2465
+ const full = join4(dir, entry.name);
2207
2466
  if (entry.isDirectory()) {
2208
2467
  const omittedReason = omittedWorktreeDirectoryReason(entry.name, omitDirNameSet);
2209
2468
  if (omittedReason) {
@@ -2221,11 +2480,7 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
2221
2480
  continue;
2222
2481
  walk(full, entryPath, depth + 1);
2223
2482
  } else if (entry.isFile() || entry.isSymbolicLink()) {
2224
- if (!pushRecursiveEntry({
2225
- name: entry.name,
2226
- path: entryPath,
2227
- type: "blob"
2228
- }))
2483
+ if (!pushRecursiveEntry(recursiveWorktreeFileEntry(cwd, full, entry.name, entryPath, entry.isSymbolicLink())))
2229
2484
  return;
2230
2485
  }
2231
2486
  }
@@ -2235,14 +2490,16 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
2235
2490
  }
2236
2491
  async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
2237
2492
  const base = normalizeTreePath(path);
2238
- const root = join3(cwd, base);
2239
- const omitDirNameSet = new Set(omitDirNames);
2240
- const excludeNameSet = new Set(excludeNames.map((name) => name.toLowerCase()));
2493
+ const root = join4(cwd, base);
2494
+ if (realpathWithinRepo(cwd, root, true) === null)
2495
+ return [];
2496
+ const omitDirNameSet = compileNamePatterns(omitDirNames);
2497
+ const excludeNameSet = compileNamePatterns(excludeNames);
2241
2498
  const submodulePaths = await worktreeSubmodulePathsAsync(cwd);
2242
2499
  let directEntries;
2243
2500
  try {
2244
2501
  const dirents = readdirSync(root, { withFileTypes: true });
2245
- directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(base, root, entry.name, entry.isDirectory(), omitDirNameSet, excludeNameSet, submodulePaths)).filter((entry) => entry.path));
2502
+ directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(cwd, base, root, entry.name, entry.isDirectory(), entry.isSymbolicLink(), omitDirNameSet, excludeNameSet, submodulePaths)).filter((entry) => entry.path));
2246
2503
  } catch {
2247
2504
  return [];
2248
2505
  }
@@ -2287,10 +2544,10 @@ async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames
2287
2544
  return;
2288
2545
  }
2289
2546
  for (const entry of entries) {
2290
- if (excludeNameSet.has(entry.name.toLowerCase()))
2547
+ if (excludeNameSet.matches(entry.name))
2291
2548
  continue;
2292
2549
  const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
2293
- const full = join3(dir, entry.name);
2550
+ const full = join4(dir, entry.name);
2294
2551
  if (entry.isDirectory()) {
2295
2552
  const omittedReason = omittedWorktreeDirectoryReason(entry.name, omitDirNameSet);
2296
2553
  if (omittedReason) {
@@ -2308,11 +2565,7 @@ async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames
2308
2565
  continue;
2309
2566
  await walk(full, entryPath, depth + 1);
2310
2567
  } else if (entry.isFile() || entry.isSymbolicLink()) {
2311
- if (!pushRecursiveEntry({
2312
- name: entry.name,
2313
- path: entryPath,
2314
- type: "blob"
2315
- }))
2568
+ if (!pushRecursiveEntry(recursiveWorktreeFileEntry(cwd, full, entry.name, entryPath, entry.isSymbolicLink())))
2316
2569
  return;
2317
2570
  }
2318
2571
  }
@@ -2322,12 +2575,24 @@ async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames
2322
2575
  }
2323
2576
  function hasDotGitEntry(dir) {
2324
2577
  try {
2325
- lstatSync(join3(dir, ".git"));
2578
+ lstatSync2(join4(dir, ".git"));
2326
2579
  return true;
2327
2580
  } catch (err) {
2328
2581
  return !!err && typeof err === "object" && "code" in err && err.code !== "ENOENT";
2329
2582
  }
2330
2583
  }
2584
+ function parseLsTreeRecord(rec, allowedTypes) {
2585
+ const match = rec.match(new RegExp(`^(\\d+)\\s+(${allowedTypes})\\s+[0-9a-fA-F]+\\t(.+)$`));
2586
+ if (!match)
2587
+ return null;
2588
+ const [, mode, type, entryPath] = match;
2589
+ return {
2590
+ name: entryPath.split("/").pop() || entryPath,
2591
+ path: entryPath,
2592
+ type,
2593
+ ...mode === LS_TREE_SYMLINK_MODE ? { is_symlink: true } : {}
2594
+ };
2595
+ }
2331
2596
  function gitTreeEntries(ref, path, cwd, recursive) {
2332
2597
  const base = normalizeTreePath(path);
2333
2598
  const args = ["git", "-c", "core.quotepath=false", "ls-tree"];
@@ -2340,17 +2605,7 @@ function gitTreeEntries(ref, path, cwd, recursive) {
2340
2605
  if (res.code !== 0)
2341
2606
  return { code: res.code, entries: [], stderr: res.stderr };
2342
2607
  const allowedTypes = recursive ? "blob|commit" : "tree|blob|commit";
2343
- let entries = res.stdout.split("\x00").filter(Boolean).map((rec) => {
2344
- const match = rec.match(new RegExp(`^\\d+\\s+(${allowedTypes})\\s+[0-9a-fA-F]+\\t(.+)$`));
2345
- if (!match)
2346
- return null;
2347
- const entryPath = match[2];
2348
- return {
2349
- name: entryPath.split("/").pop() || entryPath,
2350
- path: entryPath,
2351
- type: match[1]
2352
- };
2353
- }).filter((entry) => !!entry);
2608
+ let entries = res.stdout.split("\x00").filter(Boolean).map((rec) => parseLsTreeRecord(rec, allowedTypes)).filter((entry) => !!entry);
2354
2609
  if (recursive)
2355
2610
  entries.sort((a, b) => a.path.localeCompare(b.path));
2356
2611
  else
@@ -2369,17 +2624,7 @@ async function gitTreeEntriesAsync(ref, path, cwd, recursive) {
2369
2624
  if (res.code !== 0)
2370
2625
  return { code: res.code, entries: [], stderr: res.stderr };
2371
2626
  const allowedTypes = recursive ? "blob|commit" : "tree|blob|commit";
2372
- let entries = res.stdout.split("\x00").filter(Boolean).map((rec) => {
2373
- const match = rec.match(new RegExp(`^\\d+\\s+(${allowedTypes})\\s+[0-9a-fA-F]+\\t(.+)$`));
2374
- if (!match)
2375
- return null;
2376
- const entryPath = match[2];
2377
- return {
2378
- name: entryPath.split("/").pop() || entryPath,
2379
- path: entryPath,
2380
- type: match[1]
2381
- };
2382
- }).filter((entry) => !!entry);
2627
+ let entries = res.stdout.split("\x00").filter(Boolean).map((rec) => parseLsTreeRecord(rec, allowedTypes)).filter((entry) => !!entry);
2383
2628
  if (recursive)
2384
2629
  entries.sort((a, b) => a.path.localeCompare(b.path));
2385
2630
  else
@@ -2443,7 +2688,7 @@ async function listTreeResultAsync(ref, path, cwd, options = {}) {
2443
2688
  }
2444
2689
  function untrackedMeta(cwd) {
2445
2690
  return untracked(cwd).flatMap((path) => {
2446
- const full = join3(cwd, path);
2691
+ const full = join4(cwd, path);
2447
2692
  let fileExists = false;
2448
2693
  try {
2449
2694
  fileExists = existsSync(full) && statSync2(full).isFile();
@@ -2475,7 +2720,7 @@ function untrackedMeta(cwd) {
2475
2720
  async function untrackedMetaAsync(cwd) {
2476
2721
  const paths = await untrackedAsync(cwd);
2477
2722
  return paths.flatMap((path) => {
2478
- const full = join3(cwd, path);
2723
+ const full = join4(cwd, path);
2479
2724
  let fileExists = false;
2480
2725
  try {
2481
2726
  fileExists = existsSync(full) && statSync2(full).isFile();
@@ -2723,9 +2968,11 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
2723
2968
  lineTruncated
2724
2969
  };
2725
2970
  }
2726
- var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", ALWAYS_WORKTREE_OMIT_DIR_NAMES, DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS = 20000, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
2971
+ var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", ALWAYS_WORKTREE_OMIT_DIR_NAMES, DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS = 20000, repoStatusMapCache, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200, LS_TREE_SYMLINK_MODE = "120000";
2727
2972
  var init_git = __esm(() => {
2973
+ init_cache();
2728
2974
  init_command_resolver();
2975
+ init_name_pattern();
2729
2976
  init_runtime();
2730
2977
  ALWAYS_WORKTREE_OMIT_DIR_NAMES = [".devbox", ".direnv"];
2731
2978
  DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
@@ -2772,6 +3019,7 @@ var init_git = __esm(() => {
2772
3019
  "bin",
2773
3020
  "obj"
2774
3021
  ];
3022
+ repoStatusMapCache = new Map;
2775
3023
  });
2776
3024
 
2777
3025
  // web-src/server/server-registry.ts
@@ -2784,16 +3032,16 @@ import {
2784
3032
  writeFileSync
2785
3033
  } from "node:fs";
2786
3034
  import { homedir } from "node:os";
2787
- import { join as join4 } from "node:path";
3035
+ import { join as join5 } from "node:path";
2788
3036
  function registryDir() {
2789
3037
  const override = process.env.CODE_VIEWER_TEST_SERVER_REGISTRY_DIR;
2790
3038
  if (override)
2791
3039
  return override;
2792
- return join4(homedir(), ".cache", "code-viewer", "servers");
3040
+ return join5(homedir(), ".cache", "code-viewer", "servers");
2793
3041
  }
2794
3042
  function serverRegistryFilePath(root) {
2795
3043
  const hash = createHash("sha256").update(root).digest("hex").slice(0, 16);
2796
- return join4(registryDir(), `${hash}.json`);
3044
+ return join5(registryDir(), `${hash}.json`);
2797
3045
  }
2798
3046
  function writeServerRegistry(entry) {
2799
3047
  try {
@@ -2834,7 +3082,7 @@ function removeServerRegistry(root, pid) {
2834
3082
  var init_server_registry = () => {};
2835
3083
 
2836
3084
  // web-src/server/cli-helpers.ts
2837
- import { realpathSync as realpathSync2 } from "node:fs";
3085
+ import { realpathSync as realpathSync3 } from "node:fs";
2838
3086
  function takeValue(argv, index, flag) {
2839
3087
  const value = argv[index + 1];
2840
3088
  if (value === undefined)
@@ -2893,7 +3141,7 @@ function resolveRepoRootSafe(cwdOption) {
2893
3141
  const base = cwdOption || process.cwd();
2894
3142
  let baseReal;
2895
3143
  try {
2896
- baseReal = realpathSync2(base);
3144
+ baseReal = realpathSync3(base);
2897
3145
  } catch {
2898
3146
  return {
2899
3147
  ok: false,
@@ -4003,8 +4251,8 @@ __export(exports_file_cli, {
4003
4251
  FILE_DEFAULT_HISTORY_LIMIT: () => FILE_DEFAULT_HISTORY_LIMIT,
4004
4252
  FILE_AGENT_HELP: () => FILE_AGENT_HELP
4005
4253
  });
4006
- import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
4007
- import { join as join5, relative as relative2 } from "node:path";
4254
+ import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync4, statSync as statSync3 } from "node:fs";
4255
+ import { join as join6, relative as relative3 } from "node:path";
4008
4256
  function validatePath(value) {
4009
4257
  return validateRepoRelativePathValue(value, "--path");
4010
4258
  }
@@ -4408,13 +4656,13 @@ function sliceLines(text, start, end) {
4408
4656
  function safeWorktreePathFromRoot(root, path) {
4409
4657
  if (validatePath(path))
4410
4658
  return null;
4411
- const full = join5(root, path);
4659
+ const full = join6(root, path);
4412
4660
  if (!existsSync3(full))
4413
4661
  return null;
4414
4662
  try {
4415
- const realRoot = realpathSync3(root);
4416
- const realFull = realpathSync3(full);
4417
- const rel = relative2(realRoot, realFull);
4663
+ const realRoot = realpathSync4(root);
4664
+ const realFull = realpathSync4(full);
4665
+ const rel = relative3(realRoot, realFull);
4418
4666
  if (rel === "" || rel.startsWith("..") || rel.startsWith("/") || rel.startsWith("\\")) {
4419
4667
  return null;
4420
4668
  }
@@ -8766,11 +9014,11 @@ function normalizeGrepMax(value) {
8766
9014
  return Math.min(parsed, GREP_ABSOLUTE_MAX);
8767
9015
  }
8768
9016
  function isSkippableSearchPath(path, omitDirNames = [], excludeNames = []) {
8769
- const omitDirs = new Set(omitDirNames.map((name) => name.toLowerCase()));
8770
- const excluded = new Set(excludeNames.map((name) => name.toLowerCase()));
9017
+ const omitDirs = compileNamePatterns(omitDirNames);
9018
+ const excluded = compileNamePatterns(excludeNames);
8771
9019
  return path.split(/[\\/]+/).some((part) => {
8772
9020
  const lower = part.toLowerCase();
8773
- return lower === ".git" || lower === ".code-viewer" || omitDirs.has(lower) || excluded.has(lower);
9021
+ return lower === ".git" || lower === ".code-viewer" || omitDirs.matches(part) || excluded.matches(part);
8774
9022
  });
8775
9023
  }
8776
9024
  function fixedStringLineMatches(path, text, query, max) {
@@ -8877,6 +9125,7 @@ function parseGitGrepOutput(stdout, ref, max, omitDirNames = [], excludeNames =
8877
9125
  }
8878
9126
  var GREP_DEFAULT_MAX = 200, GREP_ABSOLUTE_MAX = 500, GREP_MAX_FILE_BYTES, FILE_SEARCH_ABSOLUTE_MAX = 50000, DEFAULT_EXCLUDE_NAMES;
8879
9127
  var init_search = __esm(() => {
9128
+ init_name_pattern();
8880
9129
  GREP_MAX_FILE_BYTES = 2 * 1024 * 1024;
8881
9130
  DEFAULT_EXCLUDE_NAMES = [".DS_Store"];
8882
9131
  });
@@ -9317,24 +9566,24 @@ Parse failures and unreachable servers exit 1.
9317
9566
 
9318
9567
  // web-src/server/root.ts
9319
9568
  import { existsSync as existsSync4 } from "node:fs";
9320
- import { dirname as dirname3, join as join6, normalize } from "node:path";
9569
+ import { dirname as dirname4, join as join7, normalize } from "node:path";
9321
9570
  import { fileURLToPath } from "node:url";
9322
9571
  function findRoot(start) {
9323
9572
  let current = start;
9324
9573
  for (let i = 0;i < 5; i++) {
9325
- if (existsSync4(join6(current, "package.json")) && existsSync4(join6(current, "web"))) {
9574
+ if (existsSync4(join7(current, "package.json")) && existsSync4(join7(current, "web"))) {
9326
9575
  return normalize(current);
9327
9576
  }
9328
- const parent = dirname3(current);
9577
+ const parent = dirname4(current);
9329
9578
  if (parent === current)
9330
9579
  break;
9331
9580
  current = parent;
9332
9581
  }
9333
- return normalize(join6(start, "..", ".."));
9582
+ return normalize(join7(start, "..", ".."));
9334
9583
  }
9335
9584
  var ROOT;
9336
9585
  var init_root = __esm(() => {
9337
- ROOT = findRoot(dirname3(fileURLToPath(import.meta.url)));
9586
+ ROOT = findRoot(dirname4(fileURLToPath(import.meta.url)));
9338
9587
  });
9339
9588
 
9340
9589
  // web-src/server/skill-cli.ts
@@ -9349,7 +9598,7 @@ __export(exports_skill_cli, {
9349
9598
  });
9350
9599
  import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "node:fs";
9351
9600
  import { homedir as homedir2 } from "node:os";
9352
- import { join as join7, resolve } from "node:path";
9601
+ import { join as join8, resolve } from "node:path";
9353
9602
  function parseAgentList(value) {
9354
9603
  if (value === "all")
9355
9604
  return [...AGENT_NAMES];
@@ -9409,7 +9658,7 @@ function parseSkillArgs(argv) {
9409
9658
  function discoverBundledSkills(skillsRoot) {
9410
9659
  if (!existsSync5(skillsRoot))
9411
9660
  return [];
9412
- return readdirSync2(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync5(join7(skillsRoot, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
9661
+ return readdirSync2(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync5(join8(skillsRoot, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
9413
9662
  }
9414
9663
  function installSkill(args, deps) {
9415
9664
  const skills = discoverBundledSkills(deps.skillsRoot);
@@ -9423,8 +9672,8 @@ function installSkill(args, deps) {
9423
9672
  const results = [];
9424
9673
  for (const agent of args.agents) {
9425
9674
  for (const skill of skills) {
9426
- const sourceDir = join7(deps.skillsRoot, skill);
9427
- const target = join7(base, AGENT_SKILL_DIRS[agent], "skills", skill);
9675
+ const sourceDir = join8(deps.skillsRoot, skill);
9676
+ const target = join8(base, AGENT_SKILL_DIRS[agent], "skills", skill);
9428
9677
  const action = existsSync5(target) ? "updated" : "installed";
9429
9678
  try {
9430
9679
  mkdirSync2(target, { recursive: true });
@@ -9453,7 +9702,7 @@ function runSkillCli(argv) {
9453
9702
  return;
9454
9703
  }
9455
9704
  const result = installSkill(parsed.args, {
9456
- skillsRoot: join7(ROOT, "skills"),
9705
+ skillsRoot: join8(ROOT, "skills"),
9457
9706
  homeDir: homedir2(),
9458
9707
  projectDir: process.cwd()
9459
9708
  });
@@ -14517,11 +14766,11 @@ import {
14517
14766
  existsSync as existsSync6,
14518
14767
  openSync as openSync2,
14519
14768
  readSync as readSync2,
14520
- realpathSync as realpathSync4,
14769
+ realpathSync as realpathSync5,
14521
14770
  statSync as statSync4
14522
14771
  } from "node:fs";
14523
14772
  import { lstat, open, readdir, readFile as readFile2, stat } from "node:fs/promises";
14524
- import { basename, join as join8, relative as relative3 } from "node:path";
14773
+ import { basename, join as join9, relative as relative4 } from "node:path";
14525
14774
  function isSqliteFile(fullPath) {
14526
14775
  try {
14527
14776
  const stat2 = statSync4(fullPath);
@@ -14592,7 +14841,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
14592
14841
  return;
14593
14842
  if (omitSet.has(entry.toLowerCase()))
14594
14843
  continue;
14595
- const full = join8(dir, entry);
14844
+ const full = join9(dir, entry);
14596
14845
  let entryStat;
14597
14846
  try {
14598
14847
  entryStat = await lstat(full);
@@ -14609,7 +14858,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
14609
14858
  continue;
14610
14859
  if (!await isSqliteFileAsync(full))
14611
14860
  continue;
14612
- const rel = relative3(cwd, full);
14861
+ const rel = relative4(cwd, full);
14613
14862
  if (rel.startsWith("..") || rel.startsWith("/"))
14614
14863
  continue;
14615
14864
  results.push({
@@ -14636,18 +14885,18 @@ function validateDbPath(cwd, dbPath) {
14636
14885
  const parts = dbPath.split(/[\\/]+/);
14637
14886
  if (parts.some((p) => p === ".." || p.toLowerCase() === ".git" || p.toLowerCase() === ".code-viewer"))
14638
14887
  return null;
14639
- const full = join8(cwd, dbPath);
14888
+ const full = join9(cwd, dbPath);
14640
14889
  if (!existsSync6(full))
14641
14890
  return null;
14642
14891
  let realCwd;
14643
14892
  let realFull;
14644
14893
  try {
14645
- realCwd = realpathSync4(cwd);
14646
- realFull = realpathSync4(full);
14894
+ realCwd = realpathSync5(cwd);
14895
+ realFull = realpathSync5(full);
14647
14896
  } catch {
14648
14897
  return null;
14649
14898
  }
14650
- const rel = relative3(realCwd, realFull);
14899
+ const rel = relative4(realCwd, realFull);
14651
14900
  if (rel === "" || rel.startsWith("..") || rel.startsWith("/"))
14652
14901
  return null;
14653
14902
  if (!isSqliteFile(realFull))
@@ -14806,7 +15055,7 @@ function resolveEnvValue(raw, composeDirEnv = {}) {
14806
15055
  }
14807
15056
  async function readDotenvAsync(composeDir) {
14808
15057
  try {
14809
- const content = await readFile2(join8(composeDir, ".env"), "utf-8");
15058
+ const content = await readFile2(join9(composeDir, ".env"), "utf-8");
14810
15059
  return parseDotenvContent(content);
14811
15060
  } catch {
14812
15061
  return {};
@@ -14928,7 +15177,7 @@ function parseComposeContent(content, filepath, composeDir, cwd, composeDirEnv,
14928
15177
  for (let match = serviceRegex.exec(servicesBlock);match !== null; match = serviceRegex.exec(servicesBlock)) {
14929
15178
  servicePositions.push({ name: match[1], start: match.index });
14930
15179
  }
14931
- const relDir = relative3(cwd, composeDir);
15180
+ const relDir = relative4(cwd, composeDir);
14932
15181
  const isRoot = relDir === "" || relDir === ".";
14933
15182
  const relDirSlash = relDir.replace(/\\/g, "/");
14934
15183
  const filename = basename(filepath);
@@ -15033,7 +15282,7 @@ async function walkForMarkerFileAsync(dir, depth, omitSet, hasCapacity, visitDir
15033
15282
  return;
15034
15283
  if (omitSet.has(entry.toLowerCase()))
15035
15284
  continue;
15036
- const full = join8(dir, entry);
15285
+ const full = join9(dir, entry);
15037
15286
  let entryStat;
15038
15287
  try {
15039
15288
  entryStat = await lstat(full);
@@ -15060,7 +15309,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
15060
15309
  omitSet.add("node_modules");
15061
15310
  await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_DOCKER_SERVICES, async (dir) => {
15062
15311
  for (const filename of COMPOSE_FILENAMES) {
15063
- const filepath = join8(dir, filename);
15312
+ const filepath = join9(dir, filename);
15064
15313
  if (await pathExistsAsync(filepath)) {
15065
15314
  await parseComposeFileAsync(filepath, dir, cwd, results);
15066
15315
  break;
@@ -15224,7 +15473,7 @@ async function discoverSupabaseCliProjectsAsync(cwd, omitDirNames = [], signal)
15224
15473
  omitSet.add("node_modules");
15225
15474
  const results = [];
15226
15475
  await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_SUPABASE_PROJECTS, async (dir) => {
15227
- const configPath = join8(dir, "supabase", "config.toml");
15476
+ const configPath = join9(dir, "supabase", "config.toml");
15228
15477
  if (!await pathExistsAsync(configPath))
15229
15478
  return;
15230
15479
  try {
@@ -15232,7 +15481,7 @@ async function discoverSupabaseCliProjectsAsync(cwd, omitDirNames = [], signal)
15232
15481
  const parsed = parseSupabaseConfigToml(content);
15233
15482
  if (!parsed)
15234
15483
  return;
15235
- const relDir = relative3(cwd, dir);
15484
+ const relDir = relative4(cwd, dir);
15236
15485
  const isRoot = relDir === "" || relDir === ".";
15237
15486
  const relDirSlash = relDir.replace(/\\/g, "/");
15238
15487
  const id = isRoot ? `supabase:${parsed.projectId}` : `supabase:${parsed.projectId}@${encodeURIComponent(relDirSlash)}`;
@@ -15333,16 +15582,16 @@ function makeId(prefix) {
15333
15582
 
15334
15583
  // web-src/server/worktree-watcher.ts
15335
15584
  import {
15336
- lstatSync as lstatSync2,
15585
+ lstatSync as lstatSync3,
15337
15586
  readdirSync as nodeReaddirSync,
15338
15587
  watch as nodeWatch
15339
15588
  } from "node:fs";
15340
- import { join as join9, relative as relative4 } from "node:path";
15589
+ import { join as join10, relative as relative5 } from "node:path";
15341
15590
  function normalizeRelativePath(path) {
15342
15591
  return path.replace(/\\/g, "/").replace(/^\/+/, "");
15343
15592
  }
15344
15593
  function isInsideRoot(root, path) {
15345
- const rel = relative4(root, path).replace(/\\/g, "/");
15594
+ const rel = relative5(root, path).replace(/\\/g, "/");
15346
15595
  return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
15347
15596
  }
15348
15597
  function startWorktreeUpdateWatch(options) {
@@ -15350,14 +15599,14 @@ function startWorktreeUpdateWatch(options) {
15350
15599
  const readDirs = options.readdirSync || ((path) => nodeReaddirSync(path, { withFileTypes: true }));
15351
15600
  const isDirectory = options.isDirectory || ((path) => {
15352
15601
  try {
15353
- return lstatSync2(path).isDirectory();
15602
+ return lstatSync3(path).isDirectory();
15354
15603
  } catch {
15355
15604
  return false;
15356
15605
  }
15357
15606
  });
15358
15607
  const directorySignature = options.directorySignature || ((path) => {
15359
15608
  try {
15360
- const stats = lstatSync2(path);
15609
+ const stats = lstatSync3(path);
15361
15610
  if (!stats.isDirectory())
15362
15611
  return null;
15363
15612
  return `${stats.dev}:${stats.ino}`;
@@ -15380,7 +15629,7 @@ function startWorktreeUpdateWatch(options) {
15380
15629
  const pendingChangedPaths = new Set;
15381
15630
  let watchLimitReported = false;
15382
15631
  const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
15383
- const directoryRelativePath = (dir) => normalizeRelativePath(relative4(options.root, dir));
15632
+ const directoryRelativePath = (dir) => normalizeRelativePath(relative5(options.root, dir));
15384
15633
  const ignoredDirectory = (dir) => {
15385
15634
  const rel = directoryRelativePath(dir);
15386
15635
  return Boolean(rel && ignored(rel));
@@ -15446,7 +15695,7 @@ function startWorktreeUpdateWatch(options) {
15446
15695
  for (const entry of entries) {
15447
15696
  if (!entry.isDirectory())
15448
15697
  continue;
15449
- const child = join9(dir, entry.name);
15698
+ const child = join10(dir, entry.name);
15450
15699
  if (ignoredDirectory(child))
15451
15700
  continue;
15452
15701
  children.push(child);
@@ -15530,10 +15779,10 @@ function startWorktreeUpdateWatch(options) {
15530
15779
  scheduleUpdate();
15531
15780
  return;
15532
15781
  }
15533
- const changed = normalizeRelativePath(join9(rel, filename.toString()));
15782
+ const changed = normalizeRelativePath(join10(rel, filename.toString()));
15534
15783
  if (ignored(changed))
15535
15784
  return;
15536
- const fullChangedPath = join9(options.root, changed);
15785
+ const fullChangedPath = join10(options.root, changed);
15537
15786
  if (!isInsideRoot(options.root, fullChangedPath))
15538
15787
  return;
15539
15788
  if (initialScanAsync) {
@@ -15582,9 +15831,9 @@ var init_worktree_watcher = __esm(() => {
15582
15831
  });
15583
15832
 
15584
15833
  // web-src/server/state-store.ts
15585
- import { join as join10 } from "node:path";
15834
+ import { join as join11 } from "node:path";
15586
15835
  function codeViewerPath(root, fileName) {
15587
- return join10(root, CODE_VIEWER_DIR2, fileName);
15836
+ return join11(root, CODE_VIEWER_DIR2, fileName);
15588
15837
  }
15589
15838
  function isRecord(value) {
15590
15839
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -16700,7 +16949,7 @@ var init_connection_pool = __esm(() => {
16700
16949
  // web-src/server/database/connections-store.ts
16701
16950
  import { randomUUID } from "node:crypto";
16702
16951
  import { chmod } from "node:fs/promises";
16703
- import { join as join11 } from "node:path";
16952
+ import { join as join12 } from "node:path";
16704
16953
  function secretKey(cwd, id) {
16705
16954
  return `${cwd}\x00${id}`;
16706
16955
  }
@@ -16721,7 +16970,7 @@ function withRuntimeSecrets(cwd, connection) {
16721
16970
  };
16722
16971
  }
16723
16972
  function connectionsFilePath(root) {
16724
- return join11(root, ".code-viewer", CONNECTIONS_FILE_NAME);
16973
+ return join12(root, ".code-viewer", CONNECTIONS_FILE_NAME);
16725
16974
  }
16726
16975
  function emptyState() {
16727
16976
  return { version: 1, connections: [] };
@@ -18503,9 +18752,9 @@ var init_handle_s3 = __esm(() => {
18503
18752
  });
18504
18753
 
18505
18754
  // web-src/server/database/query-history.ts
18506
- import { join as join12 } from "node:path";
18755
+ import { join as join13 } from "node:path";
18507
18756
  function historyFilePath(root) {
18508
- return join12(root, CODE_VIEWER_DIR3, HISTORY_FILE_NAME);
18757
+ return join13(root, CODE_VIEWER_DIR3, HISTORY_FILE_NAME);
18509
18758
  }
18510
18759
  function emptyState2() {
18511
18760
  return { version: 1, entries: [] };
@@ -18655,9 +18904,9 @@ var init_query_history = __esm(() => {
18655
18904
  // web-src/server/database/snapshot-store.ts
18656
18905
  import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
18657
18906
  import { mkdirSync as mkdirSync3 } from "node:fs";
18658
- import { join as join13 } from "node:path";
18907
+ import { join as join14 } from "node:path";
18659
18908
  async function getStoreDb(cwd) {
18660
- const dbPath = join13(cwd, CODE_VIEWER_DIR4, SNAPSHOT_DB_NAME);
18909
+ const dbPath = join14(cwd, CODE_VIEWER_DIR4, SNAPSHOT_DB_NAME);
18661
18910
  if (storeDb && storeDbPath === dbPath)
18662
18911
  return storeDb;
18663
18912
  if (storeDb) {
@@ -18665,7 +18914,7 @@ async function getStoreDb(cwd) {
18665
18914
  storeDb.close();
18666
18915
  } catch {}
18667
18916
  }
18668
- mkdirSync3(join13(cwd, CODE_VIEWER_DIR4), { recursive: true });
18917
+ mkdirSync3(join14(cwd, CODE_VIEWER_DIR4), { recursive: true });
18669
18918
  const DbClass = await loadSqliteClass();
18670
18919
  storeDb = new DbClass(dbPath);
18671
18920
  storeDbPath = dbPath;
@@ -19271,9 +19520,9 @@ var init_snapshot_runner = __esm(() => {
19271
19520
  });
19272
19521
 
19273
19522
  // web-src/server/database/tabs-store.ts
19274
- import { join as join14 } from "node:path";
19523
+ import { join as join15 } from "node:path";
19275
19524
  function tabsFilePath(root) {
19276
- return join14(root, CODE_VIEWER_DIR5, TABS_FILE_NAME);
19525
+ return join15(root, CODE_VIEWER_DIR5, TABS_FILE_NAME);
19277
19526
  }
19278
19527
  function emptyState3() {
19279
19528
  return { version: 1, tabs: [], activeTabId: null };
@@ -21210,7 +21459,7 @@ var init_handle = __esm(() => {
21210
21459
 
21211
21460
  // web-src/server/doctor.ts
21212
21461
  import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync5 } from "node:fs";
21213
- import { dirname as dirname4, join as join15, relative as relative5 } from "node:path";
21462
+ import { dirname as dirname5, join as join16, relative as relative6 } from "node:path";
21214
21463
  import { fileURLToPath as fileURLToPath2 } from "node:url";
21215
21464
  function statusWorse(a, b) {
21216
21465
  const rank = { ok: 0, warn: 1, error: 2 };
@@ -21299,12 +21548,12 @@ function findCodeViewerPackageJson() {
21299
21548
  try {
21300
21549
  let cursor;
21301
21550
  try {
21302
- cursor = dirname4(fileURLToPath2(import.meta.url));
21551
+ cursor = dirname5(fileURLToPath2(import.meta.url));
21303
21552
  } catch {
21304
- cursor = dirname4(process.argv[1] || ".");
21553
+ cursor = dirname5(process.argv[1] || ".");
21305
21554
  }
21306
21555
  for (let depth = 0;depth < 8; depth += 1) {
21307
- const candidate = join15(cursor, "package.json");
21556
+ const candidate = join16(cursor, "package.json");
21308
21557
  try {
21309
21558
  const raw = readFileSync6(candidate, "utf8");
21310
21559
  const pkg = JSON.parse(raw);
@@ -21312,7 +21561,7 @@ function findCodeViewerPackageJson() {
21312
21561
  return { version: pkg.version, path: candidate };
21313
21562
  }
21314
21563
  } catch {}
21315
- const next = dirname4(cursor);
21564
+ const next = dirname5(cursor);
21316
21565
  if (next === cursor)
21317
21566
  break;
21318
21567
  cursor = next;
@@ -21400,7 +21649,7 @@ async function checkSqlite(cwd) {
21400
21649
  return { id: "sqlite", title: "SQLite driver", rows };
21401
21650
  }
21402
21651
  async function trySnapshotDbOpen(cwd) {
21403
- const dbPath = join15(cwd, SNAPSHOT_DB_REL);
21652
+ const dbPath = join16(cwd, SNAPSHOT_DB_REL);
21404
21653
  try {
21405
21654
  statSync5(dbPath);
21406
21655
  } catch {
@@ -21421,8 +21670,8 @@ async function trySnapshotDbOpen(cwd) {
21421
21670
  }
21422
21671
  }
21423
21672
  function checkSnapshotStore(cwd) {
21424
- const dbPath = join15(cwd, SNAPSHOT_DB_REL);
21425
- const dir = dirname4(dbPath);
21673
+ const dbPath = join16(cwd, SNAPSHOT_DB_REL);
21674
+ const dir = dirname5(dbPath);
21426
21675
  let dirStatus = "ok";
21427
21676
  let dirDetail = dir;
21428
21677
  let dirHint;
@@ -21495,7 +21744,7 @@ async function checkGit(cwd, signal) {
21495
21744
  if (repoCheck && repoCheck.code === 0 && /true/.test(repoCheck.stdout)) {
21496
21745
  const topRes = await runCached(gitCache, TTL.gitRepo, commandForExternal("git"), ["rev-parse", "--show-toplevel"], TIMEOUT.git, signal, cwd);
21497
21746
  const top = topRes?.stdout.trim() || cwd;
21498
- const insideCwd = relative5(top, cwd) || ".";
21747
+ const insideCwd = relative6(top, cwd) || ".";
21499
21748
  rows.push({
21500
21749
  id: "git.repo",
21501
21750
  title: "Working tree",
@@ -22423,44 +22672,6 @@ function normalizeNewDirectoryName(name) {
22423
22672
  return trimmed;
22424
22673
  }
22425
22674
 
22426
- // web-src/server/cache.ts
22427
- import { lstatSync as lstatSync3 } from "node:fs";
22428
- import { join as join16 } from "node:path";
22429
- function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
22430
- return !!cached && now - cached.storedAt <= ttlMs;
22431
- }
22432
- function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MAX_TIMED_CACHE_ENTRIES) {
22433
- cache.set(key, { ...value, storedAt: now });
22434
- while (cache.size > maxEntries) {
22435
- const oldest = cache.keys().next().value;
22436
- if (oldest === undefined)
22437
- break;
22438
- cache.delete(oldest);
22439
- }
22440
- }
22441
- function worktreeFileSignature(path, cwd) {
22442
- try {
22443
- const stats = lstatSync3(join16(cwd, path));
22444
- const inode = "ino" in stats ? stats.ino : 0;
22445
- return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
22446
- } catch {
22447
- return "state:missing";
22448
- }
22449
- }
22450
- function fileDiffCacheKey(options) {
22451
- const worktreeTarget = options.range.from === "worktree" || !options.range.to || options.range.to === "worktree";
22452
- if (options.isUntracked && !worktreeTarget) {
22453
- throw new Error("untracked file diffs require a worktree range");
22454
- }
22455
- const signature = worktreeTarget ? `\x00${worktreeFileSignature(options.path, options.cwd)}` : "";
22456
- if (options.isUntracked) {
22457
- return `u\x00${options.path}${signature}\x00${options.extras.join("\x00")}`;
22458
- }
22459
- return `t\x00${options.path}\x00${options.oldPath || ""}${signature}\x00${[...options.extras, ...options.args].join("\x00")}`;
22460
- }
22461
- var CACHE_TTL_MS = 1500, MAX_TIMED_CACHE_ENTRIES = 200;
22462
- var init_cache = () => {};
22463
-
22464
22675
  // web-src/server/dev-assets.ts
22465
22676
  import { basename as basename2 } from "node:path";
22466
22677
  function startDevAssetReload(options) {
@@ -23123,8 +23334,8 @@ var init_journal2 = __esm(() => {
23123
23334
  });
23124
23335
 
23125
23336
  // web-src/server/search-service.ts
23126
- import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync7, realpathSync as realpathSync5 } from "node:fs";
23127
- import { join as join18, relative as relative6 } from "node:path";
23337
+ import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync7, realpathSync as realpathSync6 } from "node:fs";
23338
+ import { join as join18, relative as relative7 } from "node:path";
23128
23339
  async function rgAvailableAsync(cwd) {
23129
23340
  if (rgAvailableCache !== null)
23130
23341
  return rgAvailableCache;
@@ -23141,7 +23352,8 @@ async function rgAvailableAsync(cwd) {
23141
23352
  return rgAvailableCache;
23142
23353
  }
23143
23354
  function isExcludedScopePath(path, excludeNames) {
23144
- return path.split(/[\\/]+/).some((part) => excludeNames.some((name) => part.toLowerCase() === name.toLowerCase()));
23355
+ const excluded = compileNamePatterns(excludeNames);
23356
+ return path.split(/[\\/]+/).some((part) => excluded.matches(part));
23145
23357
  }
23146
23358
  function isSafePath(path) {
23147
23359
  if (!path || path.startsWith("/") || path.startsWith("\\") || path.includes("\x00"))
@@ -23159,12 +23371,12 @@ function safeWorktreePath(env, path) {
23159
23371
  let realCwd;
23160
23372
  let realFull;
23161
23373
  try {
23162
- realCwd = realpathSync5(env.cwd);
23163
- realFull = realpathSync5(full);
23374
+ realCwd = realpathSync6(env.cwd);
23375
+ realFull = realpathSync6(full);
23164
23376
  } catch {
23165
23377
  return null;
23166
23378
  }
23167
- const rel = relative6(realCwd, realFull);
23379
+ const rel = relative7(realCwd, realFull);
23168
23380
  if (rel === "" || rel.startsWith("..") || rel.startsWith("/") || rel.startsWith("\\"))
23169
23381
  return null;
23170
23382
  if (isGitInternalPath(rel))
@@ -23340,6 +23552,7 @@ var init_search_service = __esm(() => {
23340
23552
  init_command_resolver();
23341
23553
  init_spawn_runner();
23342
23554
  init_git();
23555
+ init_name_pattern();
23343
23556
  init_search();
23344
23557
  });
23345
23558
 
@@ -24907,7 +25120,7 @@ import {
24907
25120
  mkdirSync as mkdirSync4,
24908
25121
  openSync as openSync3,
24909
25122
  readFileSync as readFileSync9,
24910
- realpathSync as realpathSync6,
25123
+ realpathSync as realpathSync7,
24911
25124
  renameSync,
24912
25125
  statSync as statSync6,
24913
25126
  unlinkSync as unlinkSync2,
@@ -24915,7 +25128,7 @@ import {
24915
25128
  writeFileSync as writeFileSync2
24916
25129
  } from "node:fs";
24917
25130
  import { homedir as homedir3 } from "node:os";
24918
- import { basename as basename3, dirname as dirname5, extname as extname2, join as join20, relative as relative7 } from "node:path";
25131
+ import { basename as basename3, dirname as dirname6, extname as extname2, join as join20, relative as relative8 } from "node:path";
24919
25132
  function parseCli() {
24920
25133
  const rest = [];
24921
25134
  for (let i = 2;i < process.argv.length; i++) {
@@ -24969,7 +25182,7 @@ Examples:
24969
25182
  process.exit(1);
24970
25183
  }
24971
25184
  try {
24972
- cwd = realpathSync6(next);
25185
+ cwd = realpathSync7(next);
24973
25186
  cwdWasExplicit = true;
24974
25187
  } catch {
24975
25188
  console.error("--cwd must point to an existing directory");
@@ -25400,7 +25613,7 @@ function worktreePath(path) {
25400
25613
  function safeOpenWorktreePath(path) {
25401
25614
  if (path === "") {
25402
25615
  try {
25403
- const realCwd = realpathSync6(cwd);
25616
+ const realCwd = realpathSync7(cwd);
25404
25617
  if (isGitInternalPath(realCwd))
25405
25618
  return null;
25406
25619
  return realCwd;
@@ -25411,7 +25624,7 @@ function safeOpenWorktreePath(path) {
25411
25624
  return safeWorktreePath2(path);
25412
25625
  }
25413
25626
  function parentRepoPath(path) {
25414
- const parent = dirname5(path);
25627
+ const parent = dirname6(path);
25415
25628
  return parent === "." ? "" : parent;
25416
25629
  }
25417
25630
  function isoDate(ms) {
@@ -25469,6 +25682,21 @@ async function attachTreeEntryMetadata(target, entry) {
25469
25682
  return { ...entry, ...await directoryMetadata(target, entry.path) };
25470
25683
  if (entry.type !== "blob")
25471
25684
  return entry;
25685
+ if (entry.is_symlink && target !== "worktree" && target !== "") {
25686
+ const symlinkMeta = await gitSymlinkTargetMetadataAsync(target, entry.path, cwd);
25687
+ if (symlinkMeta.symlink_target_type === "tree")
25688
+ return {
25689
+ ...entry,
25690
+ ...symlinkMeta,
25691
+ type: "tree",
25692
+ ...await directoryMetadata(target, entry.path)
25693
+ };
25694
+ return {
25695
+ ...entry,
25696
+ ...symlinkMeta,
25697
+ ...await fileMetadataForTarget(target, entry.path)
25698
+ };
25699
+ }
25472
25700
  return { ...entry, ...await fileMetadataForTarget(target, entry.path) };
25473
25701
  }
25474
25702
  async function readReadme(target, dirPath) {
@@ -25491,6 +25719,22 @@ async function readReadme(target, dirPath) {
25491
25719
  }
25492
25720
  return null;
25493
25721
  }
25722
+ function deletedTreeEntriesForPath(statusMap, basePath) {
25723
+ const entries = [];
25724
+ for (const [path, status] of statusMap) {
25725
+ if (status !== "D")
25726
+ continue;
25727
+ if (basePath) {
25728
+ if (!path.startsWith(`${basePath}/`))
25729
+ continue;
25730
+ }
25731
+ const rel = basePath ? path.slice(basePath.length + 1) : path;
25732
+ if (!rel || rel.includes("/"))
25733
+ continue;
25734
+ entries.push({ name: rel, path, type: "blob", status: "D" });
25735
+ }
25736
+ return entries;
25737
+ }
25494
25738
  async function handleTree(url) {
25495
25739
  const target = url.searchParams.get("ref") || url.searchParams.get("target") || "worktree";
25496
25740
  const path = (url.searchParams.get("path") || "").replace(/^\/+|\/+$/g, "");
@@ -25517,12 +25761,21 @@ async function handleTree(url) {
25517
25761
  if (tree.error)
25518
25762
  return text(tree.error, tree.status ?? 500);
25519
25763
  const entries = tree.entries.filter((entry) => !isExcludedScopePath(entry.path, excludeNames));
25764
+ const statusMap = target === "worktree" || target === "" ? await repoStatusMapAsync(cwd) : null;
25765
+ const withStatus = (entry) => {
25766
+ const status = statusMap?.get(entry.path);
25767
+ return status ? { ...entry, status } : entry;
25768
+ };
25769
+ const deletedEntries = !recursive && statusMap ? deletedTreeEntriesForPath(statusMap, path) : [];
25520
25770
  return json2({
25521
25771
  ref: target,
25522
25772
  path,
25523
25773
  project: basename3(cwd),
25524
25774
  branch: await currentBranchMetadata(),
25525
- entries: recursive ? entries : await Promise.all(entries.map((entry) => attachTreeEntryMetadata(target, entry))),
25775
+ entries: recursive ? entries.map(withStatus) : [
25776
+ ...await Promise.all(entries.map((entry) => attachTreeEntryMetadata(target, entry).then(withStatus))),
25777
+ ...deletedEntries
25778
+ ],
25526
25779
  readme: await readReadme(target, path),
25527
25780
  upload_enabled: uploadEnabled && (target === "worktree" || target === "")
25528
25781
  });
@@ -26218,7 +26471,7 @@ async function handleUploadFiles(req) {
26218
26471
  if (total > MAX_UPLOAD_TOTAL_BYTES)
26219
26472
  return text("upload too large", 413);
26220
26473
  const target = join20(realDir, safeName);
26221
- if (relative7(realDir, dirname5(target)) !== "")
26474
+ if (relative8(realDir, dirname6(target)) !== "")
26222
26475
  return text("invalid filename", 400);
26223
26476
  if (existsSync8(target))
26224
26477
  return text("file exists", 409);
@@ -26384,10 +26637,10 @@ async function restoreTrashPath(originalPath, trashPath) {
26384
26637
  return { ok: false, error: "trash item not found" };
26385
26638
  try {
26386
26639
  const trashRoot = join20(homedir3(), ".Trash");
26387
- const trashRelative = relative7(trashRoot, trashPath);
26640
+ const trashRelative = relative8(trashRoot, trashPath);
26388
26641
  if (trashRelative === "" || trashRelative.startsWith("..") || trashRelative.startsWith("/") || trashRelative.startsWith("\\"))
26389
26642
  return { ok: false, error: "invalid trash handle" };
26390
- mkdirSync4(dirname5(original), { recursive: true });
26643
+ mkdirSync4(dirname6(original), { recursive: true });
26391
26644
  renameSync(trashPath, original);
26392
26645
  return { ok: true };
26393
26646
  } catch (error) {