getgloss 0.8.3 → 0.8.4
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 +8 -0
- package/dist/cli/index.js +221 -86
- package/dist/cli/index.js.map +1 -1
- package/dist/server/daemon.js +93 -25
- package/dist/server/daemon.js.map +1 -1
- package/dist/web/setup.md +7 -2
- package/package.json +1 -1
- package/skill/SKILL.md +6 -0
package/README.md
CHANGED
|
@@ -103,6 +103,14 @@ The background server exits automatically after a short idle window with no
|
|
|
103
103
|
pending reviews. `gloss doctor` reports unmanaged daemon processes, and
|
|
104
104
|
`gloss stop --all` cleans them up.
|
|
105
105
|
|
|
106
|
+
You do not need to unlock `~/.gloss/server.json` after finishing a review.
|
|
107
|
+
That file is only the background daemon pointer, not a review lock. If a
|
|
108
|
+
command reports a permission error while cleaning it up, run `gloss doctor`,
|
|
109
|
+
then try `gloss stop --all` from a normal terminal. If macOS flags made the
|
|
110
|
+
file immutable, inspect with `ls -lOe ~/.gloss ~/.gloss/server.json` and clear
|
|
111
|
+
the flag with `chflags nouchg ~/.gloss/server.json`. For sandboxed agents, set
|
|
112
|
+
`GLOSS_STATE_DIR` to a writable directory.
|
|
113
|
+
|
|
106
114
|
`gloss clear` deletes completed review artifacts older than 30 days from
|
|
107
115
|
`~/.gloss/reviews` while always preserving pending reviews. Use
|
|
108
116
|
`gloss clear --dry-run` to preview candidates, or `--older-than <days>` to
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5
|
+
import { constants } from "fs";
|
|
6
|
+
import { access, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
7
|
+
import path6 from "path";
|
|
4
8
|
import { Command } from "commander";
|
|
5
9
|
import openBrowser from "open";
|
|
6
10
|
|
|
@@ -14,6 +18,9 @@ function formatError(error) {
|
|
|
14
18
|
function isFileNotFound(error) {
|
|
15
19
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
16
20
|
}
|
|
21
|
+
function isPermissionError(error) {
|
|
22
|
+
return error instanceof Error && "code" in error && (error.code === "EACCES" || error.code === "EPERM");
|
|
23
|
+
}
|
|
17
24
|
|
|
18
25
|
// src/shared/paths.ts
|
|
19
26
|
import { mkdir } from "fs/promises";
|
|
@@ -23,7 +30,7 @@ import path from "path";
|
|
|
23
30
|
// package.json
|
|
24
31
|
var package_default = {
|
|
25
32
|
name: "getgloss",
|
|
26
|
-
version: "0.8.
|
|
33
|
+
version: "0.8.4",
|
|
27
34
|
description: "Local browser-based diff review for coding-agent loops.",
|
|
28
35
|
type: "module",
|
|
29
36
|
packageManager: "pnpm@10.33.2",
|
|
@@ -592,11 +599,127 @@ function cleanupResult({
|
|
|
592
599
|
};
|
|
593
600
|
}
|
|
594
601
|
|
|
602
|
+
// src/shared/server-info.ts
|
|
603
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
604
|
+
import { readFile as readFile2, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
605
|
+
import path3 from "path";
|
|
606
|
+
|
|
607
|
+
// src/shared/json.ts
|
|
608
|
+
import { randomUUID } from "crypto";
|
|
609
|
+
import { rename, rm as rm2, writeFile } from "fs/promises";
|
|
610
|
+
import path2 from "path";
|
|
611
|
+
function serializeJson(value) {
|
|
612
|
+
return `${JSON.stringify(value, null, 2)}
|
|
613
|
+
`;
|
|
614
|
+
}
|
|
615
|
+
async function writeJsonFile(filePath, value) {
|
|
616
|
+
await writeTextFile(filePath, serializeJson(value));
|
|
617
|
+
}
|
|
618
|
+
async function writeTextFile(filePath, value) {
|
|
619
|
+
const tempPath = path2.join(
|
|
620
|
+
path2.dirname(filePath),
|
|
621
|
+
`.${path2.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`
|
|
622
|
+
);
|
|
623
|
+
try {
|
|
624
|
+
await writeFile(tempPath, value);
|
|
625
|
+
await rename(tempPath, filePath);
|
|
626
|
+
} catch (error) {
|
|
627
|
+
await rm2(tempPath, { force: true }).catch(() => void 0);
|
|
628
|
+
throw error;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/shared/server-info.ts
|
|
633
|
+
async function readServerInfo() {
|
|
634
|
+
let raw;
|
|
635
|
+
try {
|
|
636
|
+
raw = await readFile2(globalServerFile(), "utf8");
|
|
637
|
+
} catch (error) {
|
|
638
|
+
if (isFileNotFound(error)) {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
if (isPermissionError(error)) {
|
|
642
|
+
throw new Error(serverInfoPermissionMessage("read", error), { cause: error });
|
|
643
|
+
}
|
|
644
|
+
throw new Error(`Could not read server info at ${globalServerFile()}: ${formatError(error)}`, {
|
|
645
|
+
cause: error
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
return parseJson(raw, isServerInfo, "server info");
|
|
650
|
+
} catch (error) {
|
|
651
|
+
throw new Error(`Invalid server info at ${globalServerFile()}: ${formatError(error)}`, {
|
|
652
|
+
cause: error
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async function writeServerInfo(info) {
|
|
657
|
+
try {
|
|
658
|
+
await ensureDir(globalStateDir());
|
|
659
|
+
} catch (error) {
|
|
660
|
+
if (isPermissionError(error)) {
|
|
661
|
+
throw new Error(serverInfoPermissionMessage("create", error), { cause: error });
|
|
662
|
+
}
|
|
663
|
+
throw error;
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
await writeJsonFile(globalServerFile(), info);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
if (!isPermissionError(error)) {
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
671
|
+
await assertStateDirWritable();
|
|
672
|
+
try {
|
|
673
|
+
await writeFile2(globalServerFile(), serializeServerInfo(info));
|
|
674
|
+
} catch (directWriteError) {
|
|
675
|
+
throw new Error(serverInfoPermissionMessage("write", directWriteError), {
|
|
676
|
+
cause: directWriteError
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
async function removeServerInfoFile() {
|
|
682
|
+
try {
|
|
683
|
+
await rm3(globalServerFile(), { force: true });
|
|
684
|
+
return null;
|
|
685
|
+
} catch (error) {
|
|
686
|
+
return serverInfoPermissionMessage("remove", error);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function serverInfoPermissionMessage(action, error) {
|
|
690
|
+
const stateDir = globalStateDir();
|
|
691
|
+
const source = process.env.GLOSS_STATE_DIR ? `GLOSS_STATE_DIR=${stateDir}` : "GLOSS_STATE_DIR is not set; defaulting to ~/.gloss";
|
|
692
|
+
return [
|
|
693
|
+
`Could not ${action} Gloss server state at ${globalServerFile()}: ${formatError(error)}.`,
|
|
694
|
+
"`server.json` is not a review lock, so there is nothing to unlock after a review.",
|
|
695
|
+
`Check that ${stateDir} and ${globalServerFile()} are owned and writable by your user.`,
|
|
696
|
+
`On macOS, if the file is immutable, run \`chflags nouchg "${globalServerFile()}"\`.`,
|
|
697
|
+
`For sandboxed agents, set GLOSS_STATE_DIR to a writable directory. ${source}.`
|
|
698
|
+
].join(" ");
|
|
699
|
+
}
|
|
700
|
+
function serializeServerInfo(info) {
|
|
701
|
+
return `${JSON.stringify(info, null, 2)}
|
|
702
|
+
`;
|
|
703
|
+
}
|
|
704
|
+
async function assertStateDirWritable() {
|
|
705
|
+
const probePath = path3.join(
|
|
706
|
+
globalStateDir(),
|
|
707
|
+
`.server.json.${process.pid}.${randomUUID2()}.probe`
|
|
708
|
+
);
|
|
709
|
+
try {
|
|
710
|
+
await writeFile2(probePath, "");
|
|
711
|
+
await rm3(probePath, { force: true });
|
|
712
|
+
} catch (error) {
|
|
713
|
+
await rm3(probePath, { force: true }).catch(() => void 0);
|
|
714
|
+
throw new Error(serverInfoPermissionMessage("write", error), { cause: error });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
595
718
|
// src/cli/git.ts
|
|
596
719
|
import { execa } from "execa";
|
|
597
720
|
|
|
598
721
|
// src/shared/language.ts
|
|
599
|
-
import
|
|
722
|
+
import path4 from "path";
|
|
600
723
|
var languageByExtension = {
|
|
601
724
|
cjs: "js",
|
|
602
725
|
css: "css",
|
|
@@ -618,7 +741,7 @@ var languageByExtension = {
|
|
|
618
741
|
yml: "yaml"
|
|
619
742
|
};
|
|
620
743
|
function languageForPath(filePath) {
|
|
621
|
-
const ext =
|
|
744
|
+
const ext = path4.extname(filePath).slice(1).toLowerCase();
|
|
622
745
|
if (!ext) {
|
|
623
746
|
return null;
|
|
624
747
|
}
|
|
@@ -984,66 +1107,11 @@ async function assertGitAvailable() {
|
|
|
984
1107
|
// src/cli/lifecycle.ts
|
|
985
1108
|
import { execFile, spawn } from "child_process";
|
|
986
1109
|
import { closeSync, existsSync, openSync } from "fs";
|
|
987
|
-
import { rm as rm3 } from "fs/promises";
|
|
988
1110
|
import { userInfo } from "os";
|
|
989
1111
|
import { fileURLToPath } from "url";
|
|
990
1112
|
import { promisify } from "util";
|
|
991
1113
|
import getPort from "get-port";
|
|
992
1114
|
|
|
993
|
-
// src/shared/server-info.ts
|
|
994
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
995
|
-
|
|
996
|
-
// src/shared/json.ts
|
|
997
|
-
import { randomUUID } from "crypto";
|
|
998
|
-
import { rename, rm as rm2, writeFile } from "fs/promises";
|
|
999
|
-
import path3 from "path";
|
|
1000
|
-
function serializeJson(value) {
|
|
1001
|
-
return `${JSON.stringify(value, null, 2)}
|
|
1002
|
-
`;
|
|
1003
|
-
}
|
|
1004
|
-
async function writeJsonFile(filePath, value) {
|
|
1005
|
-
await writeTextFile(filePath, serializeJson(value));
|
|
1006
|
-
}
|
|
1007
|
-
async function writeTextFile(filePath, value) {
|
|
1008
|
-
const tempPath = path3.join(
|
|
1009
|
-
path3.dirname(filePath),
|
|
1010
|
-
`.${path3.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`
|
|
1011
|
-
);
|
|
1012
|
-
try {
|
|
1013
|
-
await writeFile(tempPath, value);
|
|
1014
|
-
await rename(tempPath, filePath);
|
|
1015
|
-
} catch (error) {
|
|
1016
|
-
await rm2(tempPath, { force: true }).catch(() => void 0);
|
|
1017
|
-
throw error;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// src/shared/server-info.ts
|
|
1022
|
-
async function readServerInfo() {
|
|
1023
|
-
let raw;
|
|
1024
|
-
try {
|
|
1025
|
-
raw = await readFile2(globalServerFile(), "utf8");
|
|
1026
|
-
} catch (error) {
|
|
1027
|
-
if (isFileNotFound(error)) {
|
|
1028
|
-
return null;
|
|
1029
|
-
}
|
|
1030
|
-
throw new Error(`Could not read server info at ${globalServerFile()}: ${formatError(error)}`, {
|
|
1031
|
-
cause: error
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
try {
|
|
1035
|
-
return parseJson(raw, isServerInfo, "server info");
|
|
1036
|
-
} catch (error) {
|
|
1037
|
-
throw new Error(`Invalid server info at ${globalServerFile()}: ${formatError(error)}`, {
|
|
1038
|
-
cause: error
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
async function writeServerInfo(info) {
|
|
1043
|
-
await ensureDir(globalStateDir());
|
|
1044
|
-
await writeJsonFile(globalServerFile(), info);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
1115
|
// src/cli/server-client.ts
|
|
1048
1116
|
var ServerClient = class {
|
|
1049
1117
|
constructor(baseUrl) {
|
|
@@ -1172,20 +1240,20 @@ var ServerClient = class {
|
|
|
1172
1240
|
}
|
|
1173
1241
|
}
|
|
1174
1242
|
}
|
|
1175
|
-
async get(
|
|
1176
|
-
const response = await fetch(`${this.baseUrl}${
|
|
1243
|
+
async get(path7, guard, label) {
|
|
1244
|
+
const response = await fetch(`${this.baseUrl}${path7}`);
|
|
1177
1245
|
return parseResponse(response, guard, label);
|
|
1178
1246
|
}
|
|
1179
|
-
async post(
|
|
1180
|
-
const response = await fetch(`${this.baseUrl}${
|
|
1247
|
+
async post(path7, body, guard, label) {
|
|
1248
|
+
const response = await fetch(`${this.baseUrl}${path7}`, {
|
|
1181
1249
|
method: "POST",
|
|
1182
1250
|
headers: { "content-type": "application/json" },
|
|
1183
1251
|
body: JSON.stringify(body)
|
|
1184
1252
|
});
|
|
1185
1253
|
return parseResponse(response, guard, label);
|
|
1186
1254
|
}
|
|
1187
|
-
async delete(
|
|
1188
|
-
const response = await fetch(`${this.baseUrl}${
|
|
1255
|
+
async delete(path7, guard, label) {
|
|
1256
|
+
const response = await fetch(`${this.baseUrl}${path7}`, { method: "DELETE" });
|
|
1189
1257
|
return parseResponse(response, guard, label);
|
|
1190
1258
|
}
|
|
1191
1259
|
};
|
|
@@ -1276,7 +1344,12 @@ async function launchServer(port) {
|
|
|
1276
1344
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1277
1345
|
stateDir: globalStateDir()
|
|
1278
1346
|
};
|
|
1279
|
-
|
|
1347
|
+
try {
|
|
1348
|
+
await writeServerInfo(info);
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
await terminatePid(info.pid);
|
|
1351
|
+
throw error;
|
|
1352
|
+
}
|
|
1280
1353
|
const deadline = Date.now() + 8e3;
|
|
1281
1354
|
while (Date.now() < deadline) {
|
|
1282
1355
|
if (await isServerResponsive(info)) {
|
|
@@ -1290,7 +1363,7 @@ async function launchServer(port) {
|
|
|
1290
1363
|
}
|
|
1291
1364
|
async function stopServer(options = {}) {
|
|
1292
1365
|
if (options.all) {
|
|
1293
|
-
const info2 = await
|
|
1366
|
+
const { info: info2, warning: readWarning2 } = await readServerInfoForStop();
|
|
1294
1367
|
const daemonPids = await listGlossDaemonPids();
|
|
1295
1368
|
const stoppedPids = [];
|
|
1296
1369
|
for (const pid of daemonPids) {
|
|
@@ -1298,26 +1371,27 @@ async function stopServer(options = {}) {
|
|
|
1298
1371
|
stoppedPids.push(pid);
|
|
1299
1372
|
}
|
|
1300
1373
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1374
|
+
return withWarning(
|
|
1375
|
+
{ stopped: stoppedPids.length > 0, info: info2, stoppedPids },
|
|
1376
|
+
combineWarnings(readWarning2, await removeServerInfoFile())
|
|
1377
|
+
);
|
|
1303
1378
|
}
|
|
1304
|
-
const info = await
|
|
1379
|
+
const { info, warning: readWarning } = await readServerInfoForStop();
|
|
1305
1380
|
if (!info) {
|
|
1306
|
-
return { stopped: false, info: null };
|
|
1381
|
+
return withWarning({ stopped: false, info: null }, readWarning);
|
|
1307
1382
|
}
|
|
1308
1383
|
if (!isPidAlive(info.pid)) {
|
|
1309
|
-
await removeServerInfoForPid(info.pid);
|
|
1310
|
-
return { stopped: false, info };
|
|
1384
|
+
return withWarning({ stopped: false, info }, await removeServerInfoForPid(info.pid));
|
|
1311
1385
|
}
|
|
1312
1386
|
if (!await isGlossDaemonPid(info.pid)) {
|
|
1313
|
-
await removeServerInfoForPid(info.pid);
|
|
1314
|
-
return { stopped: false, info };
|
|
1387
|
+
return withWarning({ stopped: false, info }, await removeServerInfoForPid(info.pid));
|
|
1315
1388
|
}
|
|
1316
1389
|
const stopped = await terminatePid(info.pid);
|
|
1390
|
+
let warning = null;
|
|
1317
1391
|
if (stopped) {
|
|
1318
|
-
await removeServerInfoForPid(info.pid);
|
|
1392
|
+
warning = await removeServerInfoForPid(info.pid);
|
|
1319
1393
|
}
|
|
1320
|
-
return { stopped, info };
|
|
1394
|
+
return withWarning({ stopped, info }, warning);
|
|
1321
1395
|
}
|
|
1322
1396
|
function isPidAlive(pid) {
|
|
1323
1397
|
if (pid <= 0) {
|
|
@@ -1368,8 +1442,23 @@ async function waitForPidExit(pid, timeoutMs) {
|
|
|
1368
1442
|
async function removeServerInfoForPid(pid) {
|
|
1369
1443
|
const current = await readServerInfo().catch(() => null);
|
|
1370
1444
|
if (!current || current.pid === pid) {
|
|
1371
|
-
|
|
1445
|
+
return removeServerInfoFile();
|
|
1372
1446
|
}
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
function withWarning(result, warning) {
|
|
1450
|
+
return warning ? { ...result, warning } : result;
|
|
1451
|
+
}
|
|
1452
|
+
async function readServerInfoForStop() {
|
|
1453
|
+
try {
|
|
1454
|
+
return { info: await readServerInfo(), warning: null };
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
return { info: null, warning: error instanceof Error ? error.message : String(error) };
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
function combineWarnings(...warnings) {
|
|
1460
|
+
const present = warnings.filter((warning) => Boolean(warning));
|
|
1461
|
+
return present.length > 0 ? present.join(" ") : null;
|
|
1373
1462
|
}
|
|
1374
1463
|
async function isGlossDaemonPid(pid) {
|
|
1375
1464
|
const command = await readProcessCommand(pid);
|
|
@@ -1409,7 +1498,7 @@ function isGlossDaemonCommand(command) {
|
|
|
1409
1498
|
// src/server/store.ts
|
|
1410
1499
|
import { createHash } from "crypto";
|
|
1411
1500
|
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
1412
|
-
import
|
|
1501
|
+
import path5 from "path";
|
|
1413
1502
|
import { ulid } from "ulid";
|
|
1414
1503
|
|
|
1415
1504
|
// src/shared/comments.ts
|
|
@@ -2182,9 +2271,9 @@ function reconcileTurn(meta, diff, feedback, resolution) {
|
|
|
2182
2271
|
status,
|
|
2183
2272
|
submittedAt: feedback?.timestamp ?? meta.submittedAt,
|
|
2184
2273
|
resolvedAt: status === "resolved" ? resolution?.resolvedAt ?? meta.resolvedAt : void 0,
|
|
2185
|
-
feedbackPath: feedback ? meta.feedbackPath ??
|
|
2186
|
-
markdownPath: feedback ? meta.markdownPath ??
|
|
2187
|
-
resolvedPath: resolution ? meta.resolvedPath ??
|
|
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,
|
|
2188
2277
|
diff,
|
|
2189
2278
|
...feedback ? { feedback } : {},
|
|
2190
2279
|
...resolution ? { resolution } : {}
|
|
@@ -2378,9 +2467,7 @@ program.command("status").description("Show server and active reviews").action(a
|
|
|
2378
2467
|
program.command("stop").description("Stop the managed background server").option("--all", "stop all Gloss daemon processes for the current user").action(async (options) => {
|
|
2379
2468
|
const globals = program.opts();
|
|
2380
2469
|
const result = await stopServer({ all: options.all });
|
|
2381
|
-
globals.json ? printJson(result) : printPlain(
|
|
2382
|
-
options.all && result.stoppedPids ? `Stopped ${result.stoppedPids.length} Gloss daemon(s)` : result.stopped ? "Gloss server stopped" : "Gloss server was not running"
|
|
2383
|
-
);
|
|
2470
|
+
globals.json ? printJson(result) : printPlain(formatStopResult(result, options.all === true));
|
|
2384
2471
|
});
|
|
2385
2472
|
program.command("clear").description("Delete old completed review artifacts").option(
|
|
2386
2473
|
"--older-than <days>",
|
|
@@ -2438,11 +2525,19 @@ program.command("doctor").description("Diagnose setup and validate git/state").a
|
|
|
2438
2525
|
detail: error instanceof Error ? error.message : String(error)
|
|
2439
2526
|
});
|
|
2440
2527
|
}
|
|
2441
|
-
|
|
2528
|
+
checks.push(await checkStateDirAccess());
|
|
2529
|
+
checks.push(await checkServerInfoAccess());
|
|
2530
|
+
let info = null;
|
|
2531
|
+
let serverStateError = null;
|
|
2532
|
+
try {
|
|
2533
|
+
info = await readServerInfo();
|
|
2534
|
+
} catch (error) {
|
|
2535
|
+
serverStateError = error;
|
|
2536
|
+
}
|
|
2442
2537
|
checks.push({
|
|
2443
2538
|
name: "server",
|
|
2444
2539
|
ok: info ? await isServerResponsive(info) : false,
|
|
2445
|
-
detail: info ? serverUrl(info) : "not started"
|
|
2540
|
+
detail: info ? serverUrl(info) : serverStateError ? formatError(serverStateError) : "not started"
|
|
2446
2541
|
});
|
|
2447
2542
|
try {
|
|
2448
2543
|
const daemonPids = await listGlossDaemonPids();
|
|
@@ -2496,6 +2591,46 @@ async function watchReviewWithReconnect(reviewId, initialInfo, timeoutSeconds, o
|
|
|
2496
2591
|
}
|
|
2497
2592
|
}
|
|
2498
2593
|
}
|
|
2594
|
+
function formatStopResult(result, all) {
|
|
2595
|
+
const status = all && result.stoppedPids ? `Stopped ${result.stoppedPids.length} Gloss daemon(s)` : result.stopped ? "Gloss server stopped" : "Gloss server was not running";
|
|
2596
|
+
return result.warning ? `${status}
|
|
2597
|
+
Warning: ${result.warning}` : status;
|
|
2598
|
+
}
|
|
2599
|
+
async function checkStateDirAccess() {
|
|
2600
|
+
const probePath = path6.join(globalStateDir(), `.doctor-${process.pid}-${randomUUID3()}.tmp`);
|
|
2601
|
+
try {
|
|
2602
|
+
await ensureDir(globalStateDir());
|
|
2603
|
+
await access(globalStateDir(), constants.R_OK | constants.W_OK | constants.X_OK);
|
|
2604
|
+
await writeFile3(probePath, "");
|
|
2605
|
+
await rm4(probePath, { force: true });
|
|
2606
|
+
return { name: "state-dir", ok: true, detail: stateDirDetail() };
|
|
2607
|
+
} catch (error) {
|
|
2608
|
+
await rm4(probePath, { force: true }).catch(() => void 0);
|
|
2609
|
+
return {
|
|
2610
|
+
name: "state-dir",
|
|
2611
|
+
ok: false,
|
|
2612
|
+
detail: `${stateDirDetail()}: ${formatError(error)}. Set GLOSS_STATE_DIR to a writable directory for sandboxed agents.`
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
async function checkServerInfoAccess() {
|
|
2617
|
+
try {
|
|
2618
|
+
await access(globalServerFile(), constants.R_OK | constants.W_OK);
|
|
2619
|
+
return { name: "server-json", ok: true, detail: globalServerFile() };
|
|
2620
|
+
} catch (error) {
|
|
2621
|
+
if (isFileNotFound(error)) {
|
|
2622
|
+
return { name: "server-json", ok: true, detail: "not present" };
|
|
2623
|
+
}
|
|
2624
|
+
return {
|
|
2625
|
+
name: "server-json",
|
|
2626
|
+
ok: false,
|
|
2627
|
+
detail: serverInfoPermissionMessage("access", error)
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
function stateDirDetail() {
|
|
2632
|
+
return process.env.GLOSS_STATE_DIR ? `${globalStateDir()} (from GLOSS_STATE_DIR)` : `${globalStateDir()} (default; set GLOSS_STATE_DIR for a writable sandbox state dir)`;
|
|
2633
|
+
}
|
|
2499
2634
|
async function baseForExistingReview(client, reviewId) {
|
|
2500
2635
|
const record = await client.getReview(reviewId);
|
|
2501
2636
|
return record.diff.scope.mode === "explicit" ? record.diff.scope.requestedBase ?? record.diff.base.ref : null;
|