pi-hashline-edit-pro 0.3.0 → 0.3.1
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 +7 -7
- package/package.json +1 -1
- package/src/hashline/apply.ts +1 -1
- package/src/hashline/hash.ts +1 -1
- package/src/hashline/parse.ts +2 -2
- package/src/hashline/resolve.ts +6 -6
package/README.md
CHANGED
|
@@ -56,13 +56,13 @@ Images (JPEG, PNG, GIF, WebP) are passed through as attachments and do not parti
|
|
|
56
56
|
|
|
57
57
|
### `edit` — hash-anchored modifications
|
|
58
58
|
|
|
59
|
-
Edits use the
|
|
59
|
+
Edits use the `#HASH:content` anchors from `read` output to target lines precisely:
|
|
60
60
|
|
|
61
61
|
```json
|
|
62
62
|
{
|
|
63
63
|
"path": "src/main.ts",
|
|
64
64
|
"edits": [
|
|
65
|
-
{ "op": "replace", "start": "ve7o", "end": "ve7o", "lines": [" console.log('hashline');"] }
|
|
65
|
+
{ "op": "replace", "start": "#ve7o", "end": "#ve7o", "lines": [" console.log('hashline');"] }
|
|
66
66
|
]
|
|
67
67
|
}
|
|
68
68
|
```
|
|
@@ -80,11 +80,11 @@ All edits in a single call validate against the same pre-edit snapshot and apply
|
|
|
80
80
|
|
|
81
81
|
### Chained edits
|
|
82
82
|
|
|
83
|
-
After a successful edit, the result text contains an `--- Anchors ---` block with fresh
|
|
83
|
+
After a successful edit, the result text contains an `--- Anchors ---` block with fresh `#HASH:content` references for the changed region. These can be used directly in the next `edit` call on the same file without a full re-read, provided the next edit targets the same or nearby lines. For distant changes, use `read` first.
|
|
84
84
|
|
|
85
85
|
### Auto-read after write
|
|
86
86
|
|
|
87
|
-
After a successful `write`, the extension automatically reads the file and appends a `--- Auto-read (hashline anchors) ---` block to the result. This gives the model immediate
|
|
87
|
+
After a successful `write`, the extension automatically reads the file and appends a `--- Auto-read (hashline anchors) ---` block to the result. This gives the model immediate `#HASH:content` anchors for the newly written file without requiring a separate `read` call. The workflow becomes:
|
|
88
88
|
|
|
89
89
|
1. `write` a file → result includes hashline anchors
|
|
90
90
|
2. `edit` using those anchors directly
|
|
@@ -92,13 +92,13 @@ After a successful `write`, the extension automatically reads the file and appen
|
|
|
92
92
|
For large files (>2000 lines), the auto-read output is truncated with a pagination hint. Use `read` with `offset` to see more.
|
|
93
93
|
### Diff for the host
|
|
94
94
|
|
|
95
|
-
The post-edit diff (with `+`/`-` markers and new
|
|
95
|
+
The post-edit diff (with `+`/`-` markers and new `#HASH:content` anchors) is exposed to the host UI via `details.diff`. It is intentionally **not** in the LLM-visible text — the model only needs the fresh anchors in `text` to chain follow-up edits, and re-emitting the diff would cost extra tokens.
|
|
96
96
|
|
|
97
97
|
## Design Decisions
|
|
98
98
|
|
|
99
|
-
- **Stale anchors fail.** A hash mismatch means the file has changed since the last `read`. The error includes fresh `>>> HASH:content` lines for the affected region; the model copies the HASH portion and retries.
|
|
99
|
+
- **Stale anchors fail.** A hash mismatch means the file has changed since the last `read`. The error includes fresh `>>> #HASH:content` lines for the affected region; the model copies the HASH portion and retries.
|
|
100
100
|
- **No fallback relocation.** Mismatched anchors are never silently relocated to a "close enough" line. This trades convenience for correctness.
|
|
101
|
-
- **Strict patch content.** If `lines` contains
|
|
101
|
+
- **Strict patch content.** If `lines` contains `+#HASH:` display prefixes (or `-N ` diff rows), the edit is rejected with `[E_INVALID_PATCH]`. Bare `#HASH:` content (the first 6 chars of a `lines` entry looking like `#` + 4 base64 chars + `:`) is also rejected with `[E_BARE_HASH_PREFIX]` — issue #24. When the suspect's prefix happens to match a real file-line anchor, the error message flags that as strong evidence the model copied an anchor from the read output; the model should rephrase the line (quote it, escape the colon, or use a different identifier shape) and retry.
|
|
102
102
|
- **Legacy dialect rejected.** The native top-level `oldText`/`newText` (and `old_text`/`new_text`) dialect and `op: "replace_text"` are rejected with `[E_LEGACY_SHAPE]`. The error message tells the model to call `read` first and send `{op:"replace", start:"<HASH>", end:"<HASH>", lines:[...]}` (or `append`/`prepend` with `pos`).
|
|
103
103
|
- **Atomic writes.** Files are written via temp-file-then-rename to avoid corruption from interrupted writes. Symlink chains are resolved so the target file is updated without replacing the symlink. Hard-linked files are updated in place to preserve the shared inode. File permissions are preserved across atomic renames.
|
|
104
104
|
- **Per-file mutation queue.** Edits queue by the canonical write target, so concurrent edits through different symlink paths still serialize onto the same underlying file.
|
package/package.json
CHANGED
package/src/hashline/apply.ts
CHANGED
|
@@ -558,7 +558,7 @@ export function computeAffectedLineRange(params: {
|
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
/**
|
|
561
|
-
* Format a list of lines as
|
|
561
|
+
* Format a list of lines as `#HASH:content` rows.
|
|
562
562
|
*
|
|
563
563
|
* Used by the read tool's preview and the changed-mode anchor block. The
|
|
564
564
|
* hashes must be the precomputed per-line hashes for the file — see
|
package/src/hashline/hash.ts
CHANGED
|
@@ -98,7 +98,7 @@ export const DIFF_MINUS_RE = /^-\s*\d+\s{4}/;
|
|
|
98
98
|
*
|
|
99
99
|
* This is the partial-hash failure mode from issue #24: the model copies a
|
|
100
100
|
* hash it saw in `read` output into the line content but drops the rest
|
|
101
|
-
* of the rendered
|
|
101
|
+
* of the rendered `#HASH:content` form. The anchor (prefix + HASH_LENGTH chars
|
|
102
102
|
* + ":") is matched by this regex, then `assertNoBareHashPrefixLines` rejects
|
|
103
103
|
* the edit with `[E_BARE_HASH_PREFIX]` so the model gets actionable feedback
|
|
104
104
|
* instead of a silent correctness bug.
|
package/src/hashline/parse.ts
CHANGED
|
@@ -63,8 +63,8 @@ function parseAnchorRef(ref: string): Anchor {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Parse a hash anchor. Accepts
|
|
67
|
-
*
|
|
66
|
+
* Parse a hash anchor. Accepts `#HASH` (e.g. `"#aB3x"`) only. The
|
|
67
|
+
* `#HASH:content` disambiguator from earlier versions is gone — the anchor
|
|
68
68
|
* is the entire wire format for `pos` and `end`, and no content may
|
|
69
69
|
* follow it.
|
|
70
70
|
*
|
package/src/hashline/resolve.ts
CHANGED
|
@@ -88,7 +88,7 @@ export type HashlineToolEdit = {
|
|
|
88
88
|
* - `not_found`: no line in the file has this hash
|
|
89
89
|
* - `ambiguous`: the hash matches multiple lines (the model must
|
|
90
90
|
* re-read to disambiguate; the runtime does not accept a
|
|
91
|
-
|
|
91
|
+
* `#HASH:content` disambiguator on the wire)
|
|
92
92
|
*/
|
|
93
93
|
function resolveAnchor(
|
|
94
94
|
ref: Anchor,
|
|
@@ -332,12 +332,12 @@ function maybeWarnSuspiciousUnicodeEscapePlaceholder(
|
|
|
332
332
|
* `assertNoDisplayPrefixes`, which rejects the unambiguous `+HASH:` form at
|
|
333
333
|
* the parse stage; this catches the bare `HASH:` form (after optional leading
|
|
334
334
|
* whitespace) at the apply stage. The first 5 characters of every `lines`
|
|
335
|
-
* entry are checked: 4 alphabet characters (A–Z, a–z, 0–9, `-`, `_`)
|
|
335
|
+
* entry are checked: `#` prefix + 4 alphabet characters (A–Z, a–z, 0–9, `-`, `_`)
|
|
336
336
|
* followed by `:`.
|
|
337
337
|
*
|
|
338
|
-
* Bare
|
|
338
|
+
* Bare `#HASH:` prefixes in `lines` are almost always a model mistake — the
|
|
339
339
|
* model copied the hash prefix from a `read` output but dropped the rest of
|
|
340
|
-
* the rendered
|
|
340
|
+
* the rendered `#HASH:content` form. We reject with `[E_BARE_HASH_PREFIX]`
|
|
341
341
|
* rather than warn, because a stray hash in the file content is a silent
|
|
342
342
|
* correctness bug (the line is written verbatim, no autocorrection) and
|
|
343
343
|
* because the cost of a false positive is small: the model can rephrase the
|
|
@@ -346,7 +346,7 @@ function maybeWarnSuspiciousUnicodeEscapePlaceholder(
|
|
|
346
346
|
*
|
|
347
347
|
* The error message lists the offending lines, the suspect hash prefix for
|
|
348
348
|
* each, and whether any of them collide with a real file-line hash. A
|
|
349
|
-
* collision is a strong signal that the model was reading a
|
|
349
|
+
* collision is a strong signal that the model was reading a `#HASH:content`
|
|
350
350
|
* line and copied only the prefix.
|
|
351
351
|
*/
|
|
352
352
|
export function assertNoBareHashPrefixLines(
|
|
@@ -394,7 +394,7 @@ export function assertNoBareHashPrefixLines(
|
|
|
394
394
|
: `${matchedCount} match file line hashes — likely a copied hash.`;
|
|
395
395
|
|
|
396
396
|
throw new Error(
|
|
397
|
-
`[E_BARE_HASH_PREFIX] ${suspects.length} edit line(s) start with a hash-like prefix (e.g. ${JSON.stringify(exampleLine)}). ${linesHint} Use literal file content in "lines" — never paste HASH:content from read output.`
|
|
397
|
+
`[E_BARE_HASH_PREFIX] ${suspects.length} edit line(s) start with a hash-like prefix (e.g. ${JSON.stringify(exampleLine)}). ${linesHint} Use literal file content in "lines" — never paste #HASH:content from read output.`
|
|
398
398
|
);
|
|
399
399
|
}
|
|
400
400
|
|