@levnikolaevich/hex-line-mcp 1.19.0 → 1.20.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/hook.mjs CHANGED
@@ -115,8 +115,8 @@ var REVERSE_TOOL_HINTS = {
115
115
  "mcp__hex-line__bulk_replace": "Edit (text rename/refactor across files inside an explicit root path)"
116
116
  };
117
117
  var TOOL_HINTS = {
118
- Read: "mcp__hex-line__read_file (not Read). For writing: write_file (no prior Read needed)",
119
- Edit: "mcp__hex-line__edit_file for revision-aware hash edits. Batch same-file hunks, carry base_revision, use replace_between for block rewrites",
118
+ Read: "mcp__hex-line__read_file (not Read). For structure-first: mcp__hex-line__outline then mcp__hex-line__read_file with ranges",
119
+ Edit: "mcp__hex-line__edit_file (not Edit). If you need hash anchors: mcp__hex-line__grep_search(output='content', edit_ready=true) first",
120
120
  Write: "mcp__hex-line__write_file (not Write). No prior Read needed",
121
121
  Grep: "mcp__hex-line__grep_search (not Grep). Params: output, literal, context_before, context_after, multiline",
122
122
  Glob: "mcp__hex-line__inspect_path (not Glob). Use pattern=... with an explicit path for project file discovery and name/path globbing",
@@ -133,7 +133,7 @@ var TOOL_HINTS = {
133
133
  changes: "mcp__hex-line__changes (git diff with change symbols)",
134
134
  bulk: "mcp__hex-line__bulk_replace with path=<project root> (multi-file search-replace)"
135
135
  };
136
- var DEFERRED_HINT = "If schemas not loaded: ToolSearch('+hex-line read edit')";
136
+ var DEFERRED_HINT = "Run ToolSearch('+hex-line read edit') first if hex-line tools show InputValidationError.";
137
137
  var BASH_REDIRECTS = [
138
138
  { regex: /^(cat|type)\b/i, key: "cat", kind: "reader" },
139
139
  { regex: /^head\b/i, key: "head", kind: "reader" },
@@ -443,8 +443,8 @@ function handlePreToolUse(data) {
443
443
  const isPlanSafe = targetPath.includes("/.hex-skills/") || targetPath.includes(".hex-skills/") || targetPath.includes("/.claude/") || targetPath.includes(".claude/");
444
444
  if (!isPlanSafe) {
445
445
  block(
446
- "PLAN_MODE: hex-line write tools are blocked during planning.",
447
- "Use read-only tools: read_file, grep_search, outline, verify, inspect_path, changes."
446
+ "PLAN_MODE: You are in planning mode. Write your plan to the plan file, then call ExitPlanMode to get approval before making changes.",
447
+ "Do NOT try other write tools (Write, Edit, Bash). Only the plan file can be edited in plan mode. Finish planning first."
448
448
  );
449
449
  }
450
450
  }
@@ -463,13 +463,14 @@ function handlePreToolUse(data) {
463
463
  }
464
464
  if (toolName === "Read") {
465
465
  const ext = filePath ? extOf(filePath) : "";
466
- const rangeHint = isPartialRead(toolInput) ? " Preserve the same offset/limit or ranges in read_file." : "";
467
- const outlineHint = filePath && OUTLINEABLE_EXT.has(ext) ? `Use mcp__hex-line__outline(path="${filePath}") for structure, then mcp__hex-line__read_file(path="${filePath}") with ranges to read only what you need.${rangeHint}` : filePath ? `Use mcp__hex-line__read_file(path="${filePath}") with ranges or offset/limit.${rangeHint}` : "Use mcp__hex-line__inspect_path or mcp__hex-line__read_file";
468
- redirect(outlineHint, "Use hex-line for project text-file reads to keep hashes, revision metadata, and graph hints in one flow.\n" + DEFERRED_HINT);
466
+ const rangeHint = isPartialRead(toolInput) ? " Preserve the same offset/limit or ranges." : "";
467
+ const outlineTip = filePath && OUTLINEABLE_EXT.has(ext) ? ` For structure-first discovery: mcp__hex-line__outline then mcp__hex-line__read_file with ranges.` : "";
468
+ const target = filePath ? `Use mcp__hex-line__read_file(path="${filePath}").${rangeHint}${outlineTip}` : "Use mcp__hex-line__read_file or mcp__hex-line__inspect_path.";
469
+ redirect(target, DEFERRED_HINT);
469
470
  }
470
471
  if (toolName === "Edit") {
471
- const target = filePath ? `Use mcp__hex-line__grep_search or mcp__hex-line__read_file, then mcp__hex-line__edit_file with path="${filePath}"` : "Use mcp__hex-line__grep_search or mcp__hex-line__read_file, then mcp__hex-line__edit_file";
472
- redirect(target, "Use hash-verified edits for project text files. Locate anchors/checksums first, then call edit_file once with batched edits.\n" + DEFERRED_HINT);
472
+ const target = filePath ? `Use mcp__hex-line__edit_file(path="${filePath}"). If you need hash anchors first: mcp__hex-line__grep_search(output="content", edit_ready=true).` : 'Use mcp__hex-line__edit_file. If you need hash anchors first: mcp__hex-line__grep_search(output="content", edit_ready=true).';
473
+ redirect(target, "Hash-verified edits for project text files.\n" + DEFERRED_HINT);
473
474
  }
474
475
  if (toolName === "Write") {
475
476
  const pathNote = filePath ? ` with path="${filePath}"` : "";
@@ -591,7 +592,7 @@ function handleSessionStart() {
591
592
  } catch {
592
593
  }
593
594
  }
594
- const msg = styleActive ? "Hex-line MCP available. Output style active.\n<hex-line_instructions>\n <deferred_loading>If hex-line schemas not loaded, run: ToolSearch('+hex-line read edit')</deferred_loading>\n <note>Follow the active hex-line output style for primary tool choices.</note>\n <exceptions>Built-in tools stay OK for images, PDFs, notebooks, and text paths outside the current project root.</exceptions>\n</hex-line_instructions>" : "Hex-line MCP available.\n<hex-line_instructions>\n <deferred_loading>If hex-line schemas not loaded, run: ToolSearch('+hex-line read edit')</deferred_loading>\n <exploration>\n <rule>Use outline for structure (code + markdown), not Read. ~10-20 lines vs hundreds.</rule>\n <rule>Use read_file with offset/limit or ranges for targeted reads.</rule>\n <rule>Use grep_search before editing to get hash anchors.</rule>\n </exploration>\n <editing>\n <path name='surgical'>grep_search \u2192 edit_file (fastest: hash-verified, no full read needed)</path>\n <path name='exploratory'>outline \u2192 read_file (ranges) \u2192 edit_file with base_revision</path>\n <path name='multi-file'>bulk_replace(path=&quot;&lt;project root&gt;&quot;) for text rename/refactor across files</path>\n </editing>\n <tips>\n <tip>Auto-fill path from the active file or project root. Read-only tools may inspect explicit temp-file paths outside the repo. Mutating tools stay project-scoped unless you intentionally pass allow_external=true.</tip>\n <tip>Never invent range_checksum. Copy it from fresh read_file or grep_search blocks.</tip>\n <tip>Prefer set_line or insert_after for small local changes and replace_between for larger bounded rewrites.</tip>\n <tip>Carry revision from read_file into base_revision on edit_file.</tip>\n <tip>If edit returns CONFLICT, call verify \u2014 only reread when STALE.</tip>\n <tip>Avoid large first-pass edit batches. Start with 1-2 hunks, then continue from the returned revision.</tip>\n <tip>Use write_file for new files (no prior Read needed).</tip>\n <tip>Use inspect_path(pattern=...) for project file discovery and name/path globbing.</tip>\n </tips>\n <exceptions>Built-in tools stay OK for images, PDFs, notebooks, and text paths outside the current project root.</exceptions>\n</hex-line_instructions>";
595
+ const msg = styleActive ? "Hex-line MCP available. Output style active.\n<hex-line_instructions>\n <deferred_loading>If hex-line schemas not loaded, run: ToolSearch('+hex-line read edit')</deferred_loading>\n <tool_map>Read\u2192mcp__hex-line__read_file, Edit\u2192mcp__hex-line__edit_file, Write\u2192mcp__hex-line__write_file, Grep\u2192mcp__hex-line__grep_search, Glob\u2192mcp__hex-line__inspect_path</tool_map>\n <note>Follow the active hex-line output style for primary tool choices.</note>\n <exceptions>Built-in tools stay OK for images, PDFs, notebooks, and text paths outside the current project root.</exceptions>\n</hex-line_instructions>" : "Hex-line MCP available.\n<hex-line_instructions>\n <deferred_loading>If hex-line schemas not loaded, run: ToolSearch('+hex-line read edit')</deferred_loading>\n <exploration>\n <rule>Use outline for structure (code + markdown), not Read. ~10-20 lines vs hundreds.</rule>\n <rule>Use read_file with offset/limit or ranges for targeted reads.</rule>\n <rule>Use grep_search before editing to get hash anchors.</rule>\n </exploration>\n <editing>\n <path name='surgical'>grep_search \u2192 edit_file (fastest: hash-verified, no full read needed)</path>\n <path name='exploratory'>outline \u2192 read_file (ranges) \u2192 edit_file with base_revision</path>\n <path name='multi-file'>bulk_replace(path=&quot;&lt;project root&gt;&quot;) for text rename/refactor across files</path>\n </editing>\n <tips>\n <tip>Auto-fill path from the active file or project root. Read-only tools may inspect explicit temp-file paths outside the repo. Mutating tools stay project-scoped unless you intentionally pass allow_external=true.</tip>\n <tip>Never invent range_checksum. Copy it from fresh read_file or grep_search blocks.</tip>\n <tip>Prefer set_line or insert_after for small local changes and replace_between for larger bounded rewrites.</tip>\n <tip>Carry revision from read_file into base_revision on edit_file.</tip>\n <tip>If edit returns CONFLICT, call verify \u2014 only reread when STALE.</tip>\n <tip>Avoid large first-pass edit batches. Start with 1-2 hunks, then continue from the returned revision.</tip>\n <tip>Use write_file for new files (no prior Read needed).</tip>\n <tip>Use inspect_path(pattern=...) for project file discovery and name/path globbing.</tip>\n </tips>\n <exceptions>Built-in tools stay OK for images, PDFs, notebooks, and text paths outside the current project root.</exceptions>\n</hex-line_instructions>";
595
596
  safeExit(1, JSON.stringify({ systemMessage: msg }), 0);
596
597
  }
597
598
  function handleConfigChange(data) {
package/dist/server.mjs CHANGED
@@ -2579,6 +2579,25 @@ function applyReplaceBetweenEdit(edit, ctx) {
2579
2579
  return null;
2580
2580
  }
2581
2581
  replaceLogicalRange(lines, lineEndings, sliceStart, sliceStart + removeCount - 1, newLines, defaultEol);
2582
+ const insertEnd = sliceStart + newLines.length;
2583
+ if (newLines.length > 0 && insertEnd < lines.length) {
2584
+ const lastNew = newLines[newLines.length - 1].trim();
2585
+ const firstAfter = lines[insertEnd].trim();
2586
+ if (lastNew && lastNew === firstAfter) {
2587
+ lines.splice(insertEnd, 1);
2588
+ lineEndings.splice(insertEnd, 1);
2589
+ ctx.corrections.push({ type: "tail_echo", line: insertEnd + 1, content: firstAfter });
2590
+ }
2591
+ }
2592
+ if (newLines.length > 0 && sliceStart > 0) {
2593
+ const firstNew = newLines[0].trim();
2594
+ const lineBefore = lines[sliceStart - 1].trim();
2595
+ if (firstNew && firstNew === lineBefore) {
2596
+ lines.splice(sliceStart - 1, 1);
2597
+ lineEndings.splice(sliceStart - 1, 1);
2598
+ ctx.corrections.push({ type: "head_echo", line: sliceStart, content: lineBefore });
2599
+ }
2600
+ }
2582
2601
  return null;
2583
2602
  }
2584
2603
  function editFile(filePath, edits, opts = {}) {
@@ -2721,7 +2740,8 @@ ${snip.text}`;
2721
2740
  locateOrConflict,
2722
2741
  opts,
2723
2742
  origLines,
2724
- staleRevision
2743
+ staleRevision,
2744
+ corrections: []
2725
2745
  };
2726
2746
  const batchConflicts = collectBatchConflicts({
2727
2747
  edits: anchored,
@@ -2905,7 +2925,7 @@ ${sections.join("\n")}`;
2905
2925
  if (displayDiff) payloadKinds.push("diff");
2906
2926
  const semanticFactCount = semanticImpacts.reduce((sum, impact) => sum + (impact.facts?.length || 0), 0);
2907
2927
  const summaryLines = [
2908
- `summary: lines_changed=${changedSpan} diff_entries=${diffEntryCount} lines_after=${lines.length}`,
2928
+ `summary: lines_changed=${changedSpan} diff_entries=${diffEntryCount} lines_after=${lines.length}${editContext.corrections.length > 0 ? ` boundary_echo_stripped=${editContext.corrections.length}` : ``}`,
2909
2929
  `payload_sections: ${payloadSections(payloadKinds)}`,
2910
2930
  `graph_enrichment: ${graphEnrichment}`,
2911
2931
  `semantic_impact_count: ${semanticImpacts.length}`,
@@ -4013,8 +4033,8 @@ var NODE_COMMAND = process.execPath.replace(/\\/g, "/");
4013
4033
  var HOOK_COMMAND = `"${NODE_COMMAND}" "${STABLE_HOOK_PATH}"`;
4014
4034
  var __filename = fileURLToPath2(import.meta.url);
4015
4035
  var __dirname = dirname4(__filename);
4016
- var SOURCE_HOOK = resolve7(__dirname, "..", "hook.mjs");
4017
4036
  var DIST_HOOK = resolve7(__dirname, "hook.mjs");
4037
+ var BUILT_HOOK = resolve7(__dirname, "..", "dist", "hook.mjs");
4018
4038
  var SOURCE_STYLE = resolve7(__dirname, "..", "output-style.md");
4019
4039
  var INSTALLED_STYLE = resolve7(homedir(), ".claude", "output-styles", "hex-line.md");
4020
4040
  var HOOK_SIGNATURE = "hex-line";
@@ -4126,8 +4146,8 @@ function syncOutputStyle() {
4126
4146
  return true;
4127
4147
  }
4128
4148
  function autoSync() {
4129
- const hookSource = existsSync6(DIST_HOOK) ? DIST_HOOK : SOURCE_HOOK;
4130
- if (!existsSync6(hookSource)) return;
4149
+ const hookSource = existsSync6(DIST_HOOK) ? DIST_HOOK : existsSync6(BUILT_HOOK) ? BUILT_HOOK : null;
4150
+ if (!hookSource) return;
4131
4151
  const changes = [];
4132
4152
  const srcHook = safeRead(hookSource);
4133
4153
  const dstHook = safeRead(STABLE_HOOK_PATH);
@@ -4763,7 +4783,7 @@ function errorResult(code, message, recovery, { large = false, extra = null } =
4763
4783
  }
4764
4784
 
4765
4785
  // server.mjs
4766
- var version = true ? "1.19.0" : (await null).createRequire(import.meta.url)("./package.json").version;
4786
+ var version = true ? "1.20.0" : (await null).createRequire(import.meta.url)("./package.json").version;
4767
4787
  var STATUS_ENUM = z2.enum(["OK", "ERROR", "AUTO_REBASED", "CONFLICT", "STALE", "INVALID", "NO_CHANGES", "CHANGED", "UNSUPPORTED"]);
4768
4788
  var ERROR_SHAPE = z2.object({ code: z2.string(), message: z2.string(), recovery: z2.string() }).optional();
4769
4789
  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.19.0",
3
+ "version": "1.20.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.",
@@ -23,7 +23,8 @@
23
23
  "scenarios": "node scenarios/index.mjs",
24
24
  "scenarios:diagnostic": "node scenarios/index.mjs --diagnostics",
25
25
  "scenarios:diagnostic:graph": "node scenarios/index.mjs --diagnostics --with-graph",
26
- "check": "node --check server.mjs && node --check hook.mjs"
26
+ "check": "node --check server.mjs && node --check hook.mjs",
27
+ "prepare": "npm run build"
27
28
  },
28
29
  "_dep_notes": {
29
30
  "web-tree-sitter": "Tracks the latest stable runtime together with the repo-owned first-party grammar WASM bundle. Keep runtime upgrades coupled to grammar load/parse validation across Windows, macOS, and Linux.",
@@ -39,7 +40,7 @@
39
40
  "zod": "^4.3.6"
40
41
  },
41
42
  "optionalDependencies": {
42
- "better-sqlite3": "^12.8.0"
43
+ "better-sqlite3": "^12.9.0"
43
44
  },
44
45
  "author": "Lev Nikolaevich <https://github.com/levnikolaevich>",
45
46
  "license": "MIT",