@levnikolaevich/hex-line-mcp 1.27.1 → 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 +3 -3
- package/dist/server.mjs +82 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ Advanced / occasional:
|
|
|
42
42
|
| `changes` | Compare file against git ref, shows added/removed/modified symbols | AST-level semantic diff with risk/provenance preview |
|
|
43
43
|
| `bulk_replace` | Search-and-replace across multiple files inside an explicit root path | Compact summary (default) or capped diffs via `format`, dry_run, max_files |
|
|
44
44
|
|
|
45
|
-
### Hooks (SessionStart + PreToolUse + PostToolUse)
|
|
45
|
+
### Hooks (SessionStart + PreToolUse + PostToolUse + ConfigChange + PermissionDenied)
|
|
46
46
|
|
|
47
47
|
| Event | Trigger | Action |
|
|
48
48
|
|-------|---------|--------|
|
|
@@ -406,7 +406,7 @@ Logs when Claude denies a tool call after the hook delivered a redirect hint. Re
|
|
|
406
406
|
```
|
|
407
407
|
hex-line-mcp/
|
|
408
408
|
server.mjs MCP server (stdio transport, 9 tools)
|
|
409
|
-
hook.mjs Unified hook (PreToolUse + PostToolUse +
|
|
409
|
+
hook.mjs Unified hook (SessionStart + PreToolUse + PostToolUse + ConfigChange + PermissionDenied)
|
|
410
410
|
package.json
|
|
411
411
|
lib/
|
|
412
412
|
hook-policy.mjs Shared hook policy: redirects, thresholds, danger patterns
|
|
@@ -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,
|
|
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
|
|
1662
|
-
const
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
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
|
|
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
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
|
|
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.
|
|
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.
|
|
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.",
|