@levnikolaevich/hex-line-mcp 1.9.0 → 1.11.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 +31 -38
- package/dist/artifacts/tree-sitter/manifest.json +57 -0
- package/dist/artifacts/tree-sitter/tree-sitter-c_sharp.wasm +0 -0
- package/dist/artifacts/tree-sitter/tree-sitter-javascript.wasm +0 -0
- package/dist/artifacts/tree-sitter/tree-sitter-php.wasm +0 -0
- package/dist/artifacts/tree-sitter/tree-sitter-python.wasm +0 -0
- package/dist/artifacts/tree-sitter/tree-sitter-tsx.wasm +0 -0
- package/dist/artifacts/tree-sitter/tree-sitter-typescript.wasm +0 -0
- package/dist/hook.mjs +44 -52
- package/dist/server.mjs +769 -325
- package/output-style.md +21 -26
- package/package.json +11 -12
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Every line carries an FNV-1a content hash. Every edit must present those hashes
|
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
|
-
###
|
|
14
|
+
### 9 MCP Tools
|
|
15
15
|
|
|
16
16
|
Core day-to-day tools:
|
|
17
17
|
|
|
@@ -25,20 +25,18 @@ Core day-to-day tools:
|
|
|
25
25
|
Advanced / occasional:
|
|
26
26
|
|
|
27
27
|
- `write_file`
|
|
28
|
-
- `
|
|
29
|
-
- `get_file_info`
|
|
28
|
+
- `inspect_path`
|
|
30
29
|
- `changes`
|
|
31
30
|
|
|
32
31
|
| Tool | Description | Key Feature |
|
|
33
32
|
|------|-------------|-------------|
|
|
34
|
-
| `read_file` | Read file with hash-annotated lines, checksums, and
|
|
33
|
+
| `read_file` | Read file with hash-annotated lines, checksums, revision, and automatic graph hints when available | Partial reads via `offset`/`limit` or `ranges`, compact output by default |
|
|
35
34
|
| `edit_file` | Revision-aware anchor edits (`set_line`, `replace_lines`, `insert_after`, `replace_between`) | Batched same-file edits + conservative auto-rebase |
|
|
36
35
|
| `write_file` | Create new file or overwrite, auto-creates parent dirs | Path validation, no hash overhead |
|
|
37
36
|
| `grep_search` | Search with ripgrep, 3 output modes, per-group checksums | Plain `files`/`count`, compact edit-ready `content` |
|
|
38
|
-
| `outline` | AST-based structural overview with hash anchors via tree-sitter WASM. Supports
|
|
37
|
+
| `outline` | AST-based structural overview with hash anchors via tree-sitter WASM. Supports JavaScript/TypeScript, Python, C#, PHP, and fence-aware markdown headings | 95% token reduction, direct edit anchors |
|
|
39
38
|
| `verify` | Check if held checksums / revision are still current | Staleness check without full re-read |
|
|
40
|
-
| `
|
|
41
|
-
| `get_file_info` | File metadata without reading content | Size, lines, mtime, type, binary detection |
|
|
39
|
+
| `inspect_path` | Unified file-or-directory inspection | File metadata for files, tree or pattern search for directories |
|
|
42
40
|
| `changes` | Compare file against git ref, shows added/removed/modified symbols | AST-level semantic diff |
|
|
43
41
|
| `bulk_replace` | Search-and-replace across multiple files by glob | Compact summary (default) or capped diffs via `format`, dry_run, max_files |
|
|
44
42
|
|
|
@@ -93,17 +91,20 @@ Comparative built-in vs hex-line benchmarks are maintained outside this package.
|
|
|
93
91
|
|
|
94
92
|
### Optional Graph Enrichment
|
|
95
93
|
|
|
96
|
-
If a project already has `.hex-skills/codegraph/index.db`, `hex-line`
|
|
94
|
+
If a project already has `.hex-skills/codegraph/index.db`, `hex-line` automatically adds lightweight graph hints to `read_file`, `outline`, `grep_search`, `edit_file`, and `changes`.
|
|
97
95
|
|
|
98
|
-
- Graph enrichment is optional. If `.hex-skills/codegraph/index.db` is missing, `hex-line` falls back to standard behavior silently.
|
|
96
|
+
- Graph enrichment is optional. If `.hex-skills/codegraph/index.db` is missing, stale, or unreadable, `hex-line` falls back to standard behavior silently.
|
|
99
97
|
- `better-sqlite3` is optional. If it is unavailable, `hex-line` still works without graph hints.
|
|
100
|
-
- `
|
|
98
|
+
- `read_file`, `outline`, and `grep_search` stay compact: they only surface high-signal local facts such as `api`, framework entrypoints, callers, flow, and clone hints.
|
|
99
|
+
- `edit_file` and `changes` surface the deeper review layer: external callers, downstream return/property flow, clone peers, public API risk, framework entrypoint risk, and same-name sibling warnings when present.
|
|
101
100
|
|
|
102
101
|
`hex-line` does not read `hex-graph` internals directly anymore. The integration uses a small read-only contract exposed by `hex-graph-mcp`:
|
|
103
102
|
|
|
104
|
-
- `
|
|
105
|
-
- `
|
|
106
|
-
- `
|
|
103
|
+
- `hex_line_symbols`
|
|
104
|
+
- `hex_line_line_facts`
|
|
105
|
+
- `hex_line_edit_impacts`
|
|
106
|
+
- `hex_line_edit_impact_facts`
|
|
107
|
+
- `hex_line_clone_siblings`
|
|
107
108
|
|
|
108
109
|
## Tools Reference
|
|
109
110
|
|
|
@@ -131,16 +132,15 @@ Use `bulk_replace` for text rename patterns across one or more files. Returns co
|
|
|
131
132
|
|
|
132
133
|
### read_file
|
|
133
134
|
|
|
134
|
-
Read a file as canonical edit-ready blocks. Each valid range becomes a `read_range` block with absolute span, line entries, and a checksum covering exactly the emitted lines. Invalid ranges become explicit diagnostic blocks. Supports batch reads
|
|
135
|
+
Read a file as canonical edit-ready blocks. Each valid range becomes a `read_range` block with absolute span, line entries, and a checksum covering exactly the emitted lines. Invalid ranges become explicit diagnostic blocks. Supports batch reads and multi-range reads. Directories go through `inspect_path`.
|
|
135
136
|
|
|
136
137
|
| Parameter | Type | Required | Description |
|
|
137
138
|
|-----------|------|----------|-------------|
|
|
138
|
-
| `path` | string | yes | File
|
|
139
|
+
| `path` | string | yes | File path |
|
|
139
140
|
| `paths` | string[] | no | Array of file paths to read (batch mode) |
|
|
140
141
|
| `offset` | number | no | Start line, 1-indexed (default: 1) |
|
|
141
142
|
| `limit` | number | no | Max lines to return (default: 2000, 0 = all) |
|
|
142
143
|
| `ranges` | array | no | Explicit line ranges, e.g. `[{ "start": 10, "end": 30 }]` |
|
|
143
|
-
| `include_graph` | boolean | no | Opt in to graph annotations when the graph index exists |
|
|
144
144
|
| `plain` | boolean | no | Omit hashes, output `lineNum\|content` instead |
|
|
145
145
|
|
|
146
146
|
Default output is compact but block-structured:
|
|
@@ -227,13 +227,13 @@ Search file contents using ripgrep. Three output modes: `content` (canonical `se
|
|
|
227
227
|
|
|
228
228
|
### outline
|
|
229
229
|
|
|
230
|
-
AST-based structural outline with hash anchors for direct `edit_file` usage. Supports
|
|
230
|
+
AST-based structural outline with hash anchors for direct `edit_file` usage. Supports JavaScript/TypeScript, Python, C#, PHP, and fence-aware markdown heading navigation (`.md`/`.mdx`). Each entry includes a hash tag for immediate anchor use without intermediate `read_file`.
|
|
231
231
|
|
|
232
232
|
| Parameter | Type | Required | Description |
|
|
233
233
|
|-----------|------|----------|-------------|
|
|
234
234
|
| `path` | string | yes | Source file path |
|
|
235
235
|
|
|
236
|
-
Supported languages: JavaScript, TypeScript (
|
|
236
|
+
Supported languages: JavaScript (`.js`, `.mjs`, `.cjs`, `.jsx`), TypeScript (`.ts`, `.tsx`), Python (`.py`), C# (`.cs`), and PHP (`.php`) via tree-sitter WASM.
|
|
237
237
|
|
|
238
238
|
Not for `.json`, `.yaml`, `.txt` -- use `read_file` directly for those.
|
|
239
239
|
|
|
@@ -260,30 +260,22 @@ changed_ranges: 10-12(replace)
|
|
|
260
260
|
STALE 10-12 checksum: 10-12:oldc0de0 current=10-12:newc0de0
|
|
261
261
|
```
|
|
262
262
|
|
|
263
|
-
###
|
|
263
|
+
### inspect_path
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
Inspect a file or directory path without guessing which low-level tool to call first.
|
|
266
266
|
|
|
267
267
|
| Parameter | Type | Required | Description |
|
|
268
268
|
|-----------|------|----------|-------------|
|
|
269
|
-
| `path` | string | yes |
|
|
269
|
+
| `path` | string | yes | File or directory path |
|
|
270
270
|
| `pattern` | string | no | Glob filter on names (e.g. `"*-mcp"`, `"*.mjs"`). Returns flat match list instead of tree |
|
|
271
271
|
| `type` | string | no | `"file"`, `"dir"`, or `"all"` (default). Like `find -type f/d` |
|
|
272
272
|
| `max_depth` | number | no | Max recursion depth (default: 3, or 20 in pattern mode) |
|
|
273
273
|
| `gitignore` | boolean | no | Respect root .gitignore patterns (default: true). Nested .gitignore not supported |
|
|
274
|
-
| `format` | string | no | `"compact"` =
|
|
275
|
-
|
|
276
|
-
Skips `node_modules`, `.git`, `dist`, `build`, `__pycache__`, `.next`, `coverage` by default.
|
|
277
|
-
|
|
278
|
-
### get_file_info
|
|
279
|
-
|
|
280
|
-
File metadata without reading content.
|
|
281
|
-
|
|
282
|
-
| Parameter | Type | Required | Description |
|
|
283
|
-
|-----------|------|----------|-------------|
|
|
284
|
-
| `path` | string | yes | File path |
|
|
274
|
+
| `format` | string | no | `"compact"` = shorter path view. `"full"` = include sizes / metadata where available |
|
|
285
275
|
|
|
286
|
-
|
|
276
|
+
- For regular files it returns compact metadata: size, line count when cheap, modification time, type, and binary flag.
|
|
277
|
+
- For directories it returns a gitignore-aware tree.
|
|
278
|
+
- With `pattern`, it switches to flat match mode and works as the preferred replacement for `find` / recursive `ls`.
|
|
287
279
|
|
|
288
280
|
## Hook
|
|
289
281
|
|
|
@@ -322,11 +314,10 @@ Injects a short operational workflow into agent context at session start: no `To
|
|
|
322
314
|
|
|
323
315
|
```
|
|
324
316
|
hex-line-mcp/
|
|
325
|
-
server.mjs MCP server (stdio transport,
|
|
317
|
+
server.mjs MCP server (stdio transport, 9 tools)
|
|
326
318
|
hook.mjs Unified hook (PreToolUse + PostToolUse + SessionStart)
|
|
327
319
|
package.json
|
|
328
320
|
lib/
|
|
329
|
-
hash.mjs FNV-1a hashing, 2-char tags, range checksums
|
|
330
321
|
read.mjs File reading with hash annotation
|
|
331
322
|
edit.mjs Anchor-based edits, diff output
|
|
332
323
|
search.mjs ripgrep wrapper with hash-annotated results
|
|
@@ -334,13 +325,15 @@ hex-line-mcp/
|
|
|
334
325
|
verify.mjs Range checksum verification
|
|
335
326
|
info.mjs File metadata (size, lines, mtime, type)
|
|
336
327
|
tree.mjs Directory tree with .gitignore support
|
|
328
|
+
inspect-path.mjs Unified file/directory inspection
|
|
337
329
|
changes.mjs Semantic git diff via AST
|
|
338
330
|
bulk-replace.mjs Multi-file search-and-replace
|
|
339
331
|
setup.mjs Claude hook installation + output style setup
|
|
340
332
|
format.mjs Output formatting utilities
|
|
341
|
-
coerce.mjs Parameter pass-through (identity)
|
|
342
333
|
security.mjs Path validation, binary detection, size limits
|
|
343
|
-
|
|
334
|
+
@levnikolaevich/hex-common/
|
|
335
|
+
text-protocol/hash.mjs Shared FNV-1a hashing and checksum protocol
|
|
336
|
+
output/normalize.mjs Shared output normalization helpers
|
|
344
337
|
```
|
|
345
338
|
|
|
346
339
|
### Hash Format
|
|
@@ -390,7 +383,7 @@ The edit is rejected with an error showing which lines changed since the last re
|
|
|
390
383
|
<details>
|
|
391
384
|
<summary><b>Is outline available for all file types?</b></summary>
|
|
392
385
|
|
|
393
|
-
Outline works on
|
|
386
|
+
Outline works on JavaScript/TypeScript, Python, C#, PHP, and markdown heading navigation (`.md`/`.mdx`, fenced code blocks ignored). For JSON, YAML, and text files use `read_file` directly. Each outline entry includes a hash anchor (`tag.line-range: symbol`) for direct use in `edit_file`.
|
|
394
387
|
|
|
395
388
|
</details>
|
|
396
389
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generated_from": {
|
|
3
|
+
"source": "tree-sitter-cli-build",
|
|
4
|
+
"tree_sitter_cli_version": "0.26.8",
|
|
5
|
+
"note": "Repo-owned grammar WASM artifacts built from pinned npm grammar packages with the current tree-sitter CLI."
|
|
6
|
+
},
|
|
7
|
+
"grammars": [
|
|
8
|
+
{
|
|
9
|
+
"grammar": "javascript",
|
|
10
|
+
"file": "tree-sitter-javascript.wasm",
|
|
11
|
+
"package": "tree-sitter-javascript",
|
|
12
|
+
"version": "0.25.0",
|
|
13
|
+
"source_subdir": null,
|
|
14
|
+
"sha256": "29d37c59b7797fa1c56419816d1730d5ff5e2ed2077bc44c5d6f50082be09136"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"grammar": "typescript",
|
|
18
|
+
"file": "tree-sitter-typescript.wasm",
|
|
19
|
+
"package": "tree-sitter-typescript",
|
|
20
|
+
"version": "0.23.2",
|
|
21
|
+
"source_subdir": "typescript",
|
|
22
|
+
"sha256": "f7c442260d8bf63b89762af2663655d153f84d169b4b8fedd15a5ba9b29ed3a4"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"grammar": "tsx",
|
|
26
|
+
"file": "tree-sitter-tsx.wasm",
|
|
27
|
+
"package": "tree-sitter-typescript",
|
|
28
|
+
"version": "0.23.2",
|
|
29
|
+
"source_subdir": "tsx",
|
|
30
|
+
"sha256": "4f795eb1fd00d14b7d284add59df8796d3c4b30e49d27979cba70c02298d5f67"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"grammar": "python",
|
|
34
|
+
"file": "tree-sitter-python.wasm",
|
|
35
|
+
"package": "tree-sitter-python",
|
|
36
|
+
"version": "0.25.0",
|
|
37
|
+
"source_subdir": null,
|
|
38
|
+
"sha256": "6183aa51eecace847badb487766e33a0f7725c257b479a0b89c9ff1bcec9c610"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"grammar": "c_sharp",
|
|
42
|
+
"file": "tree-sitter-c_sharp.wasm",
|
|
43
|
+
"package": "tree-sitter-c-sharp",
|
|
44
|
+
"version": "0.23.1",
|
|
45
|
+
"source_subdir": null,
|
|
46
|
+
"sha256": "974cb3e3302c04a5b5e6734494af2ed2fd0c284c38693316ef013922639aecf2"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"grammar": "php",
|
|
50
|
+
"file": "tree-sitter-php.wasm",
|
|
51
|
+
"package": "tree-sitter-php",
|
|
52
|
+
"version": "0.24.2",
|
|
53
|
+
"source_subdir": "php",
|
|
54
|
+
"sha256": "393e65be27e256ed2f1a43e0e70e43de05f77b5563d7dd877bdb44760d5560a6"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/hook.mjs
CHANGED
|
@@ -54,7 +54,7 @@ function normalizeOutput(text, opts = {}) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// hook.mjs
|
|
57
|
-
import { readFileSync, statSync } from "node:fs";
|
|
57
|
+
import { readFileSync, statSync, writeSync } from "node:fs";
|
|
58
58
|
import { resolve } from "node:path";
|
|
59
59
|
import { homedir } from "node:os";
|
|
60
60
|
import { fileURLToPath } from "node:url";
|
|
@@ -97,27 +97,15 @@ var OUTLINEABLE_EXT = /* @__PURE__ */ new Set([
|
|
|
97
97
|
".ts",
|
|
98
98
|
".tsx",
|
|
99
99
|
".py",
|
|
100
|
-
".go",
|
|
101
|
-
".rs",
|
|
102
|
-
".java",
|
|
103
|
-
".c",
|
|
104
|
-
".h",
|
|
105
|
-
".cpp",
|
|
106
100
|
".cs",
|
|
107
|
-
".
|
|
108
|
-
".php",
|
|
109
|
-
".kt",
|
|
110
|
-
".swift",
|
|
111
|
-
".sh",
|
|
112
|
-
".bash"
|
|
101
|
+
".php"
|
|
113
102
|
]);
|
|
114
103
|
var REVERSE_TOOL_HINTS = {
|
|
115
104
|
"mcp__hex-line__read_file": "Read (file_path, offset, limit)",
|
|
116
105
|
"mcp__hex-line__edit_file": "Edit (old_string, new_string, replace_all)",
|
|
117
106
|
"mcp__hex-line__write_file": "Write (file_path, content)",
|
|
118
107
|
"mcp__hex-line__grep_search": "Grep (pattern, path)",
|
|
119
|
-
"mcp__hex-
|
|
120
|
-
"mcp__hex-line__get_file_info": "Bash(stat/wc)",
|
|
108
|
+
"mcp__hex-line__inspect_path": "Path info / tree / Bash(ls,stat)",
|
|
121
109
|
"mcp__hex-line__outline": "Read with offset/limit",
|
|
122
110
|
"mcp__hex-line__verify": "Read (re-read file to check freshness)",
|
|
123
111
|
"mcp__hex-line__changes": "Bash(git diff)",
|
|
@@ -131,8 +119,8 @@ var TOOL_HINTS = {
|
|
|
131
119
|
cat: "mcp__hex-line__read_file (not cat/head/tail/less/more)",
|
|
132
120
|
head: "mcp__hex-line__read_file with limit param (not head)",
|
|
133
121
|
tail: "mcp__hex-line__read_file with offset param (not tail)",
|
|
134
|
-
ls: "mcp__hex-
|
|
135
|
-
stat: "mcp__hex-
|
|
122
|
+
ls: "mcp__hex-line__inspect_path for tree or pattern search (not ls/find/tree). E.g. pattern='*-mcp' type='dir'",
|
|
123
|
+
stat: "mcp__hex-line__inspect_path for compact file metadata (not stat/wc/file)",
|
|
136
124
|
grep: "mcp__hex-line__grep_search (not grep/rg). Params: output, literal, context_before, context_after, multiline",
|
|
137
125
|
sed: "mcp__hex-line__edit_file for hash edits, or mcp__hex-line__bulk_replace for text rename (not sed -i)",
|
|
138
126
|
diff: "mcp__hex-line__changes (not diff). Git diff with change symbols",
|
|
@@ -141,6 +129,7 @@ var TOOL_HINTS = {
|
|
|
141
129
|
changes: "mcp__hex-line__changes (git diff with change symbols)",
|
|
142
130
|
bulk: "mcp__hex-line__bulk_replace (multi-file search-replace)"
|
|
143
131
|
};
|
|
132
|
+
var DEFERRED_HINT = "If schemas not loaded: ToolSearch('+hex-line read edit')";
|
|
144
133
|
var BASH_REDIRECTS = [
|
|
145
134
|
{ regex: /^cat\s+\S+/, key: "cat" },
|
|
146
135
|
{ regex: /^head\s+/, key: "head" },
|
|
@@ -282,16 +271,25 @@ function getHookMode() {
|
|
|
282
271
|
}
|
|
283
272
|
return _hookMode;
|
|
284
273
|
}
|
|
274
|
+
function safeExit(fd, data, code) {
|
|
275
|
+
writeSync(fd, data);
|
|
276
|
+
process.exit(code);
|
|
277
|
+
}
|
|
278
|
+
function debugLog(action, reason) {
|
|
279
|
+
writeSync(2, `[hex-hook] ${action}: ${reason}
|
|
280
|
+
`);
|
|
281
|
+
}
|
|
285
282
|
function block(reason, context) {
|
|
283
|
+
const msg = context ? `${reason}
|
|
284
|
+
${context}` : reason;
|
|
286
285
|
const output = {
|
|
287
286
|
hookSpecificOutput: {
|
|
288
287
|
permissionDecision: "deny"
|
|
289
288
|
},
|
|
290
|
-
systemMessage:
|
|
291
|
-
${context}` : reason
|
|
289
|
+
systemMessage: msg
|
|
292
290
|
};
|
|
293
|
-
|
|
294
|
-
|
|
291
|
+
debugLog("BLOCK", reason);
|
|
292
|
+
safeExit(1, JSON.stringify(output), 2);
|
|
295
293
|
}
|
|
296
294
|
function advise(reason, context) {
|
|
297
295
|
const output = {
|
|
@@ -301,8 +299,7 @@ function advise(reason, context) {
|
|
|
301
299
|
systemMessage: context ? `${reason}
|
|
302
300
|
${context}` : reason
|
|
303
301
|
};
|
|
304
|
-
|
|
305
|
-
process.exit(0);
|
|
302
|
+
safeExit(1, JSON.stringify(output), 0);
|
|
306
303
|
}
|
|
307
304
|
function redirect(reason, context) {
|
|
308
305
|
if (getHookMode() === "advisory") {
|
|
@@ -324,24 +321,20 @@ function handlePreToolUse(data) {
|
|
|
324
321
|
if (BINARY_EXT.has(extOf(filePath))) {
|
|
325
322
|
process.exit(0);
|
|
326
323
|
}
|
|
327
|
-
const
|
|
328
|
-
|
|
324
|
+
const resolvedNorm = resolveToolPath(filePath).replace(/\\/g, "/");
|
|
325
|
+
const cwdNorm = process.cwd().replace(/\\/g, "/");
|
|
326
|
+
const homeNorm = homedir().replace(/\\/g, "/");
|
|
327
|
+
const claudeAllow = [
|
|
328
|
+
cwdNorm + "/.claude/settings.json",
|
|
329
|
+
cwdNorm + "/.claude/settings.local.json",
|
|
330
|
+
homeNorm + "/.claude/settings.json",
|
|
331
|
+
homeNorm + "/.claude/settings.local.json"
|
|
332
|
+
];
|
|
333
|
+
if (claudeAllow.some((p) => resolvedNorm.toLowerCase() === p.toLowerCase())) {
|
|
329
334
|
process.exit(0);
|
|
330
335
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (ALLOWED_CONFIGS.has(fileName)) {
|
|
334
|
-
let candidate = filePath;
|
|
335
|
-
if (candidate.startsWith("~/")) {
|
|
336
|
-
candidate = homedir().replace(/\\/g, "/") + candidate.slice(1);
|
|
337
|
-
}
|
|
338
|
-
const absPath = resolve(process.cwd(), candidate).replace(/\\/g, "/");
|
|
339
|
-
const projectClaude = resolve(process.cwd(), ".claude").replace(/\\/g, "/") + "/";
|
|
340
|
-
const globalClaude = resolve(homedir(), ".claude").replace(/\\/g, "/") + "/";
|
|
341
|
-
const cmp = process.platform === "win32" ? (a, b) => a.toLowerCase().startsWith(b.toLowerCase()) : (a, b) => a.startsWith(b);
|
|
342
|
-
if (cmp(absPath, projectClaude) || cmp(absPath, globalClaude)) {
|
|
343
|
-
process.exit(0);
|
|
344
|
-
}
|
|
336
|
+
if (resolvedNorm.includes("/.claude/")) {
|
|
337
|
+
redirect("Protected .claude/ path. Use built-in tools for .claude/ config files.");
|
|
345
338
|
}
|
|
346
339
|
if (toolName === "Read") {
|
|
347
340
|
if (isPartialRead(toolInput)) {
|
|
@@ -349,29 +342,30 @@ function handlePreToolUse(data) {
|
|
|
349
342
|
}
|
|
350
343
|
if (fileSize !== null && fileSize <= LARGE_FILE_BYTES) {
|
|
351
344
|
const ext2 = filePath ? extOf(filePath) : "";
|
|
352
|
-
const hint = filePath && OUTLINEABLE_EXT.has(ext2) ? `mcp__hex-line__outline(path="${filePath}")
|
|
353
|
-
advise(hint);
|
|
345
|
+
const hint = filePath && OUTLINEABLE_EXT.has(ext2) ? `Use mcp__hex-line__outline(path="${filePath}") for structure, then mcp__hex-line__read_file(path="${filePath}") with ranges.` : filePath ? `Use mcp__hex-line__read_file(path="${filePath}"). Built-in Read wastes edit context.` : "Use mcp__hex-line__read_file. Built-in Read wastes edit context.";
|
|
346
|
+
advise(hint, DEFERRED_HINT);
|
|
354
347
|
}
|
|
355
348
|
const ext = filePath ? extOf(filePath) : "";
|
|
356
|
-
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.` : filePath ? `Use mcp__hex-line__read_file(path="${filePath}") with ranges or offset/limit` : "Use mcp__hex-
|
|
357
|
-
redirect(outlineHint, "Do not use built-in Read for full reads of large files
|
|
349
|
+
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.` : filePath ? `Use mcp__hex-line__read_file(path="${filePath}") with ranges or offset/limit` : "Use mcp__hex-line__inspect_path or mcp__hex-line__read_file";
|
|
350
|
+
redirect(outlineHint, "Do not use built-in Read for full reads of large files.\n" + DEFERRED_HINT);
|
|
358
351
|
}
|
|
359
352
|
if (toolName === "Edit") {
|
|
360
353
|
const oldText = String(toolInput.old_string || "");
|
|
361
354
|
const isLargeEdit = Boolean(toolInput.replace_all) || oldText.length > LARGE_EDIT_CHARS || fileSize !== null && fileSize > LARGE_FILE_BYTES;
|
|
362
355
|
if (!isLargeEdit) {
|
|
363
|
-
|
|
356
|
+
const editHint = filePath ? `Prefer mcp__hex-line__edit_file(path="${filePath}") for hash-verified edits.` : "Prefer mcp__hex-line__edit_file for hash-verified edits.";
|
|
357
|
+
advise(editHint);
|
|
364
358
|
}
|
|
365
359
|
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";
|
|
366
|
-
redirect(target, "For large or repeated edits: locate anchors/checksums first, then call edit_file once with batched edits
|
|
360
|
+
redirect(target, "For large or repeated edits: locate anchors/checksums first, then call edit_file once with batched edits.\n" + DEFERRED_HINT);
|
|
367
361
|
}
|
|
368
362
|
if (toolName === "Write") {
|
|
369
363
|
const pathNote = filePath ? ` with path="${filePath}"` : "";
|
|
370
|
-
redirect(`Use mcp__hex-line__write_file${pathNote}`, TOOL_HINTS.Write);
|
|
364
|
+
redirect(`Use mcp__hex-line__write_file${pathNote}`, TOOL_HINTS.Write + "\n" + DEFERRED_HINT);
|
|
371
365
|
}
|
|
372
366
|
if (toolName === "Grep") {
|
|
373
367
|
const pathNote = filePath ? ` with path="${filePath}"` : "";
|
|
374
|
-
redirect(`Use mcp__hex-line__grep_search${pathNote}`, TOOL_HINTS.Grep);
|
|
368
|
+
redirect(`Use mcp__hex-line__grep_search${pathNote}`, TOOL_HINTS.Grep + "\n" + DEFERRED_HINT);
|
|
375
369
|
}
|
|
376
370
|
}
|
|
377
371
|
if (toolName === "Bash") {
|
|
@@ -457,8 +451,7 @@ function handlePostToolUse(data) {
|
|
|
457
451
|
`Original: ${originalCount} lines | Filtered: ${filteredCount} lines`,
|
|
458
452
|
"=".repeat(50)
|
|
459
453
|
].join("\n");
|
|
460
|
-
|
|
461
|
-
process.exit(2);
|
|
454
|
+
safeExit(2, output, 2);
|
|
462
455
|
}
|
|
463
456
|
function handleSessionStart() {
|
|
464
457
|
const settingsFiles = [
|
|
@@ -479,9 +472,8 @@ function handleSessionStart() {
|
|
|
479
472
|
}
|
|
480
473
|
}
|
|
481
474
|
const prefix = styleActive ? "Hex-line MCP available. Output style active.\n" : "Hex-line MCP available.\n";
|
|
482
|
-
const msg = prefix + "
|
|
483
|
-
|
|
484
|
-
process.exit(0);
|
|
475
|
+
const msg = prefix + "<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 for text rename/refactor across files</path>\n </editing>\n <tips>\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>Batch multiple edits to same file in one edit_file call.</tip>\n <tip>Use write_file for new files (no prior Read needed).</tip>\n </tips>\n <exceptions>Built-in Read OK for: images, PDFs, notebooks, Glob (always), .claude/settings.json</exceptions>\n</hex-line_instructions>";
|
|
476
|
+
safeExit(1, JSON.stringify({ systemMessage: msg }), 0);
|
|
485
477
|
}
|
|
486
478
|
var _norm = (p) => p.replace(/\\/g, "/");
|
|
487
479
|
if (_norm(process.argv[1]) === _norm(fileURLToPath(import.meta.url))) {
|