getgloss 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/dist/cli/index.js +51 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/server/daemon.js +173 -19
- package/dist/server/daemon.js.map +1 -1
- package/dist/web/assets/index-0IC9C6Wd.js +307 -0
- package/dist/web/assets/index-acdiHHul.css +1 -0
- package/dist/web/assets/{syntax-UR_bDbhr.js → syntax-CvRmtNgl.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/prompt.md +3 -2
- package/dist/web/setup/index.html +5 -1
- package/dist/web/setup.md +8 -3
- package/package.json +3 -3
- package/skill/SKILL.md +2 -2
- package/dist/web/assets/index-BYtByjSm.js +0 -306
- package/dist/web/assets/index-Bn84l3Zr.css +0 -1
package/dist/server/daemon.js
CHANGED
|
@@ -10,7 +10,7 @@ import path from "path";
|
|
|
10
10
|
// package.json
|
|
11
11
|
var package_default = {
|
|
12
12
|
name: "getgloss",
|
|
13
|
-
version: "0.
|
|
13
|
+
version: "0.13.0",
|
|
14
14
|
description: "Local browser-based diff review for coding-agent loops.",
|
|
15
15
|
type: "module",
|
|
16
16
|
packageManager: "pnpm@10.33.2",
|
|
@@ -67,8 +67,8 @@ var package_default = {
|
|
|
67
67
|
tsup: "8.5.1",
|
|
68
68
|
tsx: "4.22.3",
|
|
69
69
|
typescript: "5.9.3",
|
|
70
|
-
vite: "6.4.
|
|
71
|
-
vitest: "3.2.
|
|
70
|
+
vite: "6.4.3",
|
|
71
|
+
vitest: "3.2.6"
|
|
72
72
|
},
|
|
73
73
|
keywords: [
|
|
74
74
|
"diff",
|
|
@@ -202,6 +202,7 @@ var DIFF_SCOPE_MODES = ["working", "branch", "explicit"];
|
|
|
202
202
|
var DIFF_FALLBACK_REASONS = ["working-tree-clean", "missing-branch-base"];
|
|
203
203
|
var DIFF_CONTEXT_MAX_LINES = 500;
|
|
204
204
|
var SOURCE_PEEK_MAX_BYTES = 35e4;
|
|
205
|
+
var SOURCE_PEEK_RANGE_MAX_LINES = 240;
|
|
205
206
|
var REVIEW_SCOPE_MODES = ["all", "single", "range"];
|
|
206
207
|
var RESOLUTION_STATUSES = ["partial", "resolved"];
|
|
207
208
|
var OPEN_FILE_SCOPES = ["review", "repo"];
|
|
@@ -272,6 +273,9 @@ function isDiffContextRequest(value) {
|
|
|
272
273
|
function isSourcePeekRequest(value) {
|
|
273
274
|
return isRecord(value) && isString(value.filePath) && isNullableString(value.oldPath) && isOptionalString(value.turnId) && isDiffContextSource(value.source) && isOneOf(value.side, SIDES) && isPositiveInteger(value.line) && isNonNegativeInteger(value.column) && isIdentifier(value.symbol);
|
|
274
275
|
}
|
|
276
|
+
function isSourcePeekRangeRequest(value) {
|
|
277
|
+
return isRecord(value) && isString(value.filePath) && isOptionalString(value.turnId) && isDiffContextSource(value.source) && isOneOf(value.side, SIDES) && isPositiveInteger(value.startLine) && isPositiveInteger(value.lineCount) && value.lineCount <= SOURCE_PEEK_RANGE_MAX_LINES;
|
|
278
|
+
}
|
|
275
279
|
function isSubmitReviewRequest(value) {
|
|
276
280
|
return isRecord(value) && isArrayOf(value.comments, isComment) && isOptional(value.reviewScope, isReviewScope);
|
|
277
281
|
}
|
|
@@ -361,7 +365,13 @@ function isDiffLine(value) {
|
|
|
361
365
|
return isRecord(value) && isOneOf(value.type, DIFF_LINE_TYPES) && isNullableNumber(value.oldLine) && isNullableNumber(value.newLine) && isString(value.content);
|
|
362
366
|
}
|
|
363
367
|
function isComment(value) {
|
|
364
|
-
|
|
368
|
+
if (!isRecord(value)) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
if (value.kind === "general") {
|
|
372
|
+
return isString(value.id) && isString(value.body) && isString(value.createdAt);
|
|
373
|
+
}
|
|
374
|
+
return isString(value.id) && (value.kind === void 0 || value.kind === "line") && isString(value.filePath) && isNumber(value.startLine) && isNumber(value.endLine) && isOneOf(value.side, SIDES) && isString(value.body) && isString(value.originalSnippet) && isString(value.createdAt);
|
|
365
375
|
}
|
|
366
376
|
function isResolvedComment(value) {
|
|
367
377
|
return isRecord(value) && isString(value.commentId) && value.status === "resolved" && isOptionalString(value.summary) && isString(value.resolvedAt);
|
|
@@ -576,11 +586,28 @@ import { Hono } from "hono";
|
|
|
576
586
|
import { streamSSE } from "hono/streaming";
|
|
577
587
|
|
|
578
588
|
// src/shared/comments.ts
|
|
589
|
+
function isLineComment(comment) {
|
|
590
|
+
return comment.kind === void 0 || comment.kind === "line";
|
|
591
|
+
}
|
|
579
592
|
function compareCommentsByLocation(a, b) {
|
|
593
|
+
const aIsLine = isLineComment(a);
|
|
594
|
+
const bIsLine = isLineComment(b);
|
|
595
|
+
if (!aIsLine || !bIsLine) {
|
|
596
|
+
if (aIsLine !== bIsLine) {
|
|
597
|
+
return aIsLine ? 1 : -1;
|
|
598
|
+
}
|
|
599
|
+
return a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id);
|
|
600
|
+
}
|
|
580
601
|
return a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine || a.endLine - b.endLine || a.side.localeCompare(b.side);
|
|
581
602
|
}
|
|
582
603
|
function countCommentFiles(comments) {
|
|
583
|
-
|
|
604
|
+
const filePaths = /* @__PURE__ */ new Set();
|
|
605
|
+
for (const comment of comments) {
|
|
606
|
+
if (isLineComment(comment)) {
|
|
607
|
+
filePaths.add(comment.filePath);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return filePaths.size;
|
|
584
611
|
}
|
|
585
612
|
function formatLineRange(range, options = {}) {
|
|
586
613
|
const startLine = Math.min(range.startLine, range.endLine);
|
|
@@ -777,6 +804,17 @@ function summarizeDiffFiles(files) {
|
|
|
777
804
|
);
|
|
778
805
|
}
|
|
779
806
|
|
|
807
|
+
// src/shared/file-order.ts
|
|
808
|
+
function compareFilePaths(leftPath, rightPath, leftTieBreaker = "", rightTieBreaker = "") {
|
|
809
|
+
return leftPath.localeCompare(rightPath, void 0, { sensitivity: "base" }) || leftPath.localeCompare(rightPath) || leftTieBreaker.localeCompare(rightTieBreaker, void 0, { sensitivity: "base" }) || leftTieBreaker.localeCompare(rightTieBreaker);
|
|
810
|
+
}
|
|
811
|
+
function compareDiffFiles(left, right) {
|
|
812
|
+
return compareFilePaths(left.path, right.path, left.oldPath ?? "", right.oldPath ?? "");
|
|
813
|
+
}
|
|
814
|
+
function sortDiffFiles(files) {
|
|
815
|
+
return files.toSorted(compareDiffFiles);
|
|
816
|
+
}
|
|
817
|
+
|
|
780
818
|
// src/shared/git-diff.ts
|
|
781
819
|
var DIFF_ARGS = ["diff", "--no-color", "--find-renames", "--find-copies"];
|
|
782
820
|
async function git(args, cwd) {
|
|
@@ -785,7 +823,7 @@ async function git(args, cwd) {
|
|
|
785
823
|
}
|
|
786
824
|
async function captureCommitRangeDiff(fromSha, toSha, repoRoot) {
|
|
787
825
|
const rawDiff = await git([...DIFF_ARGS, `${fromSha}^`, toSha, "--"], repoRoot);
|
|
788
|
-
const files = parseUnifiedDiff(rawDiff);
|
|
826
|
+
const files = sortDiffFiles(parseUnifiedDiff(rawDiff));
|
|
789
827
|
return {
|
|
790
828
|
stats: summarizeDiffFiles(files),
|
|
791
829
|
rawDiff,
|
|
@@ -1013,6 +1051,25 @@ async function resolveSourcePeek({
|
|
|
1013
1051
|
}
|
|
1014
1052
|
throw new Error(`No definition found for ${symbol}`);
|
|
1015
1053
|
}
|
|
1054
|
+
async function readSourcePeekRange({
|
|
1055
|
+
lineCount,
|
|
1056
|
+
repoRoot,
|
|
1057
|
+
sourceFilePath,
|
|
1058
|
+
sourceRef,
|
|
1059
|
+
startLine
|
|
1060
|
+
}) {
|
|
1061
|
+
const content = await readRepoText(repoRoot, sourceRef, sourceFilePath);
|
|
1062
|
+
const limited = limitContentRange(content, startLine, lineCount);
|
|
1063
|
+
return {
|
|
1064
|
+
filePath: sourceFilePath,
|
|
1065
|
+
startLine: limited.startLine,
|
|
1066
|
+
totalLines: limited.totalLines,
|
|
1067
|
+
content: limited.content,
|
|
1068
|
+
truncated: limited.truncated,
|
|
1069
|
+
hasMoreAbove: limited.hasMoreAbove,
|
|
1070
|
+
hasMoreBelow: limited.hasMoreBelow
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1016
1073
|
async function resolveImportedTarget({
|
|
1017
1074
|
aliasConfig,
|
|
1018
1075
|
importedName,
|
|
@@ -1191,37 +1248,86 @@ function responseForMatch({
|
|
|
1191
1248
|
language: languageForPath(filePath),
|
|
1192
1249
|
content: limited.content,
|
|
1193
1250
|
truncated: limited.truncated,
|
|
1251
|
+
totalLines: limited.totalLines,
|
|
1252
|
+
hasMoreAbove: limited.hasMoreAbove,
|
|
1253
|
+
hasMoreBelow: limited.hasMoreBelow,
|
|
1194
1254
|
matchReason
|
|
1195
1255
|
};
|
|
1196
1256
|
}
|
|
1197
1257
|
function limitContentAroundLine(content, line) {
|
|
1258
|
+
const lines = splitFileLines2(content);
|
|
1259
|
+
const totalLines = sourceTotalLines(lines);
|
|
1198
1260
|
if (Buffer.byteLength(content, "utf8") <= SOURCE_PEEK_MAX_BYTES) {
|
|
1199
|
-
return {
|
|
1261
|
+
return {
|
|
1262
|
+
content,
|
|
1263
|
+
startLine: 1,
|
|
1264
|
+
totalLines,
|
|
1265
|
+
truncated: false,
|
|
1266
|
+
hasMoreAbove: false,
|
|
1267
|
+
hasMoreBelow: false
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
if (lines.length === 0) {
|
|
1271
|
+
return emptySourceWindow();
|
|
1200
1272
|
}
|
|
1201
|
-
const lines = splitFileLines2(content);
|
|
1202
1273
|
const targetIndex = Math.max(0, line - 1);
|
|
1203
1274
|
const preferredStartIndex = Math.max(0, targetIndex - SOURCE_PEEK_TARGET_CONTEXT_LINES);
|
|
1204
1275
|
const preferredEndIndex = Math.min(lines.length, preferredStartIndex + SOURCE_PEEK_CONTEXT_LINES);
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1276
|
+
return limitLinesByIndexes(lines, preferredStartIndex, preferredEndIndex, targetIndex);
|
|
1277
|
+
}
|
|
1278
|
+
function limitContentRange(content, startLine, lineCount) {
|
|
1279
|
+
const lines = splitFileLines2(content);
|
|
1280
|
+
if (lines.length === 0) {
|
|
1281
|
+
return emptySourceWindow();
|
|
1282
|
+
}
|
|
1283
|
+
const cappedLineCount = Math.min(lineCount, SOURCE_PEEK_RANGE_MAX_LINES);
|
|
1284
|
+
const startIndex = clampNumber(startLine - 1, 0, lines.length - 1);
|
|
1285
|
+
const endIndex = Math.min(lines.length, startIndex + cappedLineCount);
|
|
1286
|
+
return limitLinesByIndexes(lines, startIndex, endIndex, null);
|
|
1287
|
+
}
|
|
1288
|
+
function limitLinesByIndexes(lines, startIndex, endIndex, targetIndex) {
|
|
1289
|
+
let limitedStartIndex = startIndex;
|
|
1290
|
+
let limitedEndIndex = endIndex;
|
|
1291
|
+
let limitedContent = lines.slice(limitedStartIndex, limitedEndIndex).join("\n");
|
|
1292
|
+
let truncatedByBytes = false;
|
|
1293
|
+
while (Buffer.byteLength(limitedContent, "utf8") > SOURCE_PEEK_MAX_BYTES && limitedEndIndex - limitedStartIndex > 1) {
|
|
1294
|
+
if (targetIndex !== null && targetIndex - limitedStartIndex > limitedEndIndex - targetIndex - 1) {
|
|
1295
|
+
limitedStartIndex += 1;
|
|
1211
1296
|
} else {
|
|
1212
|
-
|
|
1297
|
+
limitedEndIndex -= 1;
|
|
1213
1298
|
}
|
|
1214
|
-
limitedContent = lines.slice(
|
|
1299
|
+
limitedContent = lines.slice(limitedStartIndex, limitedEndIndex).join("\n");
|
|
1300
|
+
truncatedByBytes = true;
|
|
1215
1301
|
}
|
|
1216
1302
|
if (Buffer.byteLength(limitedContent, "utf8") > SOURCE_PEEK_MAX_BYTES) {
|
|
1217
1303
|
limitedContent = truncateUtf8(limitedContent, SOURCE_PEEK_MAX_BYTES);
|
|
1304
|
+
truncatedByBytes = true;
|
|
1218
1305
|
}
|
|
1219
1306
|
return {
|
|
1220
1307
|
content: limitedContent,
|
|
1221
|
-
startLine:
|
|
1222
|
-
|
|
1308
|
+
startLine: limitedStartIndex + 1,
|
|
1309
|
+
totalLines: sourceTotalLines(lines),
|
|
1310
|
+
truncated: truncatedByBytes || limitedStartIndex > 0 || limitedEndIndex < lines.length,
|
|
1311
|
+
hasMoreAbove: limitedStartIndex > 0,
|
|
1312
|
+
hasMoreBelow: limitedEndIndex < lines.length
|
|
1223
1313
|
};
|
|
1224
1314
|
}
|
|
1315
|
+
function emptySourceWindow() {
|
|
1316
|
+
return {
|
|
1317
|
+
content: "",
|
|
1318
|
+
startLine: 1,
|
|
1319
|
+
totalLines: 1,
|
|
1320
|
+
truncated: false,
|
|
1321
|
+
hasMoreAbove: false,
|
|
1322
|
+
hasMoreBelow: false
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
function sourceTotalLines(lines) {
|
|
1326
|
+
return Math.max(1, lines.length);
|
|
1327
|
+
}
|
|
1328
|
+
function clampNumber(value, min, max) {
|
|
1329
|
+
return Math.min(max, Math.max(min, value));
|
|
1330
|
+
}
|
|
1225
1331
|
function truncateUtf8(value, maxBytes) {
|
|
1226
1332
|
const buffer = Buffer.from(value, "utf8");
|
|
1227
1333
|
if (buffer.byteLength <= maxBytes) {
|
|
@@ -1833,9 +1939,11 @@ function languageForSnippet(filePath, snippet) {
|
|
|
1833
1939
|
}
|
|
1834
1940
|
function serializeFeedbackMarkdown(bundle) {
|
|
1835
1941
|
const comments = bundle.comments.toSorted(compareCommentsByLocation);
|
|
1942
|
+
const generalComments = comments.filter((comment) => !isLineComment(comment));
|
|
1943
|
+
const lineComments = comments.filter(isLineComment);
|
|
1836
1944
|
const commentsByFile = /* @__PURE__ */ new Map();
|
|
1837
1945
|
const files = [];
|
|
1838
|
-
for (const comment of
|
|
1946
|
+
for (const comment of lineComments) {
|
|
1839
1947
|
const fileComments = commentsByFile.get(comment.filePath);
|
|
1840
1948
|
if (fileComments) {
|
|
1841
1949
|
fileComments.push(comment);
|
|
@@ -1853,6 +1961,12 @@ function serializeFeedbackMarkdown(bundle) {
|
|
|
1853
1961
|
`Files: ${files.length} Comments: ${comments.length}`,
|
|
1854
1962
|
""
|
|
1855
1963
|
];
|
|
1964
|
+
if (generalComments.length > 0) {
|
|
1965
|
+
lines.push("## General comments", "");
|
|
1966
|
+
for (const comment of generalComments) {
|
|
1967
|
+
lines.push(`### ${comment.id}`, comment.body.trim(), "");
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1856
1970
|
for (const filePath of files) {
|
|
1857
1971
|
lines.push(`## ${filePath}`, "");
|
|
1858
1972
|
for (const comment of commentsByFile.get(filePath) ?? []) {
|
|
@@ -2914,6 +3028,46 @@ function createApp(origin2, options = {}) {
|
|
|
2914
3028
|
return c.json({ error: `source peek unavailable: ${formatError(error)}` }, 404);
|
|
2915
3029
|
}
|
|
2916
3030
|
});
|
|
3031
|
+
app.post("/api/reviews/:id/source-peek/range", async (c) => {
|
|
3032
|
+
const id = c.req.param("id");
|
|
3033
|
+
const existing = await reviewStore.get(id);
|
|
3034
|
+
if (!existing) {
|
|
3035
|
+
return c.json({ error: "review not found" }, 404);
|
|
3036
|
+
}
|
|
3037
|
+
const parsed = await readJsonBody(c, isSourcePeekRangeRequest, "source peek range request");
|
|
3038
|
+
if (!parsed.ok) {
|
|
3039
|
+
return parsed.response;
|
|
3040
|
+
}
|
|
3041
|
+
const body = parsed.body;
|
|
3042
|
+
const turn = body.turnId ? await reviewStore.getTurn(id, body.turnId) : null;
|
|
3043
|
+
if (body.turnId && !turn) {
|
|
3044
|
+
return c.json({ error: "turn not found" }, 404);
|
|
3045
|
+
}
|
|
3046
|
+
const diffPayload = turn?.diff ?? existing.diff;
|
|
3047
|
+
const repoRoot = path9.resolve(diffPayload.cwd);
|
|
3048
|
+
const pathError = validateContextPath(repoRoot, body.filePath, "filePath");
|
|
3049
|
+
if (pathError) {
|
|
3050
|
+
return c.json({ error: pathError }, 400);
|
|
3051
|
+
}
|
|
3052
|
+
const source = await resolveContextSource(diffPayload, body.source);
|
|
3053
|
+
if (!source.ok) {
|
|
3054
|
+
return c.json({ error: source.error }, source.status);
|
|
3055
|
+
}
|
|
3056
|
+
const sourceRef = body.side === "L" ? source.oldRef : source.newRef;
|
|
3057
|
+
try {
|
|
3058
|
+
return c.json(
|
|
3059
|
+
await readSourcePeekRange({
|
|
3060
|
+
repoRoot,
|
|
3061
|
+
sourceFilePath: body.filePath,
|
|
3062
|
+
sourceRef,
|
|
3063
|
+
startLine: body.startLine,
|
|
3064
|
+
lineCount: body.lineCount
|
|
3065
|
+
})
|
|
3066
|
+
);
|
|
3067
|
+
} catch (error) {
|
|
3068
|
+
return c.json({ error: `source range unavailable: ${formatError(error)}` }, 404);
|
|
3069
|
+
}
|
|
3070
|
+
});
|
|
2917
3071
|
app.post("/api/reviews/:id/files/content", async (c) => {
|
|
2918
3072
|
const id = c.req.param("id");
|
|
2919
3073
|
const existing = await reviewStore.get(id);
|