@levnikolaevich/hex-line-mcp 1.0.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 ADDED
@@ -0,0 +1,293 @@
1
+ # hex-line-mcp
2
+
3
+ Hash-verified file editing MCP + token efficiency hook for AI coding agents.
4
+
5
+ Every line carries an FNV-1a content hash. Every edit must present those hashes back -- proving the agent is editing what it thinks it's editing. No stale context, no silent corruption.
6
+
7
+ ## Features
8
+
9
+ ### 11 MCP Tools
10
+
11
+ | Tool | Description | Key Feature |
12
+ |------|-------------|-------------|
13
+ | `read_file` | Read file with hash-annotated lines and range checksums | Partial reads via `offset`/`limit` |
14
+ | `edit_file` | Hash-verified edits with anchor or text replacement | Returns compact diff via `diff` package |
15
+ | `write_file` | Create new file or overwrite, auto-creates parent dirs | Path validation, no hash overhead |
16
+ | `grep_search` | Search with ripgrep, returns hash-annotated matches | Edit-ready results -- search then edit directly |
17
+ | `outline` | AST-based structural overview via tree-sitter WASM | 95% token reduction (10 lines instead of 500) |
18
+ | `verify` | Check if held range checksums are still valid | Single-line response avoids full re-read |
19
+ | `directory_tree` | Compact directory tree with .gitignore support | Skips node_modules/.git, shows file sizes |
20
+ | `get_file_info` | File metadata without reading content | Size, lines, mtime, type, binary detection |
21
+ | `setup_hooks` | Configure PreToolUse + PostToolUse hooks for Claude/Gemini/Codex | One call sets up everything, idempotent |
22
+ | `changes` | Compare file against git ref, shows added/removed/modified symbols | AST-level semantic diff |
23
+ | `bulk_replace` | Search-and-replace across multiple files by glob | Per-file diffs, dry_run, max_files safety |
24
+
25
+ ### Hooks (PreToolUse + PostToolUse)
26
+
27
+ | Event | Trigger | Action |
28
+ |-------|---------|--------|
29
+ | **PreToolUse** | Read/Edit/Write/Grep on text files | Blocks built-in, forces hex-line tools |
30
+ | **PreToolUse** | Bash with dangerous commands | Blocks `rm -rf /`, `git push --force`, etc. Agent must confirm with user |
31
+ | **PostToolUse** | Bash with 50+ lines output | RTK: deduplicates, truncates, summarizes |
32
+ | **SessionStart** | Session begins | Injects full tool preference list into agent context |
33
+
34
+
35
+ ### Bash Redirects
36
+
37
+ PreToolUse also intercepts simple Bash commands: cat, head, tail, ls, tree, find, stat, wc -l, grep, rg, sed -i, diff — redirects to hex-line equivalents. Compound commands with pipes are allowed.
38
+ ## Install
39
+
40
+ ### MCP Server
41
+
42
+ ```bash
43
+ claude mcp add -s user hex-line -- node path/to/mcp/hex-line-mcp/server.mjs
44
+ ```
45
+
46
+ Then install dependencies:
47
+
48
+ ```bash
49
+ cd mcp/hex-line-mcp && npm install
50
+ ```
51
+
52
+ ### Hooks
53
+
54
+ Automatic setup (run once after MCP install):
55
+
56
+ ```
57
+ mcp__hex-line__setup_hooks(agent="claude")
58
+ ```
59
+
60
+ Or manual — add to `.claude/settings.local.json`:
61
+
62
+ ```json
63
+ {
64
+ "hooks": {
65
+ "PreToolUse": [{"matcher": "Read|Edit|Write|Grep|Bash", "hooks": [{"type": "command", "command": "node mcp/hex-line-mcp/hook.mjs", "timeout": 5}]}],
66
+ "PostToolUse": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "node mcp/hex-line-mcp/hook.mjs", "timeout": 10}]}]
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Token Efficiency
72
+
73
+ Benchmark v3 (46 code files, 9,134 lines, 18 scenarios):
74
+
75
+ | Scenario | Without | With Hex-line | Savings |
76
+ |----------|---------|---------------|--------|
77
+ | Read full (any size) | raw | hash-annotated | 6-8% |
78
+ | Outline+read (200-500L) | 10,723 ch | 3,347 ch | **69%** |
79
+ | Outline+read (500L+) | 39,617 ch | 9,531 ch | **76%** |
80
+ | Edit x5 sequential | 2,581 ch | 1,529 ch | **41%** |
81
+ | Verify checksums | 8,295 ch | 93 ch | **99%** |
82
+ | Directory tree | 80,853 ch | 22,120 ch | **73%** |
83
+ | File info | 368 ch | 195 ch | **47%** |
84
+ | bulk_replace (5 files) | 2,795 ch | 1,706 ch | **39%** |
85
+ | Changes (semantic diff) | 830 ch | 133 ch | **84%** |
86
+ | FILE_NOT_FOUND recovery | 2,020 ch | 283 ch | **86%** |
87
+ | Hash mismatch recovery | 8,918 ch | 423 ch | **95%** |
88
+ | Bash redirects (cat+ls+stat) | 54,658 ch | 25,153 ch | **54%** |
89
+ | Grep search | 3,938 ch | 4,091 ch | -4% |
90
+
91
+ **Average savings: 46%.** Break-even: ~50 lines. Hash overhead: negligible.
92
+
93
+ Reproduce: `node benchmark.mjs` or `node benchmark.mjs --repo /path/to/repo`
94
+
95
+ ## Tools Reference
96
+
97
+ ### read_file
98
+
99
+ Read a file with FNV-1a hash-annotated lines and range checksums. Supports directory listing.
100
+
101
+ | Parameter | Type | Required | Description |
102
+ |-----------|------|----------|-------------|
103
+ | `path` | string | yes | File or directory path |
104
+ | `offset` | number | no | Start line, 1-indexed (default: 1) |
105
+ | `limit` | number | no | Max lines to return (default: 2000, 0 = all) |
106
+ | `plain` | boolean | no | Omit hashes, output `lineNum\|content` instead |
107
+
108
+ Output format:
109
+
110
+ ```
111
+ ab.1 import { resolve } from "node:path";
112
+ cd.2 import { readFileSync } from "node:fs";
113
+ ...
114
+ checksum: 1-50:f7e2a1b0
115
+ ```
116
+
117
+ ### edit_file
118
+
119
+ Edit using hash-verified anchors or text replacement. Returns a unified diff.
120
+
121
+ | Parameter | Type | Required | Description |
122
+ |-----------|------|----------|-------------|
123
+ | `path` | string | yes | File to edit |
124
+ | `edits` | string | yes | JSON array of edit operations (see below) |
125
+ | `dry_run` | boolean | no | Preview changes without writing |
126
+
127
+ Edit operations (JSON array):
128
+
129
+ ```json
130
+ [
131
+ {"set_line": {"anchor": "ab.12", "new_text": "replacement line"}},
132
+ {"replace_lines": {"start_anchor": "ab.10", "end_anchor": "cd.15", "new_text": "..."}},
133
+ {"insert_after": {"anchor": "ab.20", "text": "inserted line"}},
134
+ {"replace": {"old_text": "find this", "new_text": "replace with", "all": false}}
135
+ ]
136
+ ```
137
+
138
+ ### write_file
139
+
140
+ Create a new file or overwrite an existing one. Creates parent directories automatically.
141
+
142
+ | Parameter | Type | Required | Description |
143
+ |-----------|------|----------|-------------|
144
+ | `path` | string | yes | File path |
145
+ | `content` | string | yes | File content |
146
+
147
+ ### grep_search
148
+
149
+ Search file contents using ripgrep with hash-annotated results.
150
+
151
+ | Parameter | Type | Required | Description |
152
+ |-----------|------|----------|-------------|
153
+ | `pattern` | string | yes | Regex search pattern |
154
+ | `path` | string | no | Directory or file to search (default: cwd) |
155
+ | `glob` | string | no | Glob filter, e.g. `"*.ts"` |
156
+ | `type` | string | no | File type filter, e.g. `"js"`, `"py"` |
157
+ | `case_insensitive` | boolean | no | Ignore case |
158
+ | `context` | number | no | Context lines around matches |
159
+ | `limit` | number | no | Max matches per file (default: 100) |
160
+
161
+ ### outline
162
+
163
+ AST-based structural outline: functions, classes, interfaces with line ranges.
164
+
165
+ | Parameter | Type | Required | Description |
166
+ |-----------|------|----------|-------------|
167
+ | `path` | string | yes | Source file path |
168
+
169
+ Supported languages: JavaScript, TypeScript (JSX/TSX), Python, Go, Rust, Java, C, C++, C#, Ruby, PHP, Kotlin, Swift, Bash -- 15+ via tree-sitter WASM.
170
+
171
+ Not for `.md`, `.json`, `.yaml`, `.txt` -- use `read_file` directly for those.
172
+
173
+ ### verify
174
+
175
+ Check if range checksums from a prior read are still valid.
176
+
177
+ | Parameter | Type | Required | Description |
178
+ |-----------|------|----------|-------------|
179
+ | `path` | string | yes | File path |
180
+ | `checksums` | string | yes | JSON array of checksum strings, e.g. `["1-50:f7e2a1b0"]` |
181
+
182
+ Returns a single-line confirmation or lists changed ranges.
183
+
184
+ ### directory_tree
185
+
186
+ Compact directory tree with .gitignore support and file sizes.
187
+
188
+ | Parameter | Type | Required | Description |
189
+ |-----------|------|----------|-------------|
190
+ | `path` | string | yes | Directory path |
191
+ | `max_depth` | number | no | Max recursion depth (default: 3) |
192
+ | `gitignore` | boolean | no | Respect .gitignore patterns (default: true) |
193
+
194
+ Skips `node_modules`, `.git`, `dist`, `build`, `__pycache__`, `.next`, `coverage` by default.
195
+
196
+ ### get_file_info
197
+
198
+ File metadata without reading content.
199
+
200
+ | Parameter | Type | Required | Description |
201
+ |-----------|------|----------|-------------|
202
+ | `path` | string | yes | File path |
203
+
204
+ Returns: size, line count, modification time (absolute + relative), file type, binary detection.
205
+
206
+ ## Hook
207
+
208
+ The unified PostToolUse hook (`hook.mjs`) handles two concerns:
209
+
210
+ ### Hex-line Reminder
211
+
212
+ Triggers on built-in `Read`, `Edit`, `Write`, `Grep` tool usage for text files. Outputs a short reminder to stderr (exit code 2) nudging the agent to use the corresponding hex-line tool instead.
213
+
214
+ Binary files (images, PDFs, notebooks, archives, executables, fonts, media) are excluded -- those should use built-in tools.
215
+
216
+ ### RTK Output Filter
217
+
218
+ Triggers on `Bash` tool output exceeding 50 lines. Pipeline:
219
+
220
+ 1. **Detect command type** -- npm install, test, build, pip install, git verbose, or generic
221
+ 2. **Type-specific summary** -- extracts key metrics (e.g., `npm install: 42 added, 3 warnings`)
222
+ 3. **Normalize** -- replaces UUIDs, timestamps, IPs, hex values, large numbers with placeholders
223
+ 4. **Deduplicate** -- collapses identical normalized lines with `(xN)` counts
224
+ 5. **Truncate** -- keeps first 12 + last 12 lines, omits the middle
225
+
226
+ Configuration constants in `hook.mjs`:
227
+
228
+ | Constant | Default | Purpose |
229
+ |----------|---------|---------|
230
+ | `LINE_THRESHOLD` | 50 | Minimum lines to trigger filtering |
231
+ | `TRUNCATE_LIMIT` | 30 | Lines below this are kept as-is after dedup |
232
+ | `HEAD_LINES` | 12 | Lines to keep from start |
233
+ | `TAIL_LINES` | 12 | Lines to keep from end |
234
+
235
+ ## Architecture
236
+
237
+ ```
238
+ hex-line-mcp/
239
+ server.mjs MCP server (stdio transport, 6 tools)
240
+ hook.mjs PostToolUse hook (reminder + RTK filter)
241
+ package.json
242
+ lib/
243
+ hash.mjs FNV-1a hashing, 2-char tags, range checksums
244
+ read.mjs File reading with hash annotation
245
+ edit.mjs Anchor-based and text-based edits, diff output
246
+ search.mjs ripgrep wrapper with hash-annotated results
247
+ outline.mjs tree-sitter WASM AST outline
248
+ verify.mjs Range checksum verification
249
+ security.mjs Path validation, binary detection, size limits
250
+ normalize.mjs Output normalization, deduplication, truncation
251
+ ```
252
+
253
+ ### Hash Format
254
+
255
+ ```
256
+ ab.42 const x = calculateTotal(items);
257
+ ```
258
+
259
+ - `ab` -- 2-char FNV-1a tag derived from content (whitespace-normalized)
260
+ - `42` -- line number (1-indexed)
261
+ - Tab separator, then original content
262
+ - Tag alphabet: `abcdefghijklmnopqrstuvwxyz234567` (32 symbols, bitwise selection)
263
+
264
+ ### Range Checksums
265
+
266
+ ```
267
+ checksum: 1-50:f7e2a1b0
268
+ ```
269
+
270
+ FNV-1a accumulator over all line hashes in the range (little-endian byte feed). Detects changes to any line, even ones not being edited.
271
+
272
+ ### Security
273
+
274
+ - Path canonicalization via `realpathSync` (resolves symlinks)
275
+ - Binary file detection (null byte scan in first 8KB)
276
+ - 10 MB file size limit
277
+ - Write path validation (ancestor directory must exist)
278
+ - Directory restrictions delegated to Claude Code sandbox
279
+
280
+ ## Differences from trueline-mcp
281
+
282
+ | Aspect | hex-line-mcp | trueline-mcp |
283
+ |--------|---------------|--------------|
284
+ | Hash algorithm | FNV-1a (pure JS, zero dependencies) | xxHash (native addon) |
285
+ | Diff output | Compact unified diff via `diff` npm package | Custom diff implementation |
286
+ | Hook | Unified `hook.mjs` (reminder + RTK filter) | Separate hook scripts |
287
+ | Path security | Canonicalization + binary detection, no ALLOWED_DIRS | Explicit ALLOWED_DIRS allowlist |
288
+ | Transport | stdio only | stdio |
289
+ | Outline | tree-sitter WASM (15+ languages) | tree-sitter WASM |
290
+
291
+ ## License
292
+
293
+ MIT