@youtyan/code-viewer 0.8.4 → 0.8.6

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.
@@ -996,11 +996,13 @@ function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MA
996
996
  cache.delete(oldest);
997
997
  }
998
998
  }
999
+ function fileSignatureFromStats(stats) {
1000
+ const inode = stats.ino ?? 0;
1001
+ return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
1002
+ }
999
1003
  function worktreeFileSignature(path, cwd) {
1000
1004
  try {
1001
- const stats = lstatSync(join3(cwd, path));
1002
- const inode = "ino" in stats ? stats.ino : 0;
1003
- return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
1005
+ return fileSignatureFromStats(lstatSync(join3(cwd, path)));
1004
1006
  } catch {
1005
1007
  return "state:missing";
1006
1008
  }
@@ -1407,17 +1409,15 @@ var init_runtime = () => {};
1407
1409
 
1408
1410
  // web-src/server/git.ts
1409
1411
  import {
1410
- closeSync,
1411
1412
  existsSync,
1412
1413
  lstatSync as lstatSync2,
1413
- openSync,
1414
1414
  readdirSync,
1415
1415
  readFileSync,
1416
1416
  readlinkSync,
1417
- readSync,
1418
1417
  realpathSync as realpathSync2,
1419
1418
  statSync as statSync2
1420
1419
  } from "node:fs";
1420
+ import { open, stat } from "node:fs/promises";
1421
1421
  import { dirname as dirname3, join as join4, posix, relative as relative2 } from "node:path";
1422
1422
  function normalizeBlameRef(ref, base) {
1423
1423
  const rawRef = ref || "worktree";
@@ -1485,9 +1485,6 @@ function repoRootResult(cwd) {
1485
1485
  return { kind: "outside" };
1486
1486
  return { kind: "error", error: stderr || "git rev-parse failed" };
1487
1487
  }
1488
- function currentBranch(cwd) {
1489
- return runGitRefLookup(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd);
1490
- }
1491
1488
  function currentBranchAsync(cwd) {
1492
1489
  return runGitRefLookupAsync(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd);
1493
1490
  }
@@ -1555,9 +1552,6 @@ async function repoStatusMapAsync(cwd, now = Date.now()) {
1555
1552
  setTimedCacheEntry(repoStatusMapCache, cwd, { map }, now);
1556
1553
  return map;
1557
1554
  }
1558
- function show(ref, path, cwd) {
1559
- return run(["git", "show", `${ref}:${path}`], cwd);
1560
- }
1561
1555
  function showAsync(ref, path, cwd) {
1562
1556
  return runGitAsync(["git", "show", `${ref}:${path}`], cwd);
1563
1557
  }
@@ -1849,12 +1843,6 @@ function parseRemoteWebUrl(remote) {
1849
1843
  return `https://${httpUrl[1]}/${httpUrl[2]}`;
1850
1844
  return null;
1851
1845
  }
1852
- function remoteWebUrl(cwd) {
1853
- const res = run(["git", "remote", "get-url", "origin"], cwd);
1854
- if (res.code !== 0)
1855
- return null;
1856
- return parseRemoteWebUrl(res.stdout.trim());
1857
- }
1858
1846
  async function remoteWebUrlAsync(cwd) {
1859
1847
  const res = await runGitAsync(["git", "remote", "get-url", "origin"], cwd);
1860
1848
  if (res.code !== 0)
@@ -1923,60 +1911,6 @@ function historyQueryArgs(query) {
1923
1911
  shaTerm: /^[0-9a-f]{4,40}$/i.test(trimmed) ? trimmed : ""
1924
1912
  };
1925
1913
  }
1926
- function commitHistory(cwd, options) {
1927
- const ref = (options.ref || "HEAD").trim();
1928
- if (!ref || ref.startsWith("-") || ref.includes("\x00"))
1929
- return { commits: [], hasMore: false, error: "invalid ref" };
1930
- const verified = run(["git", "rev-parse", "--verify", `${ref}^{commit}`], cwd);
1931
- if (verified.code !== 0)
1932
- return {
1933
- commits: [],
1934
- hasMore: false,
1935
- ...gitFailureResult(verified, "unknown ref")
1936
- };
1937
- const skip = Math.max(0, Math.floor(options.skip) || 0);
1938
- const limit = Math.max(1, Math.min(Math.floor(options.limit) || 1, MAX_HISTORY_LIMIT));
1939
- const { filterArgs, pathspec, shaTerm } = historyQueryArgs(options.query || "");
1940
- const pathFilter = (options.path || "").trim();
1941
- const pathArgs = [];
1942
- if (pathFilter && !pathFilter.includes("\x00") && !pathFilter.startsWith("-")) {
1943
- if (!pathFilter.endsWith("/"))
1944
- pathArgs.push("--follow");
1945
- pathArgs.push("--", pathFilter);
1946
- }
1947
- const res = run([
1948
- "git",
1949
- "log",
1950
- "-z",
1951
- `--skip=${skip}`,
1952
- `--max-count=${limit + 1}`,
1953
- `--format=${HISTORY_FORMAT}`,
1954
- ...filterArgs,
1955
- verified.stdout.trim(),
1956
- ...pathspec,
1957
- ...pathArgs
1958
- ], cwd);
1959
- if (res.code !== 0)
1960
- return {
1961
- commits: [],
1962
- hasMore: false,
1963
- ...gitFailureResult(res, "git log failed")
1964
- };
1965
- let parsed = parseHistoryLog(res.stdout);
1966
- if (shaTerm && skip === 0) {
1967
- const bySha = run(["git", "rev-parse", "--verify", `${shaTerm}^{commit}`], cwd);
1968
- const sha = bySha.code === 0 ? bySha.stdout.trim() : "";
1969
- if (sha) {
1970
- const single = run(["git", "log", "-z", "-1", `--format=${HISTORY_FORMAT}`, sha], cwd);
1971
- if (single.code === 0) {
1972
- const hit = parseHistoryLog(single.stdout);
1973
- parsed = [...hit, ...parsed.filter((c) => c.sha !== sha)];
1974
- }
1975
- }
1976
- }
1977
- const hasMore = parsed.length > limit;
1978
- return { commits: hasMore ? parsed.slice(0, limit) : parsed, hasMore };
1979
- }
1980
1914
  async function commitHistoryAsync(cwd, options) {
1981
1915
  const ref = (options.ref || "HEAD").trim();
1982
1916
  if (!ref || ref.startsWith("-") || ref.includes("\x00"))
@@ -2031,50 +1965,6 @@ async function commitHistoryAsync(cwd, options) {
2031
1965
  const hasMore = parsed.length > limit;
2032
1966
  return { commits: hasMore ? parsed.slice(0, limit) : parsed, hasMore };
2033
1967
  }
2034
- function nameStatusResult(args, cwd) {
2035
- const res = run([
2036
- "git",
2037
- "-c",
2038
- "core.quotepath=false",
2039
- "diff",
2040
- "--no-color",
2041
- "--no-ext-diff",
2042
- "--find-renames",
2043
- "--name-status",
2044
- "-z",
2045
- ...args
2046
- ], cwd);
2047
- if (res.code !== 0) {
2048
- return {
2049
- files: [],
2050
- error: gitFailureMessage(res, "git diff --name-status failed")
2051
- };
2052
- }
2053
- const parts = res.stdout.split("\x00");
2054
- const files = [];
2055
- for (let i = 0;i < parts.length; ) {
2056
- const status = parts[i++];
2057
- if (!status)
2058
- break;
2059
- const kind = status[0];
2060
- if (kind === "R" || kind === "C") {
2061
- const oldPath = parts[i++] || "";
2062
- const path = parts[i++] || "";
2063
- if (path)
2064
- files.push({
2065
- status: kind,
2066
- old_path: oldPath,
2067
- path,
2068
- similarity: Number(status.slice(1)) || undefined
2069
- });
2070
- } else {
2071
- const path = parts[i++] || "";
2072
- if (path)
2073
- files.push({ status: kind, path });
2074
- }
2075
- }
2076
- return { files };
2077
- }
2078
1968
  async function nameStatusResultAsync(args, cwd) {
2079
1969
  const res = await runGitAsync([
2080
1970
  "git",
@@ -2119,49 +2009,6 @@ async function nameStatusResultAsync(args, cwd) {
2119
2009
  }
2120
2010
  return { files };
2121
2011
  }
2122
- function numstatZResult(args, cwd) {
2123
- const res = run([
2124
- "git",
2125
- "-c",
2126
- "core.quotepath=false",
2127
- "diff",
2128
- "--no-color",
2129
- "--no-ext-diff",
2130
- "--find-renames",
2131
- "--numstat",
2132
- "-z",
2133
- ...args
2134
- ], cwd);
2135
- if (res.code !== 0) {
2136
- return {
2137
- files: [],
2138
- error: gitFailureMessage(res, "git diff --numstat failed")
2139
- };
2140
- }
2141
- const parts = res.stdout.split("\x00");
2142
- const files = [];
2143
- for (let i = 0;i < parts.length; ) {
2144
- const rec = parts[i++];
2145
- if (!rec)
2146
- break;
2147
- const match = rec.match(/^(\S+)\t(\S+)\t(.*)$/);
2148
- if (!match)
2149
- break;
2150
- const [, add, del, rest] = match;
2151
- const binary = add === "-" && del === "-";
2152
- const additions = binary ? 0 : Number(add) || 0;
2153
- const deletions = binary ? 0 : Number(del) || 0;
2154
- if (rest === "") {
2155
- const oldPath = parts[i++] || "";
2156
- const path = parts[i++] || "";
2157
- if (path)
2158
- files.push({ old_path: oldPath, path, additions, deletions, binary });
2159
- } else {
2160
- files.push({ path: rest, additions, deletions, binary });
2161
- }
2162
- }
2163
- return { files };
2164
- }
2165
2012
  async function numstatZResultAsync(args, cwd) {
2166
2013
  const res = await runGitAsync([
2167
2014
  "git",
@@ -2218,8 +2065,8 @@ function isGitInternalPath(path) {
2218
2065
  function syntheticUncommittedBlameFromWorktree(cwd, path) {
2219
2066
  const filePath = join4(cwd, path);
2220
2067
  try {
2221
- const stat = statSync2(filePath);
2222
- if (!stat.isFile())
2068
+ const stat2 = statSync2(filePath);
2069
+ if (!stat2.isFile())
2223
2070
  return { lines: [], commits: {}, error: "not a file" };
2224
2071
  const text = readFileSync(filePath, "utf8");
2225
2072
  const normalized = text.replace(/\r\n/g, `
@@ -2255,98 +2102,6 @@ function syntheticUncommittedBlameFromWorktree(cwd, path) {
2255
2102
  return { lines: [], commits: {}, error: "file not readable" };
2256
2103
  }
2257
2104
  }
2258
- function blame(cwd, options) {
2259
- const path = options.path;
2260
- if (!path || path.includes("\x00") || path.startsWith("-")) {
2261
- return { lines: [], commits: {}, error: "invalid path" };
2262
- }
2263
- const normalized = normalizeBlameRef(options.ref, options.base);
2264
- const args = ["git", "blame", "--porcelain"];
2265
- if (normalized.base === "HEAD") {
2266
- if (normalized.ref.startsWith("-") || normalized.ref.includes("\x00"))
2267
- return { lines: [], commits: {}, error: "invalid ref" };
2268
- args.push(normalized.ref);
2269
- }
2270
- args.push("--", path);
2271
- const res = run(args, cwd);
2272
- if (res.code !== 0) {
2273
- if (isCommandNotFoundResult("git", res)) {
2274
- return {
2275
- lines: [],
2276
- commits: {},
2277
- error: commandNotFoundDetail("git"),
2278
- status: 503
2279
- };
2280
- }
2281
- if (normalized.base === "worktree") {
2282
- return syntheticUncommittedBlameFromWorktree(cwd, path);
2283
- }
2284
- return {
2285
- lines: [],
2286
- commits: {},
2287
- error: res.stderr.trim() || "blame failed"
2288
- };
2289
- }
2290
- const lines = [];
2291
- const commits = {};
2292
- const rawLines = res.stdout.split(`
2293
- `);
2294
- let i = 0;
2295
- while (i < rawLines.length) {
2296
- const headerLine = rawLines[i];
2297
- if (!headerLine) {
2298
- i++;
2299
- continue;
2300
- }
2301
- const headerMatch = /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/.exec(headerLine);
2302
- if (!headerMatch) {
2303
- i++;
2304
- continue;
2305
- }
2306
- const sha = headerMatch[1];
2307
- const finalLine = Number(headerMatch[3]);
2308
- i++;
2309
- let commit = commits[sha];
2310
- if (!commit) {
2311
- commit = {
2312
- sha,
2313
- author: "",
2314
- authorMail: "",
2315
- authorTime: 0,
2316
- summary: "",
2317
- isUncommitted: sha === BLAME_ZERO_SHA
2318
- };
2319
- commits[sha] = commit;
2320
- }
2321
- while (i < rawLines.length && !rawLines[i].startsWith("\t")) {
2322
- const metaLine = rawLines[i++];
2323
- if (!metaLine)
2324
- continue;
2325
- const sp = metaLine.indexOf(" ");
2326
- const key = sp >= 0 ? metaLine.slice(0, sp) : metaLine;
2327
- const val = sp >= 0 ? metaLine.slice(sp + 1) : "";
2328
- if (key === "author" && !commit.author)
2329
- commit.author = val;
2330
- else if (key === "author-mail" && !commit.authorMail)
2331
- commit.authorMail = val.replace(/^</, "").replace(/>$/, "");
2332
- else if (key === "author-time" && !commit.authorTime)
2333
- commit.authorTime = Number(val) || 0;
2334
- else if (key === "summary" && !commit.summary)
2335
- commit.summary = val;
2336
- }
2337
- if (i < rawLines.length && rawLines[i].startsWith("\t"))
2338
- i++;
2339
- if (Number.isFinite(finalLine) && finalLine > 0) {
2340
- lines.push({
2341
- lineNo: finalLine,
2342
- sha,
2343
- isUncommitted: sha === BLAME_ZERO_SHA
2344
- });
2345
- }
2346
- }
2347
- lines.sort((a, b) => a.lineNo - b.lineNo);
2348
- return { lines, commits };
2349
- }
2350
2105
  async function blameAsync(cwd, options) {
2351
2106
  const path = options.path;
2352
2107
  if (!path || path.includes("\x00") || path.startsWith("-")) {
@@ -2439,16 +2194,6 @@ async function blameAsync(cwd, options) {
2439
2194
  lines.sort((a, b) => a.lineNo - b.lineNo);
2440
2195
  return { lines, commits };
2441
2196
  }
2442
- function untracked(cwd, path = "") {
2443
- const args = ["git", "ls-files", "--others", "--exclude-standard"];
2444
- if (path)
2445
- args.push("--", `${path}/`);
2446
- const res = run(args, cwd);
2447
- if (res.code !== 0)
2448
- return [];
2449
- return res.stdout.split(`
2450
- `).filter(Boolean).filter((entry) => !isToolInternalPath(entry));
2451
- }
2452
2197
  async function untrackedAsync(cwd, path = "") {
2453
2198
  const args = ["git", "ls-files", "--others", "--exclude-standard"];
2454
2199
  if (path)
@@ -2474,18 +2219,6 @@ function omittedWorktreeDirectoryReason(name, omitDirNames) {
2474
2219
  return "internal";
2475
2220
  return omitDirNames.matches(name) ? "heavy" : undefined;
2476
2221
  }
2477
- function worktreeSubmodulePaths(cwd) {
2478
- if (!existsSync(join4(cwd, ".gitmodules")))
2479
- return new Set;
2480
- const res = run(["git", "config", "--file", ".gitmodules", "--get-regexp", "\\.path$"], cwd);
2481
- if (res.code !== 0)
2482
- return new Set;
2483
- return new Set(res.stdout.split(`
2484
- `).map((line) => {
2485
- const split = line.indexOf(" ");
2486
- return split >= 0 ? normalizeTreePath(line.slice(split + 1)) : "";
2487
- }).filter(Boolean));
2488
- }
2489
2222
  async function worktreeSubmodulePathsAsync(cwd) {
2490
2223
  if (!existsSync(join4(cwd, ".gitmodules")))
2491
2224
  return new Set;
@@ -2522,8 +2255,8 @@ function resolveWorktreeSymlinkTarget(cwd, full) {
2522
2255
  let symlink_target_type = "missing";
2523
2256
  if (realpathWithinRepo(cwd, full, false) !== null) {
2524
2257
  try {
2525
- const stat = statSync2(full);
2526
- symlink_target_type = stat.isDirectory() ? "tree" : stat.isFile() ? "blob" : "missing";
2258
+ const stat2 = statSync2(full);
2259
+ symlink_target_type = stat2.isDirectory() ? "tree" : stat2.isFile() ? "blob" : "missing";
2527
2260
  } catch {
2528
2261
  symlink_target_type = "missing";
2529
2262
  }
@@ -2565,83 +2298,6 @@ function worktreeEntryFromDirent(cwd, base, dir, name, isDirectory, isSymlink, o
2565
2298
  children_omitted_reason: omittedReason
2566
2299
  } : baseEntry;
2567
2300
  }
2568
- function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
2569
- const base = normalizeTreePath(path);
2570
- const root = join4(cwd, base);
2571
- if (realpathWithinRepo(cwd, root, true) === null)
2572
- return [];
2573
- const omitDirNameSet = compileNamePatterns(omitDirNames);
2574
- const excludeNameSet = compileNamePatterns(excludeNames);
2575
- const submodulePaths = worktreeSubmodulePaths(cwd);
2576
- let directEntries;
2577
- try {
2578
- const dirents = readdirSync(root, { withFileTypes: true });
2579
- directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(cwd, base, root, entry.name, entry.isDirectory(), entry.isSymbolicLink(), omitDirNameSet, excludeNameSet, submodulePaths)).filter((entry) => entry.path));
2580
- } catch {
2581
- return [];
2582
- }
2583
- if (!recursive)
2584
- return directEntries;
2585
- const fileEntries = [];
2586
- let truncated = false;
2587
- const pushRecursiveEntry = (entry) => {
2588
- if (fileEntries.length >= WORKTREE_RECURSIVE_ENTRY_LIMIT) {
2589
- if (!truncated) {
2590
- fileEntries.push({
2591
- name: "more...",
2592
- path: "__code_viewer_truncated__",
2593
- type: "tree",
2594
- children_omitted: true,
2595
- children_omitted_reason: "truncated"
2596
- });
2597
- truncated = true;
2598
- }
2599
- return false;
2600
- }
2601
- fileEntries.push(entry);
2602
- return true;
2603
- };
2604
- const walk = (dir, prefix, depth) => {
2605
- if (truncated)
2606
- return;
2607
- if (depth >= WORKTREE_RECURSIVE_DEPTH_LIMIT)
2608
- return;
2609
- let entries;
2610
- try {
2611
- entries = readdirSync(dir, { withFileTypes: true });
2612
- } catch {
2613
- return;
2614
- }
2615
- for (const entry of entries) {
2616
- if (excludeNameSet.matches(entry.name))
2617
- continue;
2618
- const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
2619
- const full = join4(dir, entry.name);
2620
- if (entry.isDirectory()) {
2621
- const omittedReason = omittedWorktreeDirectoryReason(entry.name, omitDirNameSet);
2622
- if (omittedReason) {
2623
- if (!pushRecursiveEntry({
2624
- name: entry.name,
2625
- path: entryPath,
2626
- type: "tree",
2627
- children_omitted: true,
2628
- children_omitted_reason: omittedReason
2629
- }))
2630
- return;
2631
- continue;
2632
- }
2633
- if (hasDotGitEntry(full))
2634
- continue;
2635
- walk(full, entryPath, depth + 1);
2636
- } else if (entry.isFile() || entry.isSymbolicLink()) {
2637
- if (!pushRecursiveEntry(recursiveWorktreeFileEntry(cwd, full, entry.name, entryPath, entry.isSymbolicLink())))
2638
- return;
2639
- }
2640
- }
2641
- };
2642
- walk(root, base, 0);
2643
- return combineDirectAndRecursiveFiles(directEntries, fileEntries.sort((a, b) => a.path.localeCompare(b.path)));
2644
- }
2645
2301
  async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
2646
2302
  const base = normalizeTreePath(path);
2647
2303
  const root = join4(cwd, base);
@@ -2743,28 +2399,9 @@ function parseLsTreeRecord(rec, allowedTypes) {
2743
2399
  return {
2744
2400
  name: entryPath.split("/").pop() || entryPath,
2745
2401
  path: entryPath,
2746
- type,
2747
- ...mode === LS_TREE_SYMLINK_MODE ? { is_symlink: true } : {}
2748
- };
2749
- }
2750
- function gitTreeEntries(ref, path, cwd, recursive) {
2751
- const base = normalizeTreePath(path);
2752
- const args = ["git", "-c", "core.quotepath=false", "ls-tree"];
2753
- if (recursive)
2754
- args.push("-r");
2755
- args.push("-z", "--full-tree", ref, "--");
2756
- if (base)
2757
- args.push(`${base}/`);
2758
- const res = run(args, cwd);
2759
- if (res.code !== 0)
2760
- return { code: res.code, entries: [], stderr: res.stderr };
2761
- const allowedTypes = recursive ? "blob|commit" : "tree|blob|commit";
2762
- let entries = res.stdout.split("\x00").filter(Boolean).map((rec) => parseLsTreeRecord(rec, allowedTypes)).filter((entry) => !!entry);
2763
- if (recursive)
2764
- entries.sort((a, b) => a.path.localeCompare(b.path));
2765
- else
2766
- entries = sortTreeEntries(entries);
2767
- return { code: 0, entries, stderr: "" };
2402
+ type,
2403
+ ...mode === LS_TREE_SYMLINK_MODE ? { is_symlink: true } : {}
2404
+ };
2768
2405
  }
2769
2406
  async function gitTreeEntriesAsync(ref, path, cwd, recursive) {
2770
2407
  const base = normalizeTreePath(path);
@@ -2792,27 +2429,6 @@ function combineDirectAndRecursiveFiles(directEntries, fileEntries) {
2792
2429
  ...fileEntries.filter((entry) => !seen.has(entry.path))
2793
2430
  ];
2794
2431
  }
2795
- function listTree(ref, path, cwd, options = {}) {
2796
- const base = normalizeTreePath(path);
2797
- if (ref === "worktree") {
2798
- return {
2799
- code: 0,
2800
- entries: worktreeFilesystemEntries(cwd, base, !!options.recursive, options.omitDirNames, options.excludeNames),
2801
- stderr: ""
2802
- };
2803
- }
2804
- const direct = gitTreeEntries(ref, base, cwd, false);
2805
- if (direct.code !== 0 || !options.recursive)
2806
- return direct;
2807
- const recursive = gitTreeEntries(ref, base, cwd, true);
2808
- if (recursive.code !== 0)
2809
- return recursive;
2810
- return {
2811
- code: 0,
2812
- entries: combineDirectAndRecursiveFiles(direct.entries, recursive.entries),
2813
- stderr: ""
2814
- };
2815
- }
2816
2432
  async function listTreeAsync(ref, path, cwd, options = {}) {
2817
2433
  const base = normalizeTreePath(path);
2818
2434
  if (ref === "worktree") {
@@ -2840,115 +2456,82 @@ async function listTreeResultAsync(ref, path, cwd, options = {}) {
2840
2456
  return { entries: result.entries };
2841
2457
  return { entries: [], ...gitFailureResult(result, "git ls-tree failed") };
2842
2458
  }
2843
- function untrackedMeta(cwd) {
2844
- return untracked(cwd).flatMap((path) => {
2845
- const full = join4(cwd, path);
2846
- let fileExists = false;
2847
- try {
2848
- fileExists = existsSync(full) && statSync2(full).isFile();
2849
- } catch {
2850
- fileExists = false;
2851
- }
2852
- let scan;
2853
- if (fileExists) {
2854
- try {
2855
- scan = scanFileBinaryAndNewlines(full);
2856
- } catch {
2857
- return [];
2858
- }
2859
- } else {
2860
- return [];
2861
- }
2862
- return [
2863
- {
2864
- path,
2865
- status: "A",
2866
- additions: scan.binary ? 0 : scan.newlines,
2867
- deletions: 0,
2868
- binary: scan.binary,
2869
- untracked: true
2870
- }
2871
- ];
2872
- });
2873
- }
2874
- async function untrackedMetaAsync(cwd) {
2875
- const paths = await untrackedAsync(cwd);
2876
- return paths.flatMap((path) => {
2877
- const full = join4(cwd, path);
2878
- let fileExists = false;
2879
- try {
2880
- fileExists = existsSync(full) && statSync2(full).isFile();
2881
- } catch {
2882
- fileExists = false;
2883
- }
2884
- let scan;
2885
- if (fileExists) {
2886
- try {
2887
- scan = scanFileBinaryAndNewlines(full);
2888
- } catch {
2889
- return [];
2890
- }
2891
- } else {
2892
- return [];
2893
- }
2894
- return [
2895
- {
2896
- path,
2897
- status: "A",
2898
- additions: scan.binary ? 0 : scan.newlines,
2899
- deletions: 0,
2900
- binary: scan.binary,
2901
- untracked: true
2902
- }
2903
- ];
2904
- });
2459
+ function untrackedFileMeta(path, scan) {
2460
+ return {
2461
+ path,
2462
+ status: "A",
2463
+ additions: scan.binary ? 0 : scan.newlines,
2464
+ deletions: 0,
2465
+ binary: scan.binary,
2466
+ untracked: true
2467
+ };
2905
2468
  }
2906
- function scanFileBinaryAndNewlines(full) {
2907
- const fd = openSync(full, "r");
2469
+ async function scanFileBinaryAndNewlinesAsync(full) {
2470
+ const handle = await open(full, "r");
2908
2471
  const buffer = Buffer.allocUnsafe(64 * 1024);
2909
2472
  let newlines = 0;
2910
2473
  let inspected = 0;
2911
2474
  try {
2912
2475
  while (true) {
2913
- const read = readSync(fd, buffer, 0, buffer.length, null);
2914
- if (read <= 0)
2476
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
2477
+ if (bytesRead <= 0)
2915
2478
  break;
2916
- const binaryProbeBytes = Math.min(read, Math.max(0, 8192 - inspected));
2479
+ const binaryProbeBytes = Math.min(bytesRead, Math.max(0, 8192 - inspected));
2917
2480
  for (let i = 0;i < binaryProbeBytes; i++) {
2918
2481
  if (buffer[i] === 0)
2919
2482
  return { binary: true, newlines: 0 };
2920
2483
  }
2921
- inspected += read;
2922
- for (let i = 0;i < read; i++) {
2484
+ inspected += bytesRead;
2485
+ for (let i = 0;i < bytesRead; i++) {
2923
2486
  if (buffer[i] === 10)
2924
2487
  newlines++;
2925
2488
  }
2926
2489
  }
2927
2490
  } finally {
2928
- closeSync(fd);
2491
+ await handle.close();
2929
2492
  }
2930
2493
  return { binary: false, newlines };
2931
2494
  }
2932
- function fileMetaResult(args, cwd, includeUntracked = false) {
2933
- const ns = nameStatusResult(args, cwd);
2934
- if (ns.error)
2935
- return { files: [], error: ns.error };
2936
- const nm = numstatZResult(args, cwd);
2937
- if (nm.error)
2938
- return { files: [], error: nm.error };
2939
- const byPath = new Map(nm.files.map((file) => [file.path, file]));
2940
- const files = ns.files.map((file) => {
2941
- const stats = byPath.get(file.path);
2942
- return {
2943
- ...file,
2944
- additions: stats?.additions || 0,
2945
- deletions: stats?.deletions || 0,
2946
- binary: stats?.binary || false
2947
- };
2948
- });
2949
- return {
2950
- files: includeUntracked ? files.concat(untrackedMeta(cwd)) : files
2495
+ async function untrackedMetaAsync(cwd) {
2496
+ const paths = await untrackedAsync(cwd);
2497
+ const previous = untrackedScanCache.get(cwd);
2498
+ const next = new Map;
2499
+ const results = new Array(paths.length).fill(null);
2500
+ let cursor = 0;
2501
+ const worker = async () => {
2502
+ while (cursor < paths.length) {
2503
+ const index = cursor++;
2504
+ const path = paths[index];
2505
+ const full = join4(cwd, path);
2506
+ let stats;
2507
+ try {
2508
+ stats = await stat(full);
2509
+ } catch {
2510
+ continue;
2511
+ }
2512
+ if (!stats.isFile())
2513
+ continue;
2514
+ const signature = fileSignatureFromStats(stats);
2515
+ const cached = previous?.get(path);
2516
+ let scan;
2517
+ if (cached && cached.signature === signature) {
2518
+ scan = cached.scan;
2519
+ } else {
2520
+ try {
2521
+ scan = await scanFileBinaryAndNewlinesAsync(full);
2522
+ } catch {
2523
+ continue;
2524
+ }
2525
+ }
2526
+ next.set(path, { signature, scan });
2527
+ results[index] = untrackedFileMeta(path, scan);
2528
+ }
2951
2529
  };
2530
+ await Promise.all(Array.from({
2531
+ length: Math.min(UNTRACKED_SCAN_CONCURRENCY, Math.max(1, paths.length))
2532
+ }, () => worker()));
2533
+ untrackedScanCache.set(cwd, next);
2534
+ return results.filter((meta) => meta !== null);
2952
2535
  }
2953
2536
  async function fileMetaResultAsync(args, cwd, includeUntracked = false) {
2954
2537
  const ns = await nameStatusResultAsync(args, cwd);
@@ -2971,25 +2554,6 @@ async function fileMetaResultAsync(args, cwd, includeUntracked = false) {
2971
2554
  files: includeUntracked ? files.concat(await untrackedMetaAsync(cwd)) : files
2972
2555
  };
2973
2556
  }
2974
- function fileDiffText(args, path, cwd) {
2975
- const paths = Array.isArray(path) ? path : [path];
2976
- const res = run([
2977
- "git",
2978
- "-c",
2979
- "core.quotepath=false",
2980
- "diff",
2981
- "--no-color",
2982
- "--no-ext-diff",
2983
- "--find-renames",
2984
- ...args,
2985
- "--",
2986
- ...paths
2987
- ], cwd);
2988
- if (isCommandNotFoundResult("git", res)) {
2989
- return { ...res, stderr: commandNotFoundDetail("git"), status: 503 };
2990
- }
2991
- return res;
2992
- }
2993
2557
  async function fileDiffTextAsync(args, path, cwd) {
2994
2558
  const paths = Array.isArray(path) ? path : [path];
2995
2559
  const res = await runGitAsync([
@@ -3009,24 +2573,6 @@ async function fileDiffTextAsync(args, path, cwd) {
3009
2573
  }
3010
2574
  return res;
3011
2575
  }
3012
- function untrackedFileDiff(extras, path, cwd) {
3013
- const res = run([
3014
- "git",
3015
- "-c",
3016
- "core.quotepath=false",
3017
- "diff",
3018
- "--no-color",
3019
- "--no-ext-diff",
3020
- "--no-index",
3021
- ...extras,
3022
- "/dev/null",
3023
- path
3024
- ], cwd);
3025
- if (isCommandNotFoundResult("git", res)) {
3026
- return { ...res, stderr: commandNotFoundDetail("git"), status: 503 };
3027
- }
3028
- return res;
3029
- }
3030
2576
  async function untrackedFileDiffAsync(extras, path, cwd) {
3031
2577
  const res = await runGitAsync([
3032
2578
  "git",
@@ -3122,7 +2668,7 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
3122
2668
  lineTruncated
3123
2669
  };
3124
2670
  }
3125
- 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";
2671
+ 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", untrackedScanCache, UNTRACKED_SCAN_CONCURRENCY = 8;
3126
2672
  var init_git = __esm(() => {
3127
2673
  init_cache();
3128
2674
  init_command_resolver();
@@ -3174,6 +2720,7 @@ var init_git = __esm(() => {
3174
2720
  "obj"
3175
2721
  ];
3176
2722
  repoStatusMapCache = new Map;
2723
+ untrackedScanCache = new Map;
3177
2724
  });
3178
2725
 
3179
2726
  // web-src/server/server-registry.ts
@@ -4417,16 +3964,11 @@ __export(exports_file_cli, {
4417
3964
  safeWorktreePathFromRoot: () => safeWorktreePathFromRoot,
4418
3965
  runFileCli: () => runFileCli,
4419
3966
  readShowTextAsync: () => readShowTextAsync,
4420
- readShowText: () => readShowText,
4421
3967
  parseFileArgs: () => parseFileArgs,
4422
3968
  buildFileShowReportAsync: () => buildFileShowReportAsync,
4423
- buildFileShowReport: () => buildFileShowReport,
4424
3969
  buildFileHistoryReportAsync: () => buildFileHistoryReportAsync,
4425
- buildFileHistoryReport: () => buildFileHistoryReport,
4426
3970
  buildFileDiffReportAsync: () => buildFileDiffReportAsync,
4427
- buildFileDiffReport: () => buildFileDiffReport,
4428
3971
  buildFileBlameReportAsync: () => buildFileBlameReportAsync,
4429
- buildFileBlameReport: () => buildFileBlameReport,
4430
3972
  FILE_HISTORY_HARD_CAP: () => FILE_HISTORY_HARD_CAP,
4431
3973
  FILE_HELP: () => FILE_HELP,
4432
3974
  FILE_DIFF_LINE_HARD_CAP: () => FILE_DIFF_LINE_HARD_CAP,
@@ -4643,14 +4185,14 @@ function parseFileArgs(argv) {
4643
4185
  if (oldPathError)
4644
4186
  return { ok: false, error: oldPathError };
4645
4187
  }
4646
- const untracked2 = flags.has("--untracked");
4647
- if (untracked2 && rawFrom !== undefined) {
4188
+ const untracked = flags.has("--untracked");
4189
+ if (untracked && rawFrom !== undefined) {
4648
4190
  return {
4649
4191
  ok: false,
4650
4192
  error: "--untracked cannot be combined with --from"
4651
4193
  };
4652
4194
  }
4653
- if (untracked2 && to !== "worktree") {
4195
+ if (untracked && to !== "worktree") {
4654
4196
  return {
4655
4197
  ok: false,
4656
4198
  error: "--untracked requires --to worktree (or --to omitted)"
@@ -4694,7 +4236,7 @@ function parseFileArgs(argv) {
4694
4236
  ...oldPathRaw !== undefined ? { oldPath: oldPathRaw } : {},
4695
4237
  from,
4696
4238
  to,
4697
- untracked: untracked2,
4239
+ untracked,
4698
4240
  ignoreWs,
4699
4241
  ignoreBlank,
4700
4242
  mode: full ? "full" : "preview",
@@ -4726,19 +4268,6 @@ function formatBlameText(result) {
4726
4268
  function formatHistoryText(commits) {
4727
4269
  return commits.map((c) => `${c.sha.slice(0, 8)} ${c.when} ${c.author} ${c.subject}`);
4728
4270
  }
4729
- function buildFileBlameReport(root, command) {
4730
- const result = blame(root, {
4731
- path: command.path,
4732
- ref: command.ref,
4733
- base: command.base
4734
- });
4735
- return {
4736
- path: command.path,
4737
- ref: command.ref,
4738
- base: command.base,
4739
- result
4740
- };
4741
- }
4742
4271
  async function buildFileBlameReportAsync(root, command) {
4743
4272
  const result = await blameAsync(root, {
4744
4273
  path: command.path,
@@ -4752,23 +4281,6 @@ async function buildFileBlameReportAsync(root, command) {
4752
4281
  result
4753
4282
  };
4754
4283
  }
4755
- function buildFileHistoryReport(root, command) {
4756
- const result = commitHistory(root, {
4757
- ref: command.ref,
4758
- skip: command.skip,
4759
- limit: command.limit,
4760
- query: command.query,
4761
- path: command.path
4762
- });
4763
- return {
4764
- path: command.path,
4765
- ref: command.ref,
4766
- limit: command.limit,
4767
- skip: command.skip,
4768
- ...command.query !== undefined ? { query: command.query } : {},
4769
- result
4770
- };
4771
- }
4772
4284
  async function buildFileHistoryReportAsync(root, command) {
4773
4285
  const result = await commitHistoryAsync(root, {
4774
4286
  ref: command.ref,
@@ -4786,8 +4298,8 @@ async function buildFileHistoryReportAsync(root, command) {
4786
4298
  result
4787
4299
  };
4788
4300
  }
4789
- function runBlame(root, command) {
4790
- const report = buildFileBlameReport(root, command);
4301
+ async function runBlame(root, command) {
4302
+ const report = await buildFileBlameReportAsync(root, command);
4791
4303
  if (command.json) {
4792
4304
  console.log(JSON.stringify(report, null, 2));
4793
4305
  } else {
@@ -4799,8 +4311,8 @@ function runBlame(root, command) {
4799
4311
  process.exit(1);
4800
4312
  }
4801
4313
  }
4802
- function runHistory(root, command) {
4803
- const report = buildFileHistoryReport(root, command);
4314
+ async function runHistory(root, command) {
4315
+ const report = await buildFileHistoryReportAsync(root, command);
4804
4316
  if (command.json) {
4805
4317
  console.log(JSON.stringify(report, null, 2));
4806
4318
  } else if (report.result.commits.length === 0) {
@@ -4852,28 +4364,6 @@ function safeWorktreePathFromRoot(root, path) {
4852
4364
  return null;
4853
4365
  }
4854
4366
  }
4855
- function readShowText(root, command) {
4856
- if (command.ref !== "worktree" && command.ref !== "") {
4857
- return show(command.ref, command.path, root);
4858
- }
4859
- const full = safeWorktreePathFromRoot(root, command.path);
4860
- if (!full) {
4861
- return {
4862
- code: 1,
4863
- stdout: "",
4864
- stderr: "file not found or forbidden"
4865
- };
4866
- }
4867
- try {
4868
- const stat = statSync3(full);
4869
- if (!stat.isFile()) {
4870
- return { code: 1, stdout: "", stderr: "not a file" };
4871
- }
4872
- return { code: 0, stdout: readFileSync4(full, "utf8"), stderr: "" };
4873
- } catch {
4874
- return { code: 1, stdout: "", stderr: "file not readable" };
4875
- }
4876
- }
4877
4367
  async function readShowTextAsync(root, command) {
4878
4368
  if (command.ref !== "worktree" && command.ref !== "") {
4879
4369
  return showAsync(command.ref, command.path, root);
@@ -4887,8 +4377,8 @@ async function readShowTextAsync(root, command) {
4887
4377
  };
4888
4378
  }
4889
4379
  try {
4890
- const stat = statSync3(full);
4891
- if (!stat.isFile()) {
4380
+ const stat2 = statSync3(full);
4381
+ if (!stat2.isFile()) {
4892
4382
  return { code: 1, stdout: "", stderr: "not a file" };
4893
4383
  }
4894
4384
  return { code: 0, stdout: readFileSync4(full, "utf8"), stderr: "" };
@@ -4896,33 +4386,6 @@ async function readShowTextAsync(root, command) {
4896
4386
  return { code: 1, stdout: "", stderr: "file not readable" };
4897
4387
  }
4898
4388
  }
4899
- function buildFileShowReport(root, command) {
4900
- const res = readShowText(root, command);
4901
- if (res.code !== 0) {
4902
- const detail = res.stderr.trim() || `git show exited with code ${res.code}`;
4903
- return {
4904
- path: command.path,
4905
- ref: command.ref,
4906
- ...command.start !== undefined ? { start: command.start } : {},
4907
- ...command.end !== undefined ? { end: command.end } : {},
4908
- totalLines: 0,
4909
- complete: false,
4910
- text: "",
4911
- error: detail
4912
- };
4913
- }
4914
- const sliced = sliceLines(res.stdout, command.start, command.end);
4915
- return {
4916
- path: command.path,
4917
- ref: command.ref,
4918
- ...command.start !== undefined ? { start: command.start } : {},
4919
- ...command.end !== undefined ? { end: command.end } : {},
4920
- totalLines: sliced.total,
4921
- complete: sliced.complete,
4922
- text: sliced.lines.join(`
4923
- `)
4924
- };
4925
- }
4926
4389
  async function buildFileShowReportAsync(root, command) {
4927
4390
  const res = await readShowTextAsync(root, command);
4928
4391
  if (res.code !== 0) {
@@ -4950,8 +4413,8 @@ async function buildFileShowReportAsync(root, command) {
4950
4413
  `)
4951
4414
  };
4952
4415
  }
4953
- function runShow(root, command) {
4954
- const report = buildFileShowReport(root, command);
4416
+ async function runShow(root, command) {
4417
+ const report = await buildFileShowReportAsync(root, command);
4955
4418
  if (report.error !== undefined) {
4956
4419
  if (command.json) {
4957
4420
  console.log(JSON.stringify(report, null, 2));
@@ -4974,64 +4437,6 @@ function buildFileDiffRangeArgs(from, to) {
4974
4437
  refs.push(to);
4975
4438
  return refs;
4976
4439
  }
4977
- function buildFileDiffReport(root, command) {
4978
- const base = {
4979
- path: command.path,
4980
- ...command.oldPath !== undefined ? { old_path: command.oldPath } : {},
4981
- from: command.untracked ? "/dev/null" : command.from,
4982
- to: command.to,
4983
- untracked: command.untracked,
4984
- ignore_ws: command.ignoreWs,
4985
- ignore_blank: command.ignoreBlank,
4986
- mode: command.mode,
4987
- max_hunks: command.mode === "preview" ? command.maxHunks : null,
4988
- max_lines: command.mode === "preview" ? command.maxLines : null,
4989
- diff: "",
4990
- hunk_count: 0,
4991
- rendered_hunk_count: 0,
4992
- line_count: 0,
4993
- truncated: false,
4994
- binary: false
4995
- };
4996
- if (!command.untracked && isSameWorktreeRange({ from: command.from, to: command.to })) {
4997
- return base;
4998
- }
4999
- const extras = [];
5000
- if (command.ignoreWs)
5001
- extras.push("-w");
5002
- if (command.ignoreBlank)
5003
- extras.push("--ignore-blank-lines");
5004
- let diffText = "";
5005
- let errText = "";
5006
- if (command.untracked) {
5007
- const res = untrackedFileDiff(extras, command.path, root);
5008
- diffText = res.stdout || "";
5009
- if (res.code !== 0)
5010
- errText = res.stderr.trim();
5011
- } else {
5012
- const args = [
5013
- ...extras,
5014
- ...buildFileDiffRangeArgs(command.from, command.to)
5015
- ];
5016
- const paths = command.oldPath !== undefined ? [command.oldPath, command.path] : command.path;
5017
- const res = fileDiffText(args, paths, root);
5018
- diffText = res.stdout || "";
5019
- if (res.code !== 0)
5020
- errText = res.stderr.trim();
5021
- }
5022
- const truncated = command.mode === "preview" ? truncateToNHunks(diffText, command.maxHunks, command.maxLines) : truncateToNHunks(diffText, 1e9);
5023
- const previewTruncated = command.mode === "preview" && (truncated.totalHunks > truncated.renderedHunks || truncated.lineTruncated);
5024
- return {
5025
- ...base,
5026
- diff: truncated.text,
5027
- hunk_count: truncated.totalHunks,
5028
- rendered_hunk_count: truncated.renderedHunks,
5029
- line_count: truncated.lineCount,
5030
- truncated: previewTruncated,
5031
- binary: diffText.includes("Binary files"),
5032
- ...errText ? { error: errText } : {}
5033
- };
5034
- }
5035
4440
  async function buildFileDiffReportAsync(root, command) {
5036
4441
  const base = {
5037
4442
  path: command.path,
@@ -5090,8 +4495,8 @@ async function buildFileDiffReportAsync(root, command) {
5090
4495
  ...errText ? { error: errText } : {}
5091
4496
  };
5092
4497
  }
5093
- function runDiff(root, command) {
5094
- const report = buildFileDiffReport(root, command);
4498
+ async function runDiff(root, command) {
4499
+ const report = await buildFileDiffReportAsync(root, command);
5095
4500
  if (command.json) {
5096
4501
  console.log(JSON.stringify(report, null, 2));
5097
4502
  } else if (report.diff.length > 0) {
@@ -10049,18 +9454,18 @@ function formatRecentText(commits) {
10049
9454
  }
10050
9455
  return lines;
10051
9456
  }
10052
- function buildStatusReport(opts) {
9457
+ async function buildStatusReport(opts) {
10053
9458
  const { root, ref, limit } = opts;
10054
- const stagedResult = fileMetaResult(["--cached"], root, false);
10055
- const changedResult = fileMetaResult(["HEAD"], root, true);
10056
- const worktreeFallbackResult = isMissingDiffBaseError(changedResult.error) ? fileMetaResult([], root, true) : null;
9459
+ const stagedResult = await fileMetaResultAsync(["--cached"], root, false);
9460
+ const changedResult = await fileMetaResultAsync(["HEAD"], root, true);
9461
+ const worktreeFallbackResult = isMissingDiffBaseError(changedResult.error) ? await fileMetaResultAsync([], root, true) : null;
10057
9462
  const stagedFiles = stagedResult.files;
10058
9463
  const changedFiles = changedResult.error ? worktreeFallbackResult ? mergeMissingByPath(worktreeFallbackResult.files, stagedFiles) : [] : mergeMissingByPath(changedResult.files, stagedFiles);
10059
9464
  const changed = buildGroup(changedFiles, worktreeFallbackResult ? worktreeFallbackResult.error || stagedResult.error : changedResult.error || stagedResult.error);
10060
9465
  const staged = buildGroup(stagedFiles, stagedResult.error);
10061
- const history = commitHistory(root, { ref, skip: 0, limit });
10062
- const branch = currentBranch(root);
10063
- const remote = remoteWebUrl(root);
9466
+ const history = await commitHistoryAsync(root, { ref, skip: 0, limit });
9467
+ const branch = await currentBranchAsync(root);
9468
+ const remote = await remoteWebUrlAsync(root);
10064
9469
  const registry = readServerRegistry(root);
10065
9470
  const serverUrl = registry?.url ?? null;
10066
9471
  const nextCommands = buildNextCommands(changed, staged, serverUrl);
@@ -10098,7 +9503,7 @@ function formatStatusReportText(report) {
10098
9503
  return lines.join(`
10099
9504
  `);
10100
9505
  }
10101
- function runStatusCli(argv) {
9506
+ async function runStatusCli(argv) {
10102
9507
  const parsed = parseStatusArgs(argv);
10103
9508
  if (parsed.ok === false) {
10104
9509
  console.error(parsed.error);
@@ -10124,7 +9529,7 @@ function runStatusCli(argv) {
10124
9529
  process.exit(1);
10125
9530
  }
10126
9531
  const root = resolveRepoRoot(cwd);
10127
- const report = buildStatusReport({
9532
+ const report = await buildStatusReport({
10128
9533
  root,
10129
9534
  ref: command.ref,
10130
9535
  limit: command.limit
@@ -14805,26 +14210,26 @@ function hasControlCharacter(value) {
14805
14210
 
14806
14211
  // web-src/server/database/discovery.ts
14807
14212
  import {
14808
- closeSync as closeSync2,
14213
+ closeSync,
14809
14214
  existsSync as existsSync6,
14810
- openSync as openSync2,
14811
- readSync as readSync2,
14215
+ openSync,
14216
+ readSync,
14812
14217
  realpathSync as realpathSync5,
14813
14218
  statSync as statSync4
14814
14219
  } from "node:fs";
14815
- import { lstat, open, readdir, readFile as readFile2, stat } from "node:fs/promises";
14220
+ import { lstat, open as open2, readdir, readFile as readFile2, stat as stat2 } from "node:fs/promises";
14816
14221
  import { basename, join as join9, relative as relative4 } from "node:path";
14817
14222
  function isSqliteFile(fullPath) {
14818
14223
  try {
14819
- const stat2 = statSync4(fullPath);
14820
- if (!stat2.isFile() || stat2.size < 16)
14224
+ const stat3 = statSync4(fullPath);
14225
+ if (!stat3.isFile() || stat3.size < 16)
14821
14226
  return false;
14822
14227
  const buf = Buffer.alloc(16);
14823
- const fd = openSync2(fullPath, "r");
14228
+ const fd = openSync(fullPath, "r");
14824
14229
  try {
14825
- readSync2(fd, buf, 0, 16, 0);
14230
+ readSync(fd, buf, 0, 16, 0);
14826
14231
  } finally {
14827
- closeSync2(fd);
14232
+ closeSync(fd);
14828
14233
  }
14829
14234
  return buf.toString("utf8", 0, 16) === SQLITE_MAGIC;
14830
14235
  } catch {
@@ -14833,10 +14238,10 @@ function isSqliteFile(fullPath) {
14833
14238
  }
14834
14239
  async function isSqliteFileAsync(fullPath) {
14835
14240
  try {
14836
- const fileStat = await stat(fullPath);
14241
+ const fileStat = await stat2(fullPath);
14837
14242
  if (!fileStat.isFile() || fileStat.size < 16)
14838
14243
  return false;
14839
- const file = await open(fullPath, "r");
14244
+ const file = await open2(fullPath, "r");
14840
14245
  try {
14841
14246
  const buf = Buffer.alloc(16);
14842
14247
  await file.read(buf, 0, 16, 0);
@@ -15300,7 +14705,7 @@ function cloneDockerDiscoveryResult(result) {
15300
14705
  }
15301
14706
  async function pathExistsAsync(path) {
15302
14707
  try {
15303
- await stat(path);
14708
+ await stat2(path);
15304
14709
  return true;
15305
14710
  } catch {
15306
14711
  return false;
@@ -17334,7 +16739,7 @@ function createDockerAdapterCache(maxEntries = DEFAULT_MAX_DOCKER_ADAPTER_CACHE,
17334
16739
  }
17335
16740
  }
17336
16741
  return {
17337
- async getOrOpenAsync(key, open2) {
16742
+ async getOrOpenAsync(key, open3) {
17338
16743
  prune();
17339
16744
  const cached = cache.get(key);
17340
16745
  if (cached) {
@@ -17348,7 +16753,7 @@ function createDockerAdapterCache(maxEntries = DEFAULT_MAX_DOCKER_ADAPTER_CACHE,
17348
16753
  closed: false,
17349
16754
  promise: undefined
17350
16755
  };
17351
- pendingEntry.promise = Promise.resolve().then(open2).then((adapter) => {
16756
+ pendingEntry.promise = Promise.resolve().then(open3).then((adapter) => {
17352
16757
  if (pendingEntry.closed) {
17353
16758
  adapter.close();
17354
16759
  } else {
@@ -21646,27 +21051,27 @@ async function checkSqlite(cwd) {
21646
21051
  const status = await describeSqliteDriver();
21647
21052
  const rows = [sqliteStatusToRow(status)];
21648
21053
  if (status.kind === "ok") {
21649
- const open2 = await trySnapshotDbOpen(cwd);
21650
- if (open2.kind === "skipped") {
21054
+ const open3 = await trySnapshotDbOpen(cwd);
21055
+ if (open3.kind === "skipped") {
21651
21056
  rows.push({
21652
21057
  id: "sqlite.snapshot-open",
21653
21058
  title: "Snapshot DB open smoke test",
21654
21059
  status: "ok",
21655
21060
  detail: "Skipped (snapshot DB will be created on first use)"
21656
21061
  });
21657
- } else if (open2.kind === "ok") {
21062
+ } else if (open3.kind === "ok") {
21658
21063
  rows.push({
21659
21064
  id: "sqlite.snapshot-open",
21660
21065
  title: "Snapshot DB open smoke test",
21661
21066
  status: "ok",
21662
- detail: `Opened ${open2.path}`
21067
+ detail: `Opened ${open3.path}`
21663
21068
  });
21664
21069
  } else {
21665
21070
  rows.push({
21666
21071
  id: "sqlite.snapshot-open",
21667
21072
  title: "Snapshot DB open failed",
21668
21073
  status: "error",
21669
- detail: open2.message,
21074
+ detail: open3.message,
21670
21075
  hint: "The snapshot DB exists but could not be opened. Inspect file permissions, possible corruption, or another process holding the file."
21671
21076
  });
21672
21077
  }
@@ -21715,8 +21120,8 @@ function checkSnapshotStore(cwd) {
21715
21120
  }
21716
21121
  let dbDetail = dbPath;
21717
21122
  try {
21718
- const stat2 = statSync5(dbPath);
21719
- dbDetail = `${dbPath} (${stat2.size.toLocaleString()} bytes)`;
21123
+ const stat3 = statSync5(dbPath);
21124
+ dbDetail = `${dbPath} (${stat3.size.toLocaleString()} bytes)`;
21720
21125
  } catch {
21721
21126
  dbDetail = `${dbPath} (not created yet — created on first snapshot)`;
21722
21127
  }
@@ -23330,7 +22735,8 @@ var init_journal2 = __esm(() => {
23330
22735
  });
23331
22736
 
23332
22737
  // web-src/server/search-service.ts
23333
- import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync7, realpathSync as realpathSync6 } from "node:fs";
22738
+ import { existsSync as existsSync7, realpathSync as realpathSync6 } from "node:fs";
22739
+ import { lstat as lstat2, readFile as readFile3 } from "node:fs/promises";
23334
22740
  import { join as join18, relative as relative7 } from "node:path";
23335
22741
  async function rgAvailableAsync(cwd) {
23336
22742
  if (rgAvailableCache !== null)
@@ -23382,12 +22788,12 @@ function safeWorktreePath(env, path) {
23382
22788
  function filterCallerPaths(env, paths) {
23383
22789
  return paths.filter((path) => isSafePath(path) && !isGitInternalPath(path) && !isSkippableSearchPath(path, env.omitDirNames, env.excludeNames));
23384
22790
  }
23385
- function grepWorktreeFallback(env, query, max, paths) {
23386
- const candidates = paths.length ? paths : listTree("worktree", "", env.cwd, {
22791
+ async function grepWorktreeFallback(env, query, max, paths) {
22792
+ const candidates = paths.length ? paths : (await listTreeAsync("worktree", "", env.cwd, {
23387
22793
  recursive: true,
23388
22794
  omitDirNames: env.omitDirNames,
23389
22795
  excludeNames: env.excludeNames
23390
- }).entries.map((entry) => entry.path);
22796
+ })).entries.map((entry) => entry.path);
23391
22797
  const matches = [];
23392
22798
  for (const path of candidates) {
23393
22799
  if (matches.length >= max)
@@ -23397,17 +22803,17 @@ function grepWorktreeFallback(env, query, max, paths) {
23397
22803
  const full = safeWorktreePath(env, path);
23398
22804
  if (!full)
23399
22805
  continue;
23400
- let stat2;
22806
+ let stat3;
23401
22807
  try {
23402
- stat2 = lstatSync4(full);
22808
+ stat3 = await lstat2(full);
23403
22809
  } catch {
23404
22810
  continue;
23405
22811
  }
23406
- if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.size > GREP_MAX_FILE_BYTES)
22812
+ if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.size > GREP_MAX_FILE_BYTES)
23407
22813
  continue;
23408
22814
  let data;
23409
22815
  try {
23410
- data = readFileSync7(full);
22816
+ data = await readFile3(full);
23411
22817
  } catch {
23412
22818
  continue;
23413
22819
  }
@@ -23447,7 +22853,7 @@ async function grepWorktreeAsync(env, req) {
23447
22853
  matches: []
23448
22854
  };
23449
22855
  }
23450
- const matches = grepWorktreeFallback(env, req.query, req.max, paths);
22856
+ const matches = await grepWorktreeFallback(env, req.query, req.max, paths);
23451
22857
  return {
23452
22858
  ref: "worktree",
23453
22859
  engine: "fallback",
@@ -23553,7 +22959,7 @@ var init_search_service = __esm(() => {
23553
22959
  });
23554
22960
 
23555
22961
  // web-src/server/mcp.ts
23556
- import { readFileSync as readFileSync8 } from "node:fs";
22962
+ import { readFileSync as readFileSync7 } from "node:fs";
23557
22963
  import { join as join19 } from "node:path";
23558
22964
  function defaultMcpTools(options = {}) {
23559
22965
  return [
@@ -24051,7 +23457,7 @@ function defaultMcpTools(options = {}) {
24051
23457
  }
24052
23458
  ];
24053
23459
  }
24054
- function runStatusTool(input, defaultCwd) {
23460
+ async function runStatusTool(input, defaultCwd) {
24055
23461
  const params = isPlainObject(input) ? input : {};
24056
23462
  const cwdRaw = params.cwd;
24057
23463
  const refRaw = params.ref;
@@ -24092,7 +23498,7 @@ function runStatusTool(input, defaultCwd) {
24092
23498
  return { text: resolved.error, isError: true };
24093
23499
  }
24094
23500
  try {
24095
- const report = buildStatusReport({ root: resolved.root, ref, limit });
23501
+ const report = await buildStatusReport({ root: resolved.root, ref, limit });
24096
23502
  return {
24097
23503
  text: JSON.stringify(report, null, 2),
24098
23504
  isError: !!(report.changed.error || report.staged.error)
@@ -24365,14 +23771,14 @@ async function runFileDiffTool(input, defaultCwd) {
24365
23771
  if (untrackedRaw !== undefined && typeof untrackedRaw !== "boolean") {
24366
23772
  return { text: "untracked must be a boolean", isError: true };
24367
23773
  }
24368
- const untracked2 = untrackedRaw === true;
24369
- if (untracked2 && params.from !== undefined) {
23774
+ const untracked = untrackedRaw === true;
23775
+ if (untracked && params.from !== undefined) {
24370
23776
  return {
24371
23777
  text: "untracked cannot be combined with from",
24372
23778
  isError: true
24373
23779
  };
24374
23780
  }
24375
- if (untracked2 && toParsed.ref !== "worktree") {
23781
+ if (untracked && toParsed.ref !== "worktree") {
24376
23782
  return {
24377
23783
  text: "untracked requires to='worktree' (or omitted)",
24378
23784
  isError: true
@@ -24420,7 +23826,7 @@ async function runFileDiffTool(input, defaultCwd) {
24420
23826
  ...oldPath !== undefined ? { oldPath } : {},
24421
23827
  from: fromParsed.ref,
24422
23828
  to: toParsed.ref,
24423
- untracked: untracked2,
23829
+ untracked,
24424
23830
  ignoreWs,
24425
23831
  ignoreBlank,
24426
23832
  mode,
@@ -25028,7 +24434,7 @@ var init_mcp = __esm(() => {
25028
24434
  init_search_cli();
25029
24435
  init_search_service();
25030
24436
  init_status_cli();
25031
- PACKAGE_VERSION = JSON.parse(readFileSync8(join19(ROOT, "package.json"), "utf8")).version;
24437
+ PACKAGE_VERSION = JSON.parse(readFileSync7(join19(ROOT, "package.json"), "utf8")).version;
25032
24438
  MCP_SERVER_INFO = {
25033
24439
  name: "code-viewer",
25034
24440
  title: "code-viewer",
@@ -25109,13 +24515,13 @@ var init_state_route = __esm(() => {
25109
24515
  // web-src/server/preview.ts
25110
24516
  var exports_preview = {};
25111
24517
  import {
25112
- closeSync as closeSync3,
24518
+ closeSync as closeSync2,
25113
24519
  constants as constants3,
25114
24520
  existsSync as existsSync8,
25115
- lstatSync as lstatSync5,
24521
+ lstatSync as lstatSync4,
25116
24522
  mkdirSync as mkdirSync4,
25117
- openSync as openSync3,
25118
- readFileSync as readFileSync9,
24523
+ openSync as openSync2,
24524
+ readFileSync as readFileSync8,
25119
24525
  realpathSync as realpathSync7,
25120
24526
  renameSync,
25121
24527
  statSync as statSync6,
@@ -25356,7 +24762,7 @@ function staticFile(pathname) {
25356
24762
  const full = join20(WEB_ROOT, spec[0]);
25357
24763
  if (!existsSync8(full))
25358
24764
  return text("not found", 404);
25359
- return new Response(readFileSync9(full), {
24765
+ return new Response(readFileSync8(full), {
25360
24766
  headers: { "Content-Type": spec[1], "Cache-Control": "no-store" }
25361
24767
  });
25362
24768
  }
@@ -25631,11 +25037,11 @@ function worktreeFileMetadata(path, knownSize) {
25631
25037
  if (!full)
25632
25038
  return {};
25633
25039
  try {
25634
- const stat2 = statSync6(full);
25040
+ const stat3 = statSync6(full);
25635
25041
  return {
25636
- size: knownSize ?? stat2.size,
25637
- created_at: isoDate(stat2.birthtimeMs),
25638
- updated_at: isoDate(stat2.mtimeMs)
25042
+ size: knownSize ?? stat3.size,
25043
+ created_at: isoDate(stat3.birthtimeMs),
25044
+ updated_at: isoDate(stat3.mtimeMs)
25639
25045
  };
25640
25046
  } catch {
25641
25047
  return {};
@@ -25656,10 +25062,10 @@ async function directoryMetadata(target, path) {
25656
25062
  if (!full)
25657
25063
  return {};
25658
25064
  try {
25659
- const stat2 = statSync6(full);
25065
+ const stat3 = statSync6(full);
25660
25066
  return {
25661
- created_at: isoDate(stat2.birthtimeMs),
25662
- updated_at: isoDate(stat2.mtimeMs)
25067
+ created_at: isoDate(stat3.birthtimeMs),
25068
+ updated_at: isoDate(stat3.mtimeMs)
25663
25069
  };
25664
25070
  } catch {
25665
25071
  return {};
@@ -25704,7 +25110,7 @@ async function readReadme(target, dirPath) {
25704
25110
  if (!full)
25705
25111
  continue;
25706
25112
  try {
25707
- return { path, text: readFileSync9(full, "utf8") };
25113
+ return { path, text: readFileSync8(full, "utf8") };
25708
25114
  } catch {
25709
25115
  continue;
25710
25116
  }
@@ -26085,8 +25491,8 @@ async function handleFileDiff(url) {
26085
25491
  }
26086
25492
  function worktreeLineIndexSignature(full) {
26087
25493
  try {
26088
- const stat2 = statSync6(full);
26089
- return `size:${stat2.size}|mtime:${stat2.mtimeMs}|ctime:${stat2.ctimeMs}|ino:${stat2.ino || 0}`;
25494
+ const stat3 = statSync6(full);
25495
+ return `size:${stat3.size}|mtime:${stat3.mtimeMs}|ctime:${stat3.ctimeMs}|ino:${stat3.ino || 0}`;
26090
25496
  } catch {
26091
25497
  return null;
26092
25498
  }
@@ -26101,10 +25507,10 @@ async function getWorktreeLineIndex(full) {
26101
25507
  lineIndexCache.set(full, cached);
26102
25508
  return cached.index;
26103
25509
  }
26104
- const stat2 = statSync6(full);
26105
- if (stat2.size > LINE_INDEX_MAX_FILE_BYTES)
25510
+ const stat3 = statSync6(full);
25511
+ if (stat3.size > LINE_INDEX_MAX_FILE_BYTES)
26106
25512
  return null;
26107
- const index = await buildLineOffsetIndexFromStream(fileReadableStream(full), stat2.size);
25513
+ const index = await buildLineOffsetIndexFromStream(fileReadableStream(full), stat3.size);
26108
25514
  lineIndexCache.delete(full);
26109
25515
  lineIndexCache.set(full, { signature, index });
26110
25516
  while (lineIndexCache.size > 32) {
@@ -26476,11 +25882,11 @@ async function handleUploadFiles(req) {
26476
25882
  const written = [];
26477
25883
  try {
26478
25884
  for (const upload of uploads) {
26479
- const fd = openSync3(upload.target, uploadOpenFlags(), 420);
25885
+ const fd = openSync2(upload.target, uploadOpenFlags(), 420);
26480
25886
  try {
26481
25887
  writeFileSync2(fd, new Uint8Array(await upload.file.arrayBuffer()));
26482
25888
  } finally {
26483
- closeSync3(fd);
25889
+ closeSync2(fd);
26484
25890
  }
26485
25891
  written.push(upload.target);
26486
25892
  }
@@ -26494,7 +25900,7 @@ async function handleUploadFiles(req) {
26494
25900
  return text("file exists", 409);
26495
25901
  return text("upload failed", 500);
26496
25902
  }
26497
- triggerUpdate();
25903
+ triggerUpdate(uploads.map((upload) => dir ? `${dir}/${upload.name}` : upload.name));
26498
25904
  return json2({
26499
25905
  ok: true,
26500
25906
  files: uploads.map((upload) => upload.name),
@@ -26600,7 +26006,7 @@ function moveMacPathIntoTrash(path) {
26600
26006
  }
26601
26007
  }
26602
26008
  async function movePathToTrash(path) {
26603
- lstatSync5(path);
26009
+ lstatSync4(path);
26604
26010
  if (process.platform === "darwin") {
26605
26011
  return moveMacPathIntoTrash(path);
26606
26012
  }
@@ -26727,6 +26133,12 @@ async function handleTrashPath(req) {
26727
26133
  const originalFullPath = safeWorktreePath2(path);
26728
26134
  if (!originalFullPath)
26729
26135
  return text("not found", 404);
26136
+ let changedPaths;
26137
+ try {
26138
+ const stats = statSync6(originalFullPath);
26139
+ if (!stats.isDirectory())
26140
+ changedPaths = [path];
26141
+ } catch {}
26730
26142
  const moved = await movePathToTrash(worktreePath(path));
26731
26143
  if (!moved.ok)
26732
26144
  return text(moved.error || "trash failed", 500);
@@ -26739,7 +26151,7 @@ async function handleTrashPath(req) {
26739
26151
  trashPath: moved.trashPath
26740
26152
  }
26741
26153
  };
26742
- triggerUpdate();
26154
+ triggerUpdate(changedPaths);
26743
26155
  return json2({ ok: true, generation, undo });
26744
26156
  }
26745
26157
  async function handleCreateDirectory(req) {
@@ -26792,7 +26204,7 @@ async function handleCreateDirectory(req) {
26792
26204
  return text("already exists", 409);
26793
26205
  return text("create failed", 500);
26794
26206
  }
26795
- triggerUpdate();
26207
+ triggerUpdate([targetPath]);
26796
26208
  return json2({ ok: true, path: targetPath, generation });
26797
26209
  }
26798
26210
  async function handleRestoreTrash(req) {
@@ -26824,7 +26236,13 @@ async function handleRestoreTrash(req) {
26824
26236
  const restored = await restoreTrashPath(originalPath, trashPath || undefined);
26825
26237
  if (!restored.ok)
26826
26238
  return text(restored.error || "undo failed", 409);
26827
- triggerUpdate();
26239
+ let changedPaths;
26240
+ try {
26241
+ const stats = statSync6(worktreePath(originalPath));
26242
+ if (!stats.isDirectory())
26243
+ changedPaths = [originalPath];
26244
+ } catch {}
26245
+ triggerUpdate(changedPaths);
26828
26246
  return json2({ ok: true, generation });
26829
26247
  }
26830
26248
  function annotationSse(kind, sessionId, entryId) {
@@ -27381,7 +26799,7 @@ var init_preview = __esm(async () => {
27381
26799
  init_state_store();
27382
26800
  init_worktree_watcher();
27383
26801
  WEB_ROOT = join20(ROOT, "web");
27384
- VERSION = JSON.parse(readFileSync9(join20(ROOT, "package.json"), "utf8")).version;
26802
+ VERSION = JSON.parse(readFileSync8(join20(ROOT, "package.json"), "utf8")).version;
27385
26803
  DEFAULT_ARGS = ["HEAD"];
27386
26804
  WATCHED_ASSET_FILES = ["index.html", "style.css", "app.js"];
27387
26805
  LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;
@@ -27667,7 +27085,7 @@ if (process.argv[2] === "agent-help") {
27667
27085
  await runFileCli2(process.argv.slice(3));
27668
27086
  } else if (process.argv[2] === "status") {
27669
27087
  const { runStatusCli: runStatusCli2 } = await Promise.resolve().then(() => (init_status_cli(), exports_status_cli));
27670
- runStatusCli2(process.argv.slice(3));
27088
+ await runStatusCli2(process.argv.slice(3));
27671
27089
  } else if (process.argv[2] === "skill") {
27672
27090
  const { runSkillCli: runSkillCli2 } = await Promise.resolve().then(() => (init_skill_cli(), exports_skill_cli));
27673
27091
  runSkillCli2(process.argv.slice(3));