pi-hashline-edit-pro 2.8.4 → 3.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 +24 -24
- package/package.json +2 -2
- package/prompts/grep-guidelines.md +3 -3
- package/prompts/grep.md +1 -1
- package/prompts/insert-guidelines.md +1 -3
- package/prompts/insert-snippet.md +1 -1
- package/prompts/insert.md +1 -1
- package/prompts/read-guidelines.md +2 -2
- package/prompts/read.md +1 -1
- package/prompts/replace-guidelines.md +4 -5
- package/prompts/replace-snippet.md +1 -1
- package/prompts/replace.md +1 -1
- package/prompts/undo-last-change-guidelines.md +1 -2
- package/prompts/undo-last-change.md +1 -1
- package/src/commit.ts +1 -1
- package/src/config.ts +1 -1
- package/src/constants.ts +1 -1
- package/src/grep.ts +5 -5
- package/src/hashline/alphabet.ts +30 -7
- package/src/hashline/anchor-table.json +1 -0
- package/src/hashline/hash.ts +6 -28
- package/src/hashline/parse.ts +8 -8
- package/src/hashline/resolve.ts +3 -3
- package/src/insert.ts +6 -6
- package/src/payload-contract.ts +5 -5
- package/src/utils.ts +1 -1
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/pi-hashline-edit-pro) [](https://www.npmjs.com/package/pi-hashline-edit-pro)
|
|
4
4
|
|
|
5
|
-
Anchor-based `read`, `replace`, `insert`, and `anchor_grep` tools for [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent). Every line of a file gets a unique
|
|
5
|
+
Anchor-based `read`, `replace`, `insert`, and `anchor_grep` tools for [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent). Every line of a file gets a unique 4-character anchor, and you edit by anchor. There are no line numbers and no fuzzy matching, so edits land on the lines you meant.
|
|
6
6
|
|
|
7
|
-
Fork of [pi-hashline-edit](https://github.com/RimuruW/pi-hashline-edit) by RimuruW, extended with
|
|
7
|
+
Fork of [pi-hashline-edit](https://github.com/RimuruW/pi-hashline-edit) by RimuruW, extended with 4-character tokenizer-friendly anchors and collision resolution.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
@@ -22,9 +22,9 @@ Fork of [pi-hashline-edit](https://github.com/RimuruW/pi-hashline-edit) by Rimur
|
|
|
22
22
|
1. Read a file:
|
|
23
23
|
|
|
24
24
|
```text
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
Dafo│function hello() {
|
|
26
|
+
Emno│ console.log("world");
|
|
27
|
+
HDtm│}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
2. Replace a line by its anchor:
|
|
@@ -32,8 +32,8 @@ kQm│}
|
|
|
32
32
|
```json
|
|
33
33
|
{
|
|
34
34
|
"path": "src/main.ts",
|
|
35
|
-
"remove_from": "
|
|
36
|
-
"remove_to": "
|
|
35
|
+
"remove_from": "Emno",
|
|
36
|
+
"remove_to": "Emno",
|
|
37
37
|
"replacement_lines": [" console.log('hi');"]
|
|
38
38
|
}
|
|
39
39
|
```
|
|
@@ -54,7 +54,7 @@ pi install /path/to/pi-hashline-edit-pro
|
|
|
54
54
|
|
|
55
55
|
## The read tool
|
|
56
56
|
|
|
57
|
-
`read` returns a text file with every line prefixed by `anchor│content`. The anchor is
|
|
57
|
+
`read` returns a text file with every line prefixed by `anchor│content`. The anchor is 4 characters from `A-Za-z0-9` (for example `Hasu`), drawn from a curated table of two-character tokens so every anchor costs 2 tokens in the major tokenizers.
|
|
58
58
|
|
|
59
59
|
| Parameter | Description |
|
|
60
60
|
| --- | --- |
|
|
@@ -72,7 +72,7 @@ Edge cases:
|
|
|
72
72
|
- UTF-16 and UTF-32 text (detected via BOM) is rejected, since editing it would corrupt the file.
|
|
73
73
|
- Empty files come back as a single empty-line anchor (`anchor│`); use `replace` on that anchor to insert content.
|
|
74
74
|
- BOMs are stripped for display. Non-UTF-8 bytes are shown as `U+FFFD`; editing such a file rewrites it as UTF-8, with a warning.
|
|
75
|
-
- Files over
|
|
75
|
+
- Files over 257,795 lines or 100MB are rejected with `[E_FILE_TOO_LARGE]`.
|
|
76
76
|
|
|
77
77
|
## The replace tool
|
|
78
78
|
|
|
@@ -83,16 +83,16 @@ One edit per call, with `remove_from`, `remove_to`, and `replacement_lines` at t
|
|
|
83
83
|
```json
|
|
84
84
|
{
|
|
85
85
|
"path": "src/main.ts",
|
|
86
|
-
"remove_from": "
|
|
87
|
-
"remove_to": "
|
|
86
|
+
"remove_from": "Emno",
|
|
87
|
+
"remove_to": "HDtm",
|
|
88
88
|
"replacement_lines": [" console.log('hi');", "}"]
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
| Field | Description |
|
|
93
93
|
| --- | --- |
|
|
94
|
-
| `remove_from` |
|
|
95
|
-
| `remove_to` |
|
|
94
|
+
| `remove_from` | 4-char anchor from `read` output marking the FIRST line to remove (inclusive). |
|
|
95
|
+
| `remove_to` | 4-char anchor from `read` output marking the LAST line to remove (inclusive). |
|
|
96
96
|
| `replacement_lines` | Replacement lines as an array of strings, one element per line. Mirror the removed lines exactly, blank lines included: use `[]` to delete the range, `[""]` for a single blank line, `["a", ""]` for a line followed by a blank line, and `["", ""]` for two blank lines. Do not embed `\n` inside an element: each element is exactly one line. |
|
|
97
97
|
|
|
98
98
|
Notes:
|
|
@@ -111,7 +111,7 @@ Notes:
|
|
|
111
111
|
```json
|
|
112
112
|
{
|
|
113
113
|
"path": "src/main.ts",
|
|
114
|
-
"anchor": "
|
|
114
|
+
"anchor": "Emno",
|
|
115
115
|
"direction": "after",
|
|
116
116
|
"lines": [" console.log('hi');"]
|
|
117
117
|
}
|
|
@@ -119,7 +119,7 @@ Notes:
|
|
|
119
119
|
|
|
120
120
|
| Field | Description |
|
|
121
121
|
| --- | --- |
|
|
122
|
-
| `anchor` |
|
|
122
|
+
| `anchor` | 4-char anchor from `read` output marking the line next to which the lines go (inclusive; the line is preserved). A pasted diff row like `+Hasu│x` or an `anchor│` prefix is stripped automatically with a warning. |
|
|
123
123
|
| `direction` | `"after"` to insert below the anchor line, `"before"` to insert above it. |
|
|
124
124
|
| `lines` | Lines to insert as an array of strings, one element per line. Mirror `replacement_lines` semantics: use `[""]` for a blank line and do not embed `\n` inside an element. The anchor line is never part of `lines`. |
|
|
125
125
|
|
|
@@ -133,7 +133,7 @@ Notes:
|
|
|
133
133
|
|
|
134
134
|
## The anchor_grep tool
|
|
135
135
|
|
|
136
|
-
`anchor_grep` replaces the built-in grep with an anchored search backed by ripgrep. While `anchor_grep` is enabled, the built-in grep is disabled; disabling `anchor_grep` restores it if it was active before the extension loaded. Every matching line (and each requested context line) is returned as `lineNumber │ anchor│content` — the `anchor│content` part is served exactly like `read` output, so you can target it with `replace`/`insert` without a separate `read`, while the line-number gutter and `=== path ===` header give filename and line for navigation (press Return to jump).
|
|
136
|
+
When enabled, `anchor_grep` replaces the built-in grep with an anchored search backed by ripgrep; it is disabled by default (see below). While `anchor_grep` is enabled, the built-in grep is disabled; disabling `anchor_grep` restores it if it was active before the extension loaded. Every matching line (and each requested context line) is returned as `lineNumber │ anchor│content` — the `anchor│content` part is served exactly like `read` output, so you can target it with `replace`/`insert` without a separate `read`, while the line-number gutter and `=== path ===` header give filename and line for navigation (press Return to jump).
|
|
137
137
|
|
|
138
138
|
| Field | Description |
|
|
139
139
|
| --- | --- |
|
|
@@ -153,7 +153,7 @@ Notes:
|
|
|
153
153
|
- `file_path` works as an alias for `path`.
|
|
154
154
|
- Line endings and BOMs survive every edit. The file's line ending is detected from its first newline and restored on write; a file that mixes LF and CRLF (for example a WSL-edited file) is normalized to the first-seen ending.
|
|
155
155
|
- Files with multiple hard links (`nlink > 1`) are rewritten in place rather than via a temp-file rename, so every link keeps seeing the same content; that write is direct rather than atomic.
|
|
156
|
-
- The anchor_grep tool is
|
|
156
|
+
- The anchor_grep tool is disabled by default. Enable it with `/toggle-anchor-grep` (or set `anchorGrepEnabled` to `true` in the config file); the setting persists across sessions. While enabled, the built-in grep is disabled; disabling anchor_grep removes it from the model's toolset and restores the built-in grep only if it was active before the extension loaded — a grep tool that was never enabled stays off.
|
|
157
157
|
|
|
158
158
|
## Undo
|
|
159
159
|
|
|
@@ -196,17 +196,17 @@ Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created automatic
|
|
|
196
196
|
```json
|
|
197
197
|
{
|
|
198
198
|
"autoRead": true,
|
|
199
|
-
"anchorGrepEnabled":
|
|
199
|
+
"anchorGrepEnabled": false
|
|
200
200
|
}
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
## How anchors work
|
|
204
204
|
|
|
205
|
-
Each line is canonicalized (carriage returns stripped, trailing whitespace trimmed) and hashed with [xxhash-wasm](https://github.com/jungomi/xxhash-wasm) (xxHash32), then mapped to a
|
|
205
|
+
Each line is canonicalized (carriage returns stripped, trailing whitespace trimmed) and hashed with [xxhash-wasm](https://github.com/jungomi/xxhash-wasm) (xxHash32), then mapped to a 4-character anchor from a frozen table of 257,795 anchors. The canonicalization keeps anchors stable across editor-save cycles that add or remove trailing whitespace. A line longer than 500 bytes is hashed from its first 500 bytes; uniqueness is still guaranteed by the collision-resolution below.
|
|
206
206
|
|
|
207
|
-
The
|
|
207
|
+
The table is curated for tokenizers, not for humans: every anchor is the concatenation of two 2-character sequences that are single tokens in the o200k, cl100k, GPT-2, Llama, and Mistral vocabularies (verified offline against each full vocabulary), and the 4-character concatenation itself is verified to encode as exactly 2 tokens in all of them. An anchor therefore costs 2 tokens on a read row and 2 tokens in an edit call, with the `│` separator as the only overhead. Anchors are letters only; the digits dropped out because SentencePiece vocabularies have no two-digit tokens. The table is generated by `scripts/generate-anchor-table.py` and shipped as `src/hashline/anchor-table.json`.
|
|
208
208
|
|
|
209
|
-
Anchors are unique by construction. If a line's base hash collides with an already-assigned hash, the next free hash is allocated from a bitset by probing with a stride coprime to the hash space (O(1) amortized). The stride
|
|
209
|
+
Anchors are unique by construction. If a line's base hash collides with an already-assigned hash, the next free hash is allocated from a bitset by probing with a stride coprime to the hash space (O(1) amortized). The stride advances both anchor halves, so consecutive collisions — runs of blank lines, repeated `}` — never share their first two characters. Every line in a file therefore gets a unique anchor; two byte-identical lines (repeated `}`, repeated `import` statements) never share one. The same guarantee sets the file size cap: at most 257,795 lines per file, beyond which `read`, `replace`, and `insert` reject with `[E_FILE_TOO_LARGE]` (use `write` for very large files).
|
|
210
210
|
|
|
211
211
|
Hashes live in a persistent per-file store (`~/.config/pi-hashline-edit-pro/hash-store.sqlite`) that keeps the hashes of unchanged lines across edits. When a range is replaced, the runtime maps the old content onto the new content and copies hashes for lines that survived; only genuinely new lines get fresh hashes.
|
|
212
212
|
|
|
@@ -226,10 +226,10 @@ A no-op replace never changes the file, so anchors remain valid. On first run af
|
|
|
226
226
|
| Code | Meaning |
|
|
227
227
|
| --- | --- |
|
|
228
228
|
| `[E_BAD_SHAPE]` | Request envelope or edit item has unknown, missing, or wrongly-typed fields (for example `replacement_lines` must be an array of strings, one element per line). |
|
|
229
|
-
| `[E_BAD_REF]` | An anchor in `remove_from`/`remove_to` is not a bare
|
|
229
|
+
| `[E_BAD_REF]` | An anchor in `remove_from`/`remove_to` is not a bare 4-char anchor. |
|
|
230
230
|
| `[E_STALE_ANCHOR]` | An anchor does not match any line in the current file; call `read` for fresh anchors. |
|
|
231
231
|
| `[E_AMBIGUOUS_ANCHOR]` | An anchor matches multiple lines; call `read` for fresh anchors. |
|
|
232
|
-
| `[E_INVALID_PATCH]` | A `replacement_lines` element is a diff-preview row (`+anchor│`, `-anchor│`, `-
|
|
232
|
+
| `[E_INVALID_PATCH]` | A `replacement_lines` element is a diff-preview row (`+anchor│`, `-anchor│`, `- │`). The marker is stripped automatically with a warning. |
|
|
233
233
|
| `[E_BARE_HASH_PREFIX]` | A `replacement_lines` element starts with an `anchor│` prefix (the anchor plus the separator). The prefix is stripped automatically with a warning. |
|
|
234
234
|
| `[E_BAD_OP]` | Range start line is after range end line. The pair is swapped automatically with a warning. |
|
|
235
235
|
| `[E_WOULD_EMPTY]` | An edit would empty a non-empty file; use `write` instead. |
|
|
@@ -240,7 +240,7 @@ A no-op replace never changes the file, so anchors remain valid. On first run af
|
|
|
240
240
|
| `[E_UNDO_UNAVAILABLE]` | Undo history could not be persisted to the hash store; the edit was refused and the file was left unchanged. |
|
|
241
241
|
| `[E_RANGE_STALE]` | A line in the replaced range no longer matches what was last shown (the file changed on disk, or the line was never shown). The edit was refused; the current range is returned with fresh anchors. |
|
|
242
242
|
| `[E_BOUNDARY_BYPASS]` | The boundary anti-duplication was turned off for one replace call (an identical replacement had previously been cut to a noop); the duplicate lines were applied literally. The dedup is restored for the next call. |
|
|
243
|
-
| `[E_FILE_TOO_LARGE]` | The file exceeds the
|
|
243
|
+
| `[E_FILE_TOO_LARGE]` | The file exceeds the 257,795-line hashline limit or the 100MB size limit. |
|
|
244
244
|
| `[E_WRITE_HASH_ECHO]` | A `write` `content` line begins with the exact `anchor│` served for this file at the same line. The write is refused, file byte-identical; retry with bare content (remove the copied anchors). |
|
|
245
245
|
| `[E_PATH_CHANGED]` | A write target changed identity after it was read; the write was refused to avoid following a swapped symlink or overwriting a replacement file. |
|
|
246
246
|
| `[E_UNSAFE_REGEX]` | A grep regex can trigger excessive backtracking; simplify it or search with `literal: true`. |
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hashline-edit-pro",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Hash-anchored read/replace/insert/grep tools for pi-coding-agent. Every line gets a unique
|
|
5
|
+
"description": "Hash-anchored read/replace/insert/grep tools for pi-coding-agent. Every line gets a unique 4-char tokenizer-friendly anchor that stays stable across edits; stale or ambiguous anchors are rejected, never fuzzy-matched. Undo persists across restarts.",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
- `anchor_grep`:
|
|
2
|
-
- `anchor_grep`:
|
|
3
|
-
- `anchor_grep`:
|
|
1
|
+
- `anchor_grep`: hits and `context` rows come back as `lineNumber │ anchor│content` — target them with `replace`/`insert` directly; `lineNumber` enables jump-to-line.
|
|
2
|
+
- `anchor_grep`: `path` takes a file or folder (default cwd), `glob` like `*.ts` filters, `literal:true` for literal text, `context:N` for surrounding lines.
|
|
3
|
+
- `anchor_grep`: binary/image files are skipped silently.
|
package/prompts/grep.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Search text files
|
|
1
|
+
Search text files with ripgrep. Every hit and `context` line is returned as `lineNumber │ anchor│content` — the `anchor│content` part works directly in `replace`/`insert` without a new `read`; `lineNumber` and the `=== path ===` header give file and line for navigation. Respects `.gitignore`; `.git` is always skipped, `node_modules`/`.tmp`/`coverage` are skipped only when a `.gitignore` lists them; binary/image files are skipped silently. Matches over 500 bytes are shown as a `...` fragment around the match, but the anchor still covers the whole line. If output says truncated, refine `pattern` or raise `limit` as hinted.
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
- `insert`:
|
|
2
|
-
- `insert`: `lines` is bare content, one element per line, kept literally even if it duplicates neighbors — nothing is removed.
|
|
3
|
-
- `insert`: the anchor must have been shown by `read`, a post-edit diff (`+anchor│`/` anchor│`), or `anchor_grep`. For an empty file, `read` shows one `anchor│` empty row — insert `after` it.
|
|
1
|
+
- `insert`: the anchor must have been shown by `read`, a post-edit diff (`+anchor│`/` anchor│`), or any served `anchor│content` row. Empty file: `read` shows one `anchor│` row — insert `after` it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Insert `lines` after/before bare anchor `
|
|
1
|
+
Insert `lines` after/before bare anchor `Hasu` from `Hasu│content`: anchor stays, lines are bare without `│`, one per element
|
package/prompts/insert.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Insert lines after or before one existing line in a text file,
|
|
1
|
+
Insert lines after or before one existing line in a text file, by a bare anchor like `Hasu` from `Hasu│content`. The anchor line stays; `lines` are added after (`"after"`) or before (`"before"`) it. Lines are added literally, even if they duplicate neighbors.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
- `read`: call before `replace` when you need fresh anchors
|
|
2
|
-
- `read`: call again after an edit when you need anchors you
|
|
1
|
+
- `read`: call before `replace` when you need fresh anchors.
|
|
2
|
+
- `read`: call again after an edit when you need anchors you lack — post-edit diff `+anchor│`/` anchor│` rows and any served `anchor│content` rows already carry fresh anchors for the changed range.
|
package/prompts/read.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Read a text file; each line returned as `anchor│content`
|
|
1
|
+
Read a text file; each line returned as `anchor│content` — a 4-character alphanumeric anchor plus the content (e.g. `Hasu│hello`: anchor `Hasu`). No line numbers: edit by anchor in replace/insert. Images → visual attachments; Binary/directory → rejected; UTF-16/UTF-32 (BOM) → rejected; empty → anchor│ (replace to insert); pageable with offset/limit; BOM stripped; non-UTF-8 shown as U+FFFD. If output says truncated, use `offset`/`limit` as hinted.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
- `replace`:
|
|
2
|
-
- `replace`: `replacement_lines`
|
|
3
|
-
- `replace`: the range
|
|
4
|
-
- `replace`: diff
|
|
5
|
-
- `replace`: `+anchor│` and ` anchor│` rows in a post-edit diff are fresh anchors — you can use them for the next edit without a new `read`. Do one `replace`/`insert` per turn and check the diff before the next edit.
|
|
1
|
+
- `replace`: use the same anchor for `remove_from` and `remove_to` to change one line.
|
|
2
|
+
- `replace`: `replacement_lines` takes bare lines without `│`; `[""]` is one blank line; pasted `anchor│` prefixes are stripped automatically.
|
|
3
|
+
- `replace`: keep the range tight — only lines that actually change — and copy leading spaces exactly.
|
|
4
|
+
- `replace`: post-edit diff `+anchor│`/` anchor│` rows are fresh anchors for the next edit — no new `read` needed. One edit per turn; check the diff before the next edit on that file.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Replace lines via bare
|
|
1
|
+
Replace lines via bare 4-char anchor `Hasu` from `Hasu│content`: `remove_from`/`remove_to` are `Hasu` only, `replacement_lines` is bare lines without `│`; one edit per call
|
package/prompts/replace.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Replace a range of lines (or a single line) in a text file, targeted by
|
|
1
|
+
Replace a range of lines (or a single line) in a text file, targeted by 4-character anchors from read output. In `Hasu│content`, `Hasu` is the bare anchor before `│` and the full row is `Hasu│content`. `remove_from`/`remove_to` are bare anchors only (e.g. `"Hasu"`), never the row or file content. `replacement_lines` is bare content without `│`, one string per line — `[]` deletes. Example: read showed `Hasu│old` and `arvm│old2`; to replace both use `{"remove_from":"Hasu","remove_to":"arvm","replacement_lines":["new line 1","new line 2"]}`. Single line → same anchor for both.
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
- `undo_last_change`: only the last `replace`/`insert` per file
|
|
2
|
-
- `undo_last_change`: if the file changed after the edit you get `[E_UNDO_STALE]` — nothing is reverted and the record is kept; do not modify the file to force an undo, verify with `read` and stop; if the file was deleted it is restored.
|
|
1
|
+
- `undo_last_change`: only the last `replace`/`insert` per file is undoable; a `write` clears it, so undo right after a bad diff (check the `-anchor│` lines you wanted to keep).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Undo the last `replace` or `insert` on a file, restoring previous content, BOM and line endings. Use after a bad edit
|
|
1
|
+
Undo the last `replace` or `insert` on a file, restoring previous content, BOM and line endings. Use after a bad edit removed wrong lines. Deleted file → restored; changed elsewhere → `[E_UNDO_STALE]`, nothing reverted, record kept — do not modify the file to force an undo; call `read` to verify the current state, then stop. If output says truncated, use `read` to see full file.
|
package/src/commit.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface CommitMeta {
|
|
|
25
25
|
function boundaryDedupWarning(lineTexts: string[]): string {
|
|
26
26
|
const quoted = lineTexts.map((line) => `"${clipLine(line, 80)}"`).join(", ");
|
|
27
27
|
const plural = lineTexts.length > 1;
|
|
28
|
-
return `Boundary dedup: ${quoted} already ${plural ? "exist" : "exists"} next to the edited range, so ${plural ? "they were" : "it was"} not added again
|
|
28
|
+
return `Boundary dedup: ${quoted} already ${plural ? "exist" : "exists"} next to the edited range, so ${plural ? "they were" : "it was"} not added again.`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export async function commitEdit(pipe: PipelineResult, meta: CommitMeta): Promise<TResult> {
|
package/src/config.ts
CHANGED
package/src/constants.ts
CHANGED
|
@@ -7,6 +7,6 @@ export const MAX_HASH_SOURCE_BYTES = 500;
|
|
|
7
7
|
export const MAX_GREP_LINE_BYTES = 500;
|
|
8
8
|
|
|
9
9
|
export const HASH_STORE_BUSY_TIMEOUT = 1000;
|
|
10
|
-
export const HASH_STORE_VERSION =
|
|
10
|
+
export const HASH_STORE_VERSION = 7;
|
|
11
11
|
export const NEW_CONTENT_NOT_ARRAY_MSG =
|
|
12
12
|
`[E_BAD_SHAPE] "replacement_lines" must be an array of strings, one per line (use [] to delete).`;
|
package/src/grep.ts
CHANGED
|
@@ -412,29 +412,29 @@ const grepToolSchema = Type.Object(
|
|
|
412
412
|
),
|
|
413
413
|
glob: Type.Optional(
|
|
414
414
|
Type.String({
|
|
415
|
-
description: "Filter files by glob
|
|
415
|
+
description: "Filter files by glob; `*` crosses directories, e.g. `*.ts`. A leading `/` is ignored; relative to the search root or cwd.",
|
|
416
416
|
}),
|
|
417
417
|
),
|
|
418
418
|
ignoreCase: Type.Optional(
|
|
419
419
|
Type.Boolean({
|
|
420
|
-
description: "Case-insensitive search
|
|
420
|
+
description: "Case-insensitive search",
|
|
421
421
|
}),
|
|
422
422
|
),
|
|
423
423
|
literal: Type.Optional(
|
|
424
424
|
Type.Boolean({
|
|
425
|
-
description: "Treat pattern as literal
|
|
425
|
+
description: "Treat pattern as literal text instead of regex",
|
|
426
426
|
}),
|
|
427
427
|
),
|
|
428
428
|
context: Type.Optional(
|
|
429
429
|
Type.Integer({
|
|
430
430
|
minimum: 0,
|
|
431
|
-
description: "
|
|
431
|
+
description: "Lines of context before and after each match",
|
|
432
432
|
}),
|
|
433
433
|
),
|
|
434
434
|
limit: Type.Optional(
|
|
435
435
|
Type.Integer({
|
|
436
436
|
minimum: 1,
|
|
437
|
-
description: "Maximum number of
|
|
437
|
+
description: "Maximum number of matched lines to return",
|
|
438
438
|
}),
|
|
439
439
|
),
|
|
440
440
|
},
|
package/src/hashline/alphabet.ts
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
import anchorData from "./anchor-table.json";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
3
|
+
const TABLE: string = anchorData.anchors;
|
|
5
4
|
|
|
6
|
-
const
|
|
5
|
+
export const HASH_LEN = 4;
|
|
7
6
|
|
|
8
|
-
export const
|
|
7
|
+
export const ANCHOR_COUNT = TABLE.length / HASH_LEN;
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
const ALNUM = "A-Za-z0-9";
|
|
11
10
|
|
|
12
|
-
export const
|
|
11
|
+
export const ALPH_RE = new RegExp(`^[${ALNUM}]+$`);
|
|
12
|
+
|
|
13
|
+
export const HASH_CLASS = `[${ALNUM}]{${HASH_LEN}}`;
|
|
14
|
+
|
|
15
|
+
export const HASH_RUN = `[${ALNUM}]{1,${HASH_LEN * 2}}`;
|
|
13
16
|
|
|
14
17
|
export const HASH_RE = new RegExp(`^${HASH_CLASS}$`);
|
|
18
|
+
|
|
19
|
+
let indexByAnchor: Map<string, number> | undefined;
|
|
20
|
+
|
|
21
|
+
function reverseIndex(): Map<string, number> {
|
|
22
|
+
if (!indexByAnchor) {
|
|
23
|
+
indexByAnchor = new Map();
|
|
24
|
+
for (let i = 0; i < ANCHOR_COUNT; i++) {
|
|
25
|
+
indexByAnchor.set(TABLE.slice(i * HASH_LEN, i * HASH_LEN + HASH_LEN), i);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return indexByAnchor;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function anchorAt(idx: number): string {
|
|
32
|
+
return TABLE.slice(idx * HASH_LEN, idx * HASH_LEN + HASH_LEN);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function anchorIndex(hash: string): number {
|
|
36
|
+
return reverseIndex().get(hash) ?? -1;
|
|
37
|
+
}
|