getgloss 0.8.4 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -100,8 +100,11 @@ compares only against the requested ref and does not switch to a branch diff.
100
100
  Use `gloss open --review <reviewId> --json` after applying feedback to capture
101
101
  the next diff as another turn in the same browser review.
102
102
  The background server exits automatically after a short idle window with no
103
- pending reviews. `gloss doctor` reports unmanaged daemon processes, and
104
- `gloss stop --all` cleans them up.
103
+ live review clients. Pending review artifacts stay on disk and can be resumed,
104
+ but they do not keep the daemon alive by themselves. `gloss doctor` reports
105
+ unmanaged daemon processes, and normal startup/status commands also clean up
106
+ clearly stale daemons. `gloss stop --all` cleans up every Gloss daemon for the
107
+ current user.
105
108
 
106
109
  You do not need to unlock `~/.gloss/server.json` after finishing a review.
107
110
  That file is only the background daemon pointer, not a review lock. If a
package/dist/cli/index.js CHANGED
@@ -3,8 +3,8 @@
3
3
  // src/cli/index.ts
4
4
  import { randomUUID as randomUUID3 } from "crypto";
5
5
  import { constants } from "fs";
6
- import { access, rm as rm4, writeFile as writeFile3 } from "fs/promises";
7
- import path6 from "path";
6
+ import { access, rm as rm5, writeFile as writeFile4 } from "fs/promises";
7
+ import path7 from "path";
8
8
  import { Command } from "commander";
9
9
  import openBrowser from "open";
10
10
 
@@ -30,7 +30,7 @@ import path from "path";
30
30
  // package.json
31
31
  var package_default = {
32
32
  name: "getgloss",
33
- version: "0.8.4",
33
+ version: "0.8.5",
34
34
  description: "Local browser-based diff review for coding-agent loops.",
35
35
  type: "module",
36
36
  packageManager: "pnpm@10.33.2",
@@ -126,6 +126,9 @@ function globalStateDir() {
126
126
  function globalServerFile() {
127
127
  return path.join(globalStateDir(), "server.json");
128
128
  }
129
+ function globalServerLockDir() {
130
+ return path.join(globalStateDir(), "server.lock");
131
+ }
129
132
  function globalLogDir() {
130
133
  return path.join(globalStateDir(), "logs");
131
134
  }
@@ -205,10 +208,10 @@ function parseJsonValue(value, guard, label) {
205
208
  return value;
206
209
  }
207
210
  function isServerInfo(value) {
208
- return isRecord(value) && isNumber(value.pid) && isNumber(value.port) && isString(value.version) && isString(value.startedAt) && isString(value.stateDir);
211
+ return isRecord(value) && isNumber(value.pid) && isNumber(value.port) && isString(value.version) && isString(value.startedAt) && isString(value.stateDir) && isOptionalString(value.cwd) && isOptionalString(value.daemonPath);
209
212
  }
210
213
  function isHealthResponse(value) {
211
- return isRecord(value) && isBoolean(value.ok) && isString(value.version) && isNumber(value.activeReviews);
214
+ return isRecord(value) && isBoolean(value.ok) && isString(value.version) && isNumber(value.activeReviews) && isOptionalNumber(value.connections) && isOptionalString(value.stateDir) && isOptionalString(value.cwd) && isOptionalString(value.daemonPath);
212
215
  }
213
216
  function isClearReviewsResult(value) {
214
217
  return isRecord(value) && isString(value.reviewsDir) && isString(value.cutoff) && isNumber(value.olderThanDays) && isBoolean(value.dryRun) && isArrayOf(value.candidates, isClearReviewEntry) && isArrayOf(value.deleted, isClearReviewEntry) && isArrayOf(value.skipped, isClearReviewSkipped) && isRecord(value.counts) && isNumber(value.counts.candidates) && isNumber(value.counts.deleted) && isNumber(value.counts.skipped);
@@ -1107,12 +1110,15 @@ async function assertGitAvailable() {
1107
1110
  // src/cli/lifecycle.ts
1108
1111
  import { execFile, spawn } from "child_process";
1109
1112
  import { closeSync, existsSync, openSync } from "fs";
1113
+ import { mkdir as mkdir2, readFile as readFile3, rm as rm4, stat, writeFile as writeFile3 } from "fs/promises";
1110
1114
  import { userInfo } from "os";
1115
+ import path5 from "path";
1111
1116
  import { fileURLToPath } from "url";
1112
1117
  import { promisify } from "util";
1113
1118
  import getPort from "get-port";
1114
1119
 
1115
1120
  // src/cli/server-client.ts
1121
+ var timedWatchAttemptMs = 1e3;
1116
1122
  var ServerClient = class {
1117
1123
  constructor(baseUrl) {
1118
1124
  this.baseUrl = baseUrl;
@@ -1191,11 +1197,15 @@ var ServerClient = class {
1191
1197
  throw new Error(`watch timed out after ${timeoutSeconds} seconds`);
1192
1198
  }
1193
1199
  const controller = new AbortController();
1194
- const timeout = remainingMs ? setTimeout(() => controller.abort(), remainingMs) : null;
1200
+ const timeoutMs = remainingMs === null ? null : Math.min(remainingMs, timedWatchAttemptMs);
1201
+ const timeout = timeoutMs ? setTimeout(() => controller.abort(), timeoutMs) : null;
1195
1202
  try {
1196
1203
  return await this.readReviewEvents(reviewId, controller.signal);
1197
1204
  } catch (error) {
1198
1205
  if (isAbortError(error)) {
1206
+ if (deadline && Date.now() < deadline) {
1207
+ throw new Error("watch stream ended before completion");
1208
+ }
1199
1209
  throw new Error(`watch timed out after ${timeoutSeconds} seconds`);
1200
1210
  }
1201
1211
  if (!isPrematureWatchEnd(error)) {
@@ -1240,20 +1250,20 @@ var ServerClient = class {
1240
1250
  }
1241
1251
  }
1242
1252
  }
1243
- async get(path7, guard, label) {
1244
- const response = await fetch(`${this.baseUrl}${path7}`);
1253
+ async get(path8, guard, label) {
1254
+ const response = await fetch(`${this.baseUrl}${path8}`);
1245
1255
  return parseResponse(response, guard, label);
1246
1256
  }
1247
- async post(path7, body, guard, label) {
1248
- const response = await fetch(`${this.baseUrl}${path7}`, {
1257
+ async post(path8, body, guard, label) {
1258
+ const response = await fetch(`${this.baseUrl}${path8}`, {
1249
1259
  method: "POST",
1250
1260
  headers: { "content-type": "application/json" },
1251
1261
  body: JSON.stringify(body)
1252
1262
  });
1253
1263
  return parseResponse(response, guard, label);
1254
1264
  }
1255
- async delete(path7, guard, label) {
1256
- const response = await fetch(`${this.baseUrl}${path7}`, { method: "DELETE" });
1265
+ async delete(path8, guard, label) {
1266
+ const response = await fetch(`${this.baseUrl}${path8}`, { method: "DELETE" });
1257
1267
  return parseResponse(response, guard, label);
1258
1268
  }
1259
1269
  };
@@ -1278,6 +1288,8 @@ async function sleep(milliseconds) {
1278
1288
  var execFileAsync = promisify(execFile);
1279
1289
  var gracefulShutdownTimeoutMs = 2e3;
1280
1290
  var forceShutdownTimeoutMs = 1e3;
1291
+ var serverLockTimeoutMs = 8e3;
1292
+ var staleServerLockMs = 3e4;
1281
1293
  function serverUrl(info) {
1282
1294
  return `http://localhost:${info.port}`;
1283
1295
  }
@@ -1293,17 +1305,26 @@ async function isServerResponsive(info) {
1293
1305
  }
1294
1306
  }
1295
1307
  async function ensureServer(options = {}) {
1296
- const existing = await readServerInfo();
1297
- if (existing && await isServerResponsive(existing)) {
1298
- return existing;
1299
- }
1300
- return startServer(options);
1308
+ return withServerLock(async () => {
1309
+ const existing = await readServerInfo();
1310
+ await reapStaleDaemons(existing);
1311
+ if (existing && await isServerResponsive(existing)) {
1312
+ return existing;
1313
+ }
1314
+ return startServerUnlocked(existing, options);
1315
+ });
1301
1316
  }
1302
1317
  async function startServer(options = {}) {
1303
- const existing = await readServerInfo();
1304
- if (existing && await isServerResponsive(existing)) {
1305
- return existing;
1306
- }
1318
+ return withServerLock(async () => {
1319
+ const existing = await readServerInfo();
1320
+ await reapStaleDaemons(existing);
1321
+ if (existing && await isServerResponsive(existing)) {
1322
+ return existing;
1323
+ }
1324
+ return startServerUnlocked(existing, options);
1325
+ });
1326
+ }
1327
+ async function startServerUnlocked(existing, options = {}) {
1307
1328
  if (existing) {
1308
1329
  await retireServer(existing);
1309
1330
  }
@@ -1342,7 +1363,9 @@ async function launchServer(port) {
1342
1363
  port,
1343
1364
  version: packageVersion,
1344
1365
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1345
- stateDir: globalStateDir()
1366
+ stateDir: globalStateDir(),
1367
+ cwd: process.cwd(),
1368
+ daemonPath
1346
1369
  };
1347
1370
  try {
1348
1371
  await writeServerInfo(info);
@@ -1361,10 +1384,86 @@ async function launchServer(port) {
1361
1384
  await removeServerInfoForPid(info.pid);
1362
1385
  throw new Error(`Server did not become responsive. See ${globalServerLogFile()}`);
1363
1386
  }
1387
+ async function withServerLock(fn) {
1388
+ const release = await acquireServerLock();
1389
+ try {
1390
+ return await fn();
1391
+ } finally {
1392
+ await release();
1393
+ }
1394
+ }
1395
+ async function acquireServerLock() {
1396
+ await ensureDir(globalStateDir());
1397
+ const lockDir = globalServerLockDir();
1398
+ const ownerFile = serverLockOwnerFile();
1399
+ const deadline = Date.now() + serverLockTimeoutMs;
1400
+ while (true) {
1401
+ try {
1402
+ await mkdir2(lockDir);
1403
+ try {
1404
+ await writeFile3(
1405
+ ownerFile,
1406
+ `${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
1407
+ `
1408
+ );
1409
+ } catch (error) {
1410
+ await rm4(lockDir, { recursive: true, force: true }).catch(() => void 0);
1411
+ throw error;
1412
+ }
1413
+ return () => rm4(lockDir, { recursive: true, force: true });
1414
+ } catch (error) {
1415
+ if (!isAlreadyExistsError(error)) {
1416
+ throw error;
1417
+ }
1418
+ if (await removeStaleServerLock(lockDir, ownerFile)) {
1419
+ continue;
1420
+ }
1421
+ if (Date.now() >= deadline) {
1422
+ throw new Error(`Timed out waiting for Gloss server lock at ${lockDir}`);
1423
+ }
1424
+ await sleep2(50);
1425
+ }
1426
+ }
1427
+ }
1428
+ async function removeStaleServerLock(lockDir, ownerFile) {
1429
+ const owner = await readServerLockOwner(ownerFile);
1430
+ if (owner?.pid && !isPidAlive(owner.pid)) {
1431
+ await rm4(lockDir, { recursive: true, force: true });
1432
+ return true;
1433
+ }
1434
+ if (!owner && await isOldLockDir(lockDir)) {
1435
+ await rm4(lockDir, { recursive: true, force: true });
1436
+ return true;
1437
+ }
1438
+ return false;
1439
+ }
1440
+ async function readServerLockOwner(ownerFile) {
1441
+ try {
1442
+ const raw = await readFile3(ownerFile, "utf8");
1443
+ const parsed = JSON.parse(raw);
1444
+ return typeof parsed.pid === "number" && Number.isFinite(parsed.pid) ? { pid: parsed.pid } : null;
1445
+ } catch {
1446
+ return null;
1447
+ }
1448
+ }
1449
+ async function isOldLockDir(lockDir) {
1450
+ try {
1451
+ const info = await stat(lockDir);
1452
+ return Date.now() - info.mtimeMs > staleServerLockMs;
1453
+ } catch {
1454
+ return false;
1455
+ }
1456
+ }
1457
+ function serverLockOwnerFile() {
1458
+ return path5.join(globalServerLockDir(), "owner.json");
1459
+ }
1460
+ function isAlreadyExistsError(error) {
1461
+ return error instanceof Error && "code" in error && error.code === "EEXIST";
1462
+ }
1364
1463
  async function stopServer(options = {}) {
1365
1464
  if (options.all) {
1366
1465
  const { info: info2, warning: readWarning2 } = await readServerInfoForStop();
1367
- const daemonPids = await listGlossDaemonPids();
1466
+ const daemonPids = (await listGlossDaemonProcesses()).map((processInfo) => processInfo.pid);
1368
1467
  const stoppedPids = [];
1369
1468
  for (const pid of daemonPids) {
1370
1469
  if (await terminatePid(pid)) {
@@ -1393,6 +1492,28 @@ async function stopServer(options = {}) {
1393
1492
  }
1394
1493
  return withWarning({ stopped, info }, warning);
1395
1494
  }
1495
+ async function reapStaleDaemons(managed) {
1496
+ if (process.env.GLOSS_SKIP_STALE_DAEMON_REAP === "1") {
1497
+ return [];
1498
+ }
1499
+ const processes = await listGlossDaemonProcesses();
1500
+ const managedProcess = managed ? processes.find((processInfo) => processInfo.pid === managed.pid) : null;
1501
+ const managedSource = managedProcess ? daemonSourceKey(managedProcess) : managed?.daemonPath ? daemonPathSourceKey(managed.daemonPath) : null;
1502
+ const reapedPids = [];
1503
+ for (const processInfo of processes) {
1504
+ const shouldReap = isMissingDaemonSource(processInfo) || isStaleHomebrewDaemon(processInfo) || managedSource !== null && processInfo.pid !== managed?.pid && daemonSourceKey(processInfo) === managedSource;
1505
+ if (!shouldReap) {
1506
+ continue;
1507
+ }
1508
+ if (await terminatePid(processInfo.pid)) {
1509
+ reapedPids.push(processInfo.pid);
1510
+ if (processInfo.pid === managed?.pid) {
1511
+ await removeServerInfoForPid(processInfo.pid);
1512
+ }
1513
+ }
1514
+ }
1515
+ return reapedPids;
1516
+ }
1396
1517
  function isPidAlive(pid) {
1397
1518
  if (pid <= 0) {
1398
1519
  return false;
@@ -1435,7 +1556,7 @@ async function waitForPidExit(pid, timeoutMs) {
1435
1556
  if (!isPidAlive(pid)) {
1436
1557
  return true;
1437
1558
  }
1438
- await new Promise((resolve) => setTimeout(resolve, 50));
1559
+ await sleep2(50);
1439
1560
  }
1440
1561
  return !isPidAlive(pid);
1441
1562
  }
@@ -1462,7 +1583,7 @@ function combineWarnings(...warnings) {
1462
1583
  }
1463
1584
  async function isGlossDaemonPid(pid) {
1464
1585
  const command = await readProcessCommand(pid);
1465
- return command ? isGlossDaemonCommand(command) : false;
1586
+ return command ? parseGlossDaemonCommand(command) !== null : false;
1466
1587
  }
1467
1588
  async function readProcessCommand(pid) {
1468
1589
  try {
@@ -1473,6 +1594,9 @@ async function readProcessCommand(pid) {
1473
1594
  }
1474
1595
  }
1475
1596
  async function listGlossDaemonPids() {
1597
+ return (await listGlossDaemonProcesses()).map((processInfo) => processInfo.pid);
1598
+ }
1599
+ async function listGlossDaemonProcesses() {
1476
1600
  let stdout;
1477
1601
  try {
1478
1602
  ({ stdout } = await execFileAsync("ps", ["-axo", "pid=,user=,command=", "-ww"]));
@@ -1480,25 +1604,60 @@ async function listGlossDaemonPids() {
1480
1604
  return [];
1481
1605
  }
1482
1606
  const currentUser = userInfo().username;
1483
- return parseGlossDaemonPids(stdout, currentUser, process.pid);
1607
+ return parseGlossDaemonProcesses(stdout, currentUser, process.pid);
1484
1608
  }
1485
- function parseGlossDaemonPids(stdout, currentUser, currentPid = process.pid) {
1609
+ function parseGlossDaemonProcesses(stdout, currentUser, currentPid = process.pid) {
1486
1610
  return stdout.split("\n").map((line) => /^\s*(\d+)\s+(\S+)\s+(.+)$/.exec(line)).filter((match) => Boolean(match)).map((match) => ({
1487
1611
  pid: Number(match[1]),
1488
1612
  user: match[2],
1489
1613
  command: match[3]
1614
+ })).map((processInfo) => ({
1615
+ ...processInfo,
1616
+ parsed: parseGlossDaemonCommand(processInfo.command)
1490
1617
  })).filter(
1491
- ({ pid, user, command }) => pid !== currentPid && user === currentUser && isGlossDaemonCommand(command)
1492
- ).map(({ pid }) => pid);
1618
+ (processInfo) => processInfo.pid !== currentPid && processInfo.user === currentUser && processInfo.parsed !== null
1619
+ ).map(({ pid, user, command, parsed }) => ({
1620
+ pid,
1621
+ user,
1622
+ command,
1623
+ daemonPath: parsed.daemonPath,
1624
+ ...parsed.homebrewVersion ? { homebrewVersion: parsed.homebrewVersion } : {}
1625
+ }));
1626
+ }
1627
+ function parseGlossDaemonCommand(command) {
1628
+ const match = /(?:^|\s)(?:\S*\/)?node\s+(\S*dist\/server\/daemon\.js)(?:\s|$)/.exec(command);
1629
+ if (!match) {
1630
+ return null;
1631
+ }
1632
+ const daemonPath = match[1];
1633
+ const homebrewVersion = /\/Cellar\/gloss\/([^/]+)\/libexec\/lib\/node_modules\/getgloss\/dist\/server\/daemon\.js$/.exec(
1634
+ daemonPath
1635
+ )?.[1];
1636
+ return {
1637
+ daemonPath,
1638
+ ...homebrewVersion ? { homebrewVersion } : {}
1639
+ };
1640
+ }
1641
+ function isMissingDaemonSource(processInfo) {
1642
+ return !existsSync(processInfo.daemonPath);
1643
+ }
1644
+ function isStaleHomebrewDaemon(processInfo) {
1645
+ return Boolean(processInfo.homebrewVersion && processInfo.homebrewVersion !== packageVersion);
1493
1646
  }
1494
- function isGlossDaemonCommand(command) {
1495
- return /(?:^|\s)(?:\S*\/)?node\s+\S*dist\/server\/daemon\.js(?:\s|$)/.test(command);
1647
+ function daemonSourceKey(processInfo) {
1648
+ return daemonPathSourceKey(processInfo.daemonPath);
1649
+ }
1650
+ function daemonPathSourceKey(daemonPath) {
1651
+ return path5.normalize(daemonPath);
1652
+ }
1653
+ async function sleep2(milliseconds) {
1654
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
1496
1655
  }
1497
1656
 
1498
1657
  // src/server/store.ts
1499
1658
  import { createHash } from "crypto";
1500
- import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
1501
- import path5 from "path";
1659
+ import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
1660
+ import path6 from "path";
1502
1661
  import { ulid } from "ulid";
1503
1662
 
1504
1663
  // src/shared/comments.ts
@@ -1969,16 +2128,25 @@ var ReviewStore = class {
1969
2128
  const reviewLoads = [];
1970
2129
  for (const entry of entries) {
1971
2130
  if (entry.isDirectory()) {
1972
- reviewLoads.push(this.loadReview(entry.name));
2131
+ reviewLoads.push(this.loadReviewForList(entry.name));
1973
2132
  }
1974
2133
  }
1975
2134
  await Promise.all(reviewLoads);
1976
2135
  }
2136
+ async loadReviewForList(id) {
2137
+ try {
2138
+ return await this.loadReview(id);
2139
+ } catch (error) {
2140
+ process.stderr.write(`Warning: Skipping corrupt review ${id}: ${formatError(error)}
2141
+ `);
2142
+ return null;
2143
+ }
2144
+ }
1977
2145
  async loadReview(id) {
1978
2146
  const metaPath = globalReviewMetaFile(id);
1979
2147
  let metaRaw;
1980
2148
  try {
1981
- metaRaw = await readFile3(metaPath, "utf8");
2149
+ metaRaw = await readFile4(metaPath, "utf8");
1982
2150
  } catch (error) {
1983
2151
  if (isFileNotFound(error)) {
1984
2152
  return this.loadReviewFromTurnsOnly(id);
@@ -2060,8 +2228,8 @@ var ReviewStore = class {
2060
2228
  let diffRaw;
2061
2229
  try {
2062
2230
  [metaRaw, diffRaw] = await Promise.all([
2063
- readFile3(metaPath, "utf8"),
2064
- readFile3(diffPath, "utf8")
2231
+ readFile4(metaPath, "utf8"),
2232
+ readFile4(diffPath, "utf8")
2065
2233
  ]);
2066
2234
  } catch (error) {
2067
2235
  if (isFileNotFound(error)) {
@@ -2091,7 +2259,7 @@ var ReviewStore = class {
2091
2259
  const diffPath = globalReviewDiffFile(id);
2092
2260
  let diffRaw;
2093
2261
  try {
2094
- diffRaw = await readFile3(diffPath, "utf8");
2262
+ diffRaw = await readFile4(diffPath, "utf8");
2095
2263
  } catch (error) {
2096
2264
  if (isFileNotFound(error)) {
2097
2265
  return null;
@@ -2271,9 +2439,9 @@ function reconcileTurn(meta, diff, feedback, resolution) {
2271
2439
  status,
2272
2440
  submittedAt: feedback?.timestamp ?? meta.submittedAt,
2273
2441
  resolvedAt: status === "resolved" ? resolution?.resolvedAt ?? meta.resolvedAt : void 0,
2274
- feedbackPath: feedback ? meta.feedbackPath ?? path5.join(meta.artifactDir, "feedback.json") : void 0,
2275
- markdownPath: feedback ? meta.markdownPath ?? path5.join(meta.artifactDir, "feedback.md") : void 0,
2276
- resolvedPath: resolution ? meta.resolvedPath ?? path5.join(meta.artifactDir, "resolved.json") : void 0,
2442
+ feedbackPath: feedback ? meta.feedbackPath ?? path6.join(meta.artifactDir, "feedback.json") : void 0,
2443
+ markdownPath: feedback ? meta.markdownPath ?? path6.join(meta.artifactDir, "feedback.md") : void 0,
2444
+ resolvedPath: resolution ? meta.resolvedPath ?? path6.join(meta.artifactDir, "resolved.json") : void 0,
2277
2445
  diff,
2278
2446
  ...feedback ? { feedback } : {},
2279
2447
  ...resolution ? { resolution } : {}
@@ -2306,7 +2474,7 @@ function requiredPath(value, label) {
2306
2474
  async function readOptionalJsonFile(filePath, guard, label) {
2307
2475
  let raw;
2308
2476
  try {
2309
- raw = await readFile3(filePath, "utf8");
2477
+ raw = await readFile4(filePath, "utf8");
2310
2478
  } catch (error) {
2311
2479
  if (isFileNotFound(error)) {
2312
2480
  return void 0;
@@ -2456,7 +2624,9 @@ program.command("start").description("Start or reuse the background server").opt
2456
2624
  });
2457
2625
  program.command("status").description("Show server and active reviews").action(async () => {
2458
2626
  const globals = program.opts();
2459
- const info = await readServerInfo();
2627
+ let info = await readServerInfo();
2628
+ await reapStaleDaemons(info);
2629
+ info = await readServerInfo();
2460
2630
  const responsive = info ? await isServerResponsive(info) : false;
2461
2631
  const reviews = await listReviewsForStatus({ responsive, server: info });
2462
2632
  const status = { running: responsive, server: info, reviews };
@@ -2582,7 +2752,7 @@ async function watchReviewWithReconnect(reviewId, initialInfo, timeoutSeconds, o
2582
2752
  if (!isReconnectableWatchError(error)) {
2583
2753
  throw error;
2584
2754
  }
2585
- await sleep2(500);
2755
+ await sleep3(500);
2586
2756
  const nextInfo = await ensureServer();
2587
2757
  if (nextInfo.port !== info.port) {
2588
2758
  await onServerChanged(nextInfo);
@@ -2597,15 +2767,15 @@ function formatStopResult(result, all) {
2597
2767
  Warning: ${result.warning}` : status;
2598
2768
  }
2599
2769
  async function checkStateDirAccess() {
2600
- const probePath = path6.join(globalStateDir(), `.doctor-${process.pid}-${randomUUID3()}.tmp`);
2770
+ const probePath = path7.join(globalStateDir(), `.doctor-${process.pid}-${randomUUID3()}.tmp`);
2601
2771
  try {
2602
2772
  await ensureDir(globalStateDir());
2603
2773
  await access(globalStateDir(), constants.R_OK | constants.W_OK | constants.X_OK);
2604
- await writeFile3(probePath, "");
2605
- await rm4(probePath, { force: true });
2774
+ await writeFile4(probePath, "");
2775
+ await rm5(probePath, { force: true });
2606
2776
  return { name: "state-dir", ok: true, detail: stateDirDetail() };
2607
2777
  } catch (error) {
2608
- await rm4(probePath, { force: true }).catch(() => void 0);
2778
+ await rm5(probePath, { force: true }).catch(() => void 0);
2609
2779
  return {
2610
2780
  name: "state-dir",
2611
2781
  ok: false,
@@ -2661,7 +2831,7 @@ function isWatchTimeout(error) {
2661
2831
  function isReconnectableWatchError(error) {
2662
2832
  return error instanceof Error && !/^watch failed: [45]\d\d /.test(error.message);
2663
2833
  }
2664
- async function sleep2(milliseconds) {
2834
+ async function sleep3(milliseconds) {
2665
2835
  await new Promise((resolve) => setTimeout(resolve, milliseconds));
2666
2836
  }
2667
2837
  program.parseAsync(process.argv).catch((error) => {