getgloss 0.12.3 → 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 CHANGED
@@ -122,9 +122,16 @@ choose a different retention window.
122
122
  ## Review UI
123
123
 
124
124
  In the browser review, drag over a changed line or range to open a draft
125
- comment. Use `Command+Enter` to save the active draft comment. Use
126
- `Command+Shift+Enter` to submit the review; this matches the Submit button and
127
- includes already-saved comments only.
125
+ comment. Add review-level general comments from the submit bar when the feedback
126
+ is not tied to one line. Use `Command+Enter` to save the active draft comment.
127
+ Use `Command+Shift+Enter` to submit the review; this matches the Submit button
128
+ and includes already-saved comments only.
129
+
130
+ Lockfiles are hidden by default in the browser review and stay one click away in
131
+ the hidden-files banner. Use the file filter menu to also hide boring files such
132
+ as snapshots, generated files, and vendored files. These filters affect only the
133
+ browser view: submitted feedback and review artifacts still keep the full
134
+ captured diff.
128
135
 
129
136
  When branch reviews include per-commit diffs, the commit picker changes only the
130
137
  current preview. Gloss persists that selected scope when the review is submitted,
@@ -148,9 +155,10 @@ Submitted reviews are written to:
148
155
  ```
149
156
 
150
157
  `feedback.json` is the machine-readable payload and includes `reviewScope` for
151
- submitted commit-preview scope. `feedback.md` is a readable summary ordered by
152
- file and line. `resolved.json` is mutable resolution progress for individual
153
- comments and the turn. After applying feedback, use
158
+ submitted commit-preview scope. `feedback.md` is a readable summary with
159
+ general comments first, then file comments ordered by path and line.
160
+ `resolved.json` is mutable resolution progress for individual comments and the
161
+ turn. After applying feedback, use
154
162
  `gloss resolve <reviewId> --comment <commentId> --summary "..."` for a single
155
163
  comment or `gloss resolve <reviewId> --turn <id-or-index> --summary "..."` for a
156
164
  specific turn. Without `--turn`, whole-review resolution targets the latest
package/dist/cli/index.js CHANGED
@@ -30,7 +30,7 @@ import path from "path";
30
30
  // package.json
31
31
  var package_default = {
32
32
  name: "getgloss",
33
- version: "0.12.3",
33
+ version: "0.13.0",
34
34
  description: "Local browser-based diff review for coding-agent loops.",
35
35
  type: "module",
36
36
  packageManager: "pnpm@10.33.2",
@@ -87,8 +87,8 @@ var package_default = {
87
87
  tsup: "8.5.1",
88
88
  tsx: "4.22.3",
89
89
  typescript: "5.9.3",
90
- vite: "6.4.2",
91
- vitest: "3.2.4"
90
+ vite: "6.4.3",
91
+ vitest: "3.2.6"
92
92
  },
93
93
  keywords: [
94
94
  "diff",
@@ -339,7 +339,13 @@ function isDiffLine(value) {
339
339
  return isRecord(value) && isOneOf(value.type, DIFF_LINE_TYPES) && isNullableNumber(value.oldLine) && isNullableNumber(value.newLine) && isString(value.content);
340
340
  }
341
341
  function isComment(value) {
342
- return isRecord(value) && isString(value.id) && isString(value.filePath) && isNumber(value.startLine) && isNumber(value.endLine) && isOneOf(value.side, SIDES) && isString(value.body) && isString(value.originalSnippet) && isString(value.createdAt);
342
+ if (!isRecord(value)) {
343
+ return false;
344
+ }
345
+ if (value.kind === "general") {
346
+ return isString(value.id) && isString(value.body) && isString(value.createdAt);
347
+ }
348
+ 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);
343
349
  }
344
350
  function isResolvedComment(value) {
345
351
  return isRecord(value) && isString(value.commentId) && value.status === "resolved" && isOptionalString(value.summary) && isString(value.resolvedAt);
@@ -892,6 +898,17 @@ function summarizeDiffFiles(files) {
892
898
  );
893
899
  }
894
900
 
901
+ // src/shared/file-order.ts
902
+ function compareFilePaths(leftPath, rightPath, leftTieBreaker = "", rightTieBreaker = "") {
903
+ return leftPath.localeCompare(rightPath, void 0, { sensitivity: "base" }) || leftPath.localeCompare(rightPath) || leftTieBreaker.localeCompare(rightTieBreaker, void 0, { sensitivity: "base" }) || leftTieBreaker.localeCompare(rightTieBreaker);
904
+ }
905
+ function compareDiffFiles(left, right) {
906
+ return compareFilePaths(left.path, right.path, left.oldPath ?? "", right.oldPath ?? "");
907
+ }
908
+ function sortDiffFiles(files) {
909
+ return files.toSorted(compareDiffFiles);
910
+ }
911
+
895
912
  // src/cli/git.ts
896
913
  var DIFF_ARGS = ["diff", "--no-color", "--find-renames", "--find-copies"];
897
914
  async function git(args, cwd = process.cwd()) {
@@ -923,7 +940,7 @@ function buildPayload({
923
940
  fallbackReason,
924
941
  commitDiffs
925
942
  }) {
926
- const files = parseUnifiedDiff(rawDiff);
943
+ const files = sortDiffFiles(parseUnifiedDiff(rawDiff));
927
944
  return {
928
945
  base,
929
946
  branch,
@@ -1030,7 +1047,7 @@ async function captureCommitDiffs(baseSha, comparisonRef, repoRoot) {
1030
1047
  const commitDiffs = [];
1031
1048
  for (const commit of commits) {
1032
1049
  const rawDiff = await git([...DIFF_ARGS, `${commit.sha}^`, commit.sha, "--"], repoRoot);
1033
- const files = parseUnifiedDiff(rawDiff);
1050
+ const files = sortDiffFiles(parseUnifiedDiff(rawDiff));
1034
1051
  commitDiffs.push({
1035
1052
  commit,
1036
1053
  stats: summarizeDiffFiles(files),
@@ -1662,11 +1679,28 @@ import path6 from "path";
1662
1679
  import { ulid } from "ulid";
1663
1680
 
1664
1681
  // src/shared/comments.ts
1682
+ function isLineComment(comment) {
1683
+ return comment.kind === void 0 || comment.kind === "line";
1684
+ }
1665
1685
  function compareCommentsByLocation(a, b) {
1686
+ const aIsLine = isLineComment(a);
1687
+ const bIsLine = isLineComment(b);
1688
+ if (!aIsLine || !bIsLine) {
1689
+ if (aIsLine !== bIsLine) {
1690
+ return aIsLine ? 1 : -1;
1691
+ }
1692
+ return a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id);
1693
+ }
1666
1694
  return a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine || a.endLine - b.endLine || a.side.localeCompare(b.side);
1667
1695
  }
1668
1696
  function countCommentFiles(comments) {
1669
- return new Set(comments.map((comment) => comment.filePath)).size;
1697
+ const filePaths = /* @__PURE__ */ new Set();
1698
+ for (const comment of comments) {
1699
+ if (isLineComment(comment)) {
1700
+ filePaths.add(comment.filePath);
1701
+ }
1702
+ }
1703
+ return filePaths.size;
1670
1704
  }
1671
1705
  function formatLineRange(range, options = {}) {
1672
1706
  const startLine = Math.min(range.startLine, range.endLine);
@@ -1757,9 +1791,11 @@ function languageForSnippet(filePath, snippet) {
1757
1791
  }
1758
1792
  function serializeFeedbackMarkdown(bundle) {
1759
1793
  const comments = bundle.comments.toSorted(compareCommentsByLocation);
1794
+ const generalComments = comments.filter((comment) => !isLineComment(comment));
1795
+ const lineComments = comments.filter(isLineComment);
1760
1796
  const commentsByFile = /* @__PURE__ */ new Map();
1761
1797
  const files = [];
1762
- for (const comment of comments) {
1798
+ for (const comment of lineComments) {
1763
1799
  const fileComments = commentsByFile.get(comment.filePath);
1764
1800
  if (fileComments) {
1765
1801
  fileComments.push(comment);
@@ -1777,6 +1813,12 @@ function serializeFeedbackMarkdown(bundle) {
1777
1813
  `Files: ${files.length} Comments: ${comments.length}`,
1778
1814
  ""
1779
1815
  ];
1816
+ if (generalComments.length > 0) {
1817
+ lines.push("## General comments", "");
1818
+ for (const comment of generalComments) {
1819
+ lines.push(`### ${comment.id}`, comment.body.trim(), "");
1820
+ }
1821
+ }
1780
1822
  for (const filePath of files) {
1781
1823
  lines.push(`## ${filePath}`, "");
1782
1824
  for (const comment of commentsByFile.get(filePath) ?? []) {