@levnikolaevich/hex-line-mcp 1.28.0 → 1.29.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
@@ -462,7 +462,7 @@ FNV-1a accumulator over all line hashes in the range (little-endian byte feed).
462
462
  <details>
463
463
  <summary><b>Does it work without Claude Code?</b></summary>
464
464
 
465
- Yes. hex-line-mcp is a standard MCP server (stdio transport). It works with any MCP-compatible client -- Claude Code, Gemini CLI, Codex CLI, or custom integrations. Hook installation is Claude-specific; Gemini/Codex use MCP Tool Preferences guidance instead.
465
+ Yes. hex-line-mcp is a standard MCP server (stdio transport). It works with any MCP-compatible client -- Claude Code, Codex CLI, or custom integrations. Hook installation is Claude-specific; Codex uses MCP Tool Preferences guidance instead.
466
466
 
467
467
  </details>
468
468
 
package/dist/server.mjs CHANGED
@@ -1658,17 +1658,78 @@ import { statSync as statSync6, writeFileSync } from "node:fs";
1658
1658
  import { diffLines as diffLines2 } from "diff";
1659
1659
 
1660
1660
  // lib/edit-resolution.mjs
1661
- function normalizeAnchoredEdits(edits) {
1662
- const anchored = [];
1663
- for (const edit of edits) {
1664
- if (edit.set_line || edit.replace_lines || edit.insert_after || edit.replace_between) {
1665
- anchored.push(edit);
1666
- continue;
1667
- }
1661
+ function badInput(message) {
1662
+ const err = new Error(`BAD_INPUT: ${message}`);
1663
+ err.code = "BAD_INPUT";
1664
+ err.recovery = "Use canonical edit shapes with required string fields: set_line.anchor/new_text, insert_after.anchor/text, replace_lines.start_anchor/end_anchor/range_checksum/new_text, or replace_between.start_anchor/end_anchor/new_text.";
1665
+ return err;
1666
+ }
1667
+ function requirePlainObject(value, path) {
1668
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1669
+ throw badInput(`${path} must be an object`);
1670
+ }
1671
+ }
1672
+ function requireString(value, path) {
1673
+ if (typeof value !== "string") {
1674
+ throw badInput(`${path} must be a string`);
1675
+ }
1676
+ }
1677
+ function requireOptionalString(value, path) {
1678
+ if (value !== void 0 && typeof value !== "string") {
1679
+ throw badInput(`${path} must be a string when provided`);
1680
+ }
1681
+ }
1682
+ function validateEditShape(edit) {
1683
+ requirePlainObject(edit, "edit");
1684
+ const kinds = ["set_line", "replace_lines", "insert_after", "replace_between"].filter((kind2) => edit[kind2] !== void 0);
1685
+ if (kinds.length !== 1) {
1668
1686
  if (edit.replace) {
1669
1687
  throw new Error("REPLACE_REMOVED: replace is no longer supported in edit_file. Use set_line/replace_lines for single edits, bulk_replace tool for rename/refactor.");
1670
1688
  }
1671
- throw new Error(`BAD_INPUT: unknown edit type: ${JSON.stringify(edit)}`);
1689
+ if (kinds.length === 0) throw badInput(`unknown edit type: ${JSON.stringify(edit)}`);
1690
+ throw badInput(`exactly one edit type is allowed, got ${kinds.join(", ")}`);
1691
+ }
1692
+ const kind = kinds[0];
1693
+ if (kind === "set_line") {
1694
+ requirePlainObject(edit.set_line, "set_line");
1695
+ requireString(edit.set_line.anchor, "set_line.anchor");
1696
+ requireString(edit.set_line.new_text, "set_line.new_text");
1697
+ return;
1698
+ }
1699
+ if (kind === "insert_after") {
1700
+ requirePlainObject(edit.insert_after, "insert_after");
1701
+ requireString(edit.insert_after.anchor, "insert_after.anchor");
1702
+ requireString(edit.insert_after.text, "insert_after.text");
1703
+ return;
1704
+ }
1705
+ if (kind === "replace_lines") {
1706
+ requirePlainObject(edit.replace_lines, "replace_lines");
1707
+ requireString(edit.replace_lines.start_anchor, "replace_lines.start_anchor");
1708
+ requireString(edit.replace_lines.end_anchor, "replace_lines.end_anchor");
1709
+ requireString(edit.replace_lines.range_checksum, "replace_lines.range_checksum");
1710
+ requireString(edit.replace_lines.new_text, "replace_lines.new_text");
1711
+ return;
1712
+ }
1713
+ requirePlainObject(edit.replace_between, "replace_between");
1714
+ requireString(edit.replace_between.start_anchor, "replace_between.start_anchor");
1715
+ requireString(edit.replace_between.end_anchor, "replace_between.end_anchor");
1716
+ requireString(edit.replace_between.new_text, "replace_between.new_text");
1717
+ requireOptionalString(edit.replace_between.range_checksum, "replace_between.range_checksum");
1718
+ if (edit.replace_between.boundary_mode !== void 0 && !["inclusive", "exclusive"].includes(edit.replace_between.boundary_mode)) {
1719
+ throw badInput('replace_between.boundary_mode must be "inclusive" or "exclusive"');
1720
+ }
1721
+ }
1722
+ function normalizeAnchoredEdits(edits) {
1723
+ if (!Array.isArray(edits)) {
1724
+ throw badInput("edits must be a non-empty array");
1725
+ }
1726
+ if (edits.length === 0) {
1727
+ throw badInput("edits must be a non-empty array");
1728
+ }
1729
+ const anchored = [];
1730
+ for (const edit of edits) {
1731
+ validateEditShape(edit);
1732
+ anchored.push(edit);
1672
1733
  }
1673
1734
  return anchored;
1674
1735
  }
@@ -1832,12 +1893,16 @@ var REASON = {
1832
1893
  };
1833
1894
 
1834
1895
  // lib/edit.mjs
1896
+ function asLine(value) {
1897
+ return typeof value === "string" ? value : String(value ?? "");
1898
+ }
1835
1899
  function restoreIndent(origLines, newLines) {
1836
1900
  if (!origLines.length || !newLines.length) return newLines;
1837
- const origIndent = origLines[0].match(/^\s*/)[0];
1838
- const newIndent = newLines[0].match(/^\s*/)[0];
1839
- if (origIndent === newIndent) return newLines;
1840
- return newLines.map((line) => {
1901
+ const safeNewLines = newLines.map(asLine);
1902
+ const origIndent = asLine(origLines[0]).match(/^\s*/)[0];
1903
+ const newIndent = safeNewLines[0].match(/^\s*/)[0];
1904
+ if (origIndent === newIndent) return safeNewLines;
1905
+ return safeNewLines.map((line) => {
1841
1906
  if (!line.trim()) return line;
1842
1907
  if (line.startsWith(newIndent)) return origIndent + line.slice(newIndent.length);
1843
1908
  return line;
@@ -2815,8 +2880,8 @@ Recovery: read_file path ranges=["${csStart}-${csEnd}"], then retry edit with fr
2815
2880
  let boundaryEchoSkipped = false;
2816
2881
  const insertEnd = sliceStart + newLines.length;
2817
2882
  if (newLines.length > 0 && insertEnd < lines.length) {
2818
- const lastNew = newLines[newLines.length - 1].trim();
2819
- const firstAfter = lines[insertEnd].trim();
2883
+ const lastNew = asLine(newLines[newLines.length - 1]).trim();
2884
+ const firstAfter = asLine(lines[insertEnd]).trim();
2820
2885
  if (lastNew && lastNew === firstAfter) {
2821
2886
  if (isAmbiguousDelimiter(firstAfter)) {
2822
2887
  boundaryEchoSkipped = true;
@@ -2834,8 +2899,8 @@ Recovery: read_file path ranges=["${csStart}-${csEnd}"], then retry edit with fr
2834
2899
  }
2835
2900
  }
2836
2901
  if (newLines.length > 0 && sliceStart > 0) {
2837
- const firstNew = newLines[0].trim();
2838
- const lineBefore = lines[sliceStart - 1].trim();
2902
+ const firstNew = asLine(newLines[0]).trim();
2903
+ const lineBefore = asLine(lines[sliceStart - 1]).trim();
2839
2904
  if (firstNew && firstNew === lineBefore) {
2840
2905
  if (isAmbiguousDelimiter(lineBefore)) {
2841
2906
  boundaryEchoSkipped = true;
@@ -4967,7 +5032,7 @@ function errorResult(code, message, recovery, { large = false, extra = null } =
4967
5032
  }
4968
5033
 
4969
5034
  // server.mjs
4970
- var version = true ? "1.28.0" : (await null).createRequire(import.meta.url)("./package.json").version;
5035
+ var version = true ? "1.29.0" : (await null).createRequire(import.meta.url)("./package.json").version;
4971
5036
  var STATUS_ENUM = z2.enum(STATUS_VALUES);
4972
5037
  var ERROR_SHAPE = z2.object({ code: z2.string(), message: z2.string(), recovery: z2.string() }).optional();
4973
5038
  var LINE_REPORT_KEYS = /* @__PURE__ */ new Set([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levnikolaevich/hex-line-mcp",
3
- "version": "1.28.0",
3
+ "version": "1.29.0",
4
4
  "mcpName": "io.github.levnikolaevich/hex-line-mcp",
5
5
  "type": "module",
6
6
  "description": "Hash-verified file editing MCP + token efficiency hook for AI coding agents. 9 tools: inspect_path, read, edit, write, grep, outline, verify, changes, bulk_replace.",