@mrsf/cli 0.5.0 → 0.6.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/dist/index.cjs CHANGED
@@ -31,20 +31,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  ));
32
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
33
 
34
- // src/lib/parser.ts
35
- var parser_exports = {};
36
- __export(parser_exports, {
37
- parseSidecar: () => parseSidecar,
38
- parseSidecarContent: () => parseSidecarContent,
39
- parseSidecarContentLenient: () => parseSidecarContentLenient,
40
- parseSidecarLenient: () => parseSidecarLenient,
41
- readDocumentLines: () => readDocumentLines
42
- });
43
- async function parseSidecar(filePath) {
44
- const abs = import_node_path3.default.resolve(filePath);
45
- const content = await (0, import_promises2.readFile)(abs, "utf-8");
46
- return parseSidecarContent(content, abs);
47
- }
34
+ // src/lib/serialize.ts
48
35
  function parseSidecarContent(content, filenameHint) {
49
36
  const trimmed = content.trim();
50
37
  let parsed;
@@ -67,16 +54,6 @@ function parseSidecarContent(content, filenameHint) {
67
54
  }
68
55
  return parsed;
69
56
  }
70
- async function parseSidecarLenient(filePath) {
71
- const abs = import_node_path3.default.resolve(filePath);
72
- let content;
73
- try {
74
- content = await (0, import_promises2.readFile)(abs, "utf-8");
75
- } catch (e) {
76
- return { doc: null, error: `Cannot read file: ${e.message}` };
77
- }
78
- return parseSidecarContentLenient(content, abs);
79
- }
80
57
  function parseSidecarContentLenient(content, filenameHint) {
81
58
  const trimmed = content.trim();
82
59
  if (!trimmed) {
@@ -163,18 +140,59 @@ function salvageYaml(content) {
163
140
  partialComments: salvaged.length > 0 ? salvaged : void 0
164
141
  };
165
142
  }
143
+ function toYaml(doc) {
144
+ const yamlDoc = new import_yaml.Document(doc);
145
+ return yamlDoc.toString({ lineWidth: 0 });
146
+ }
147
+ function toJson(doc) {
148
+ return JSON.stringify(doc, null, 2) + "\n";
149
+ }
150
+ var import_js_yaml2, import_yaml;
151
+ var init_serialize = __esm({
152
+ "src/lib/serialize.ts"() {
153
+ "use strict";
154
+ import_js_yaml2 = __toESM(require("js-yaml"), 1);
155
+ import_yaml = require("yaml");
156
+ }
157
+ });
158
+
159
+ // src/lib/parser.ts
160
+ var parser_exports = {};
161
+ __export(parser_exports, {
162
+ parseSidecar: () => parseSidecar,
163
+ parseSidecarContent: () => parseSidecarContent,
164
+ parseSidecarContentLenient: () => parseSidecarContentLenient,
165
+ parseSidecarLenient: () => parseSidecarLenient,
166
+ readDocumentLines: () => readDocumentLines
167
+ });
168
+ async function parseSidecar(filePath) {
169
+ const abs = import_node_path3.default.resolve(filePath);
170
+ const content = await (0, import_promises2.readFile)(abs, "utf-8");
171
+ return parseSidecarContent(content, abs);
172
+ }
173
+ async function parseSidecarLenient(filePath) {
174
+ const abs = import_node_path3.default.resolve(filePath);
175
+ let content;
176
+ try {
177
+ content = await (0, import_promises2.readFile)(abs, "utf-8");
178
+ } catch (e) {
179
+ return { doc: null, error: `Cannot read file: ${e.message}` };
180
+ }
181
+ return parseSidecarContentLenient(content, abs);
182
+ }
166
183
  async function readDocumentLines(filePath) {
167
184
  const content = await (0, import_promises2.readFile)(import_node_path3.default.resolve(filePath), "utf-8");
168
185
  const lines = content.replace(/\r\n?/g, "\n").split("\n");
169
186
  return ["", ...lines];
170
187
  }
171
- var import_promises2, import_node_path3, import_js_yaml2;
188
+ var import_promises2, import_node_path3;
172
189
  var init_parser = __esm({
173
190
  "src/lib/parser.ts"() {
174
191
  "use strict";
175
192
  import_promises2 = require("node:fs/promises");
176
193
  import_node_path3 = __toESM(require("node:path"), 1);
177
- import_js_yaml2 = __toESM(require("js-yaml"), 1);
194
+ init_serialize();
195
+ init_serialize();
178
196
  }
179
197
  });
180
198
 
@@ -358,7 +376,9 @@ var import_promises3 = require("node:fs/promises");
358
376
  var import_node_fs2 = require("node:fs");
359
377
  var import_node_crypto = require("node:crypto");
360
378
  var import_node_path4 = __toESM(require("node:path"), 1);
361
- var import_yaml = require("yaml");
379
+ var import_yaml2 = require("yaml");
380
+ init_serialize();
381
+ init_serialize();
362
382
  var writeQueue = /* @__PURE__ */ new Map();
363
383
  function enqueueWrite(abs, fn) {
364
384
  const prev = writeQueue.get(abs) ?? Promise.resolve();
@@ -558,13 +578,6 @@ function deepEqual(a, b) {
558
578
  if (typeof a === "number" && typeof b === "number") return a === b;
559
579
  return false;
560
580
  }
561
- function toYaml(doc) {
562
- const yamlDoc = new import_yaml.Document(doc);
563
- return yamlDoc.toString({ lineWidth: 0 });
564
- }
565
- function toJson(doc) {
566
- return JSON.stringify(doc, null, 2) + "\n";
567
- }
568
581
  async function writeSidecar(filePath, doc) {
569
582
  const abs = import_node_path4.default.resolve(filePath);
570
583
  return enqueueWrite(abs, () => writeSidecarInternal(abs, doc));
@@ -593,14 +606,14 @@ async function writeSidecarInternal(abs, doc) {
593
606
  }
594
607
  let tokens;
595
608
  try {
596
- tokens = [...new import_yaml.Parser().parse(raw)];
609
+ tokens = [...new import_yaml2.Parser().parse(raw)];
597
610
  } catch {
598
611
  await atomicWriteFile(abs, toYaml(doc));
599
612
  return;
600
613
  }
601
614
  let currentDoc = null;
602
615
  try {
603
- currentDoc = (0, import_yaml.parse)(raw);
616
+ currentDoc = (0, import_yaml2.parse)(raw);
604
617
  } catch {
605
618
  }
606
619
  if (currentDoc && !Array.isArray(currentDoc.comments)) {
@@ -687,7 +700,7 @@ async function writeSidecarInternal(abs, doc) {
687
700
  }
688
701
  }
689
702
  }
690
- const result = tokens.map((t) => import_yaml.CST.stringify(t)).join("");
703
+ const result = tokens.map((t) => import_yaml2.CST.stringify(t)).join("");
691
704
  await atomicWriteFile(abs, result);
692
705
  }
693
706
 
@@ -1349,11 +1362,13 @@ async function detectRenames(fromCommit, toCommit, repoRoot) {
1349
1362
  // src/lib/reanchor-core.ts
1350
1363
  var HIGH_THRESHOLD = 0.8;
1351
1364
  var DEFAULT_THRESHOLD = 0.6;
1365
+ var DEFAULT_PROXIMITY_WINDOW = 5;
1352
1366
  function toReanchorLines(documentText) {
1353
1367
  return ["", ...documentText.replace(/\r\n/g, "\n").split("\n")];
1354
1368
  }
1355
1369
  function reanchorComment(comment, documentLines, opts = {}) {
1356
1370
  const threshold = opts.threshold ?? DEFAULT_THRESHOLD;
1371
+ const proximityWindow = opts.proximityWindow ?? DEFAULT_PROXIMITY_WINDOW;
1357
1372
  const commentId = comment.id;
1358
1373
  const selectedText = comment.selected_text;
1359
1374
  if (!selectedText && comment.line == null) {
@@ -1404,30 +1419,46 @@ function reanchorComment(comment, documentLines, opts = {}) {
1404
1419
  }
1405
1420
  if (selectedText) {
1406
1421
  const exactCandidates = exactMatch(documentLines, selectedText);
1422
+ let chosen;
1423
+ let chosenReason = "";
1407
1424
  if (exactCandidates.length === 1) {
1408
- const candidate = exactCandidates[0];
1409
- return {
1410
- commentId,
1411
- status: "anchored",
1412
- score: 1,
1413
- newLine: candidate.line,
1414
- newEndLine: candidate.endLine,
1415
- newStartColumn: candidate.startColumn,
1416
- newEndColumn: candidate.endColumn,
1417
- reason: "Exact text match (unique)."
1418
- };
1419
- }
1420
- if (exactCandidates.length > 1 && comment.line != null) {
1421
- const best = closestToLine(exactCandidates, comment.line);
1425
+ chosen = exactCandidates[0];
1426
+ chosenReason = "Exact text match (unique).";
1427
+ } else if (exactCandidates.length > 1 && comment.line != null) {
1428
+ chosen = closestToLine(exactCandidates, comment.line);
1429
+ chosenReason = `Exact text match (${exactCandidates.length} occurrences; chose nearest to original line ${comment.line}).`;
1430
+ }
1431
+ if (chosen) {
1432
+ if (isImplausibleExactRelocation(comment, chosen, documentLines, proximityWindow)) {
1433
+ const textAtOrigin = extractText(
1434
+ documentLines,
1435
+ comment.line,
1436
+ comment.end_line,
1437
+ comment.start_column,
1438
+ comment.end_column
1439
+ );
1440
+ return {
1441
+ commentId,
1442
+ status: "fuzzy",
1443
+ score: 0.5,
1444
+ newLine: comment.line,
1445
+ newEndLine: comment.end_line,
1446
+ newStartColumn: comment.start_column,
1447
+ newEndColumn: comment.end_column,
1448
+ anchoredText: textAtOrigin ?? void 0,
1449
+ previousSelectedText: selectedText,
1450
+ reason: `Lone exact match at line ${chosen.line} is beyond the proximity window (\xB1${proximityWindow}) of original line ${comment.line} and the text at the original position changed; kept at original position, needs re-anchoring.`
1451
+ };
1452
+ }
1422
1453
  return {
1423
1454
  commentId,
1424
1455
  status: "anchored",
1425
1456
  score: 1,
1426
- newLine: best.line,
1427
- newEndLine: best.endLine,
1428
- newStartColumn: best.startColumn,
1429
- newEndColumn: best.endColumn,
1430
- reason: `Exact text match (${exactCandidates.length} occurrences; chose nearest to original line ${comment.line}).`
1457
+ newLine: chosen.line,
1458
+ newEndLine: chosen.endLine,
1459
+ newStartColumn: chosen.startColumn,
1460
+ newEndColumn: chosen.endColumn,
1461
+ reason: chosenReason
1431
1462
  };
1432
1463
  }
1433
1464
  const normCandidates = normalizedMatch(documentLines, selectedText);
@@ -1685,6 +1716,20 @@ function closestToLine(candidates, targetLine) {
1685
1716
  (best, candidate) => Math.abs(candidate.line - targetLine) < Math.abs(best.line - targetLine) ? candidate : best
1686
1717
  );
1687
1718
  }
1719
+ function isImplausibleExactRelocation(comment, candidate, lines, proximityWindow) {
1720
+ if (comment.line == null) return false;
1721
+ if (comment.line <= 0 || comment.line >= lines.length) return false;
1722
+ if (Math.abs(candidate.line - comment.line) <= proximityWindow) return false;
1723
+ const textAtOrigin = extractText(
1724
+ lines,
1725
+ comment.line,
1726
+ comment.end_line,
1727
+ comment.start_column,
1728
+ comment.end_column
1729
+ );
1730
+ if (textAtOrigin === comment.selected_text) return false;
1731
+ return true;
1732
+ }
1688
1733
  function getLineShift2(diffHunks, line) {
1689
1734
  let shift = 0;
1690
1735
  let modified = false;
@@ -1708,6 +1753,7 @@ var import_node_path6 = __toESM(require("node:path"), 1);
1708
1753
  async function reanchorDocument(doc, documentLines, opts = {}) {
1709
1754
  const results = [];
1710
1755
  const threshold = opts.threshold ?? DEFAULT_THRESHOLD;
1756
+ const proximityWindow = opts.proximityWindow;
1711
1757
  let diffHunks;
1712
1758
  let commitIsStale = false;
1713
1759
  if (!opts.noGit && await isGitAvailable()) {
@@ -1724,19 +1770,20 @@ async function reanchorDocument(doc, documentLines, opts = {}) {
1724
1770
  const result = reanchorComment(comment, documentLines, {
1725
1771
  diffHunks: hunks,
1726
1772
  threshold,
1727
- commitIsStale: true
1773
+ commitIsStale: true,
1774
+ proximityWindow
1728
1775
  });
1729
1776
  results.push(result);
1730
1777
  continue;
1731
1778
  }
1732
1779
  results.push(
1733
- reanchorComment(comment, documentLines, { threshold, commitIsStale: false })
1780
+ reanchorComment(comment, documentLines, { threshold, commitIsStale: false, proximityWindow })
1734
1781
  );
1735
1782
  }
1736
1783
  return results;
1737
1784
  }
1738
1785
  }
1739
- return reanchorDocumentLines(doc, documentLines, { threshold });
1786
+ return reanchorDocumentLines(doc, documentLines, { threshold, proximityWindow });
1740
1787
  }
1741
1788
  async function reanchorFile(sidecarPath, opts = {}) {
1742
1789
  const doc = await parseSidecar(sidecarPath);