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 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 `HASH:content` anchors from `read` output to target lines precisely:
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 `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.
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 `HASH:content` anchors for the newly written file without requiring a separate `read` call. The workflow becomes:
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 `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.
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 `+HASH:` display prefixes (or `-N ` diff rows), the edit is rejected with `[E_INVALID_PATCH]`. Bare `HASH:` content (the first 5 chars of a `lines` entry looking like a 4-char hash followed by `:`) is also rejected with `[E_BARE_HASH_PREFIX]` — issue #24. When the suspect's prefix happens to match a real file-line hash, the error message flags that as strong evidence the model copied a hash from the read output; the model should rephrase the line (quote it, escape the colon, or use a different identifier shape) and retry.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hashline-edit-pro",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Strict hashline read/edit tool override for pi-coding-agent with hash-anchored edits (4-char, 24-bit)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -558,7 +558,7 @@ export function computeAffectedLineRange(params: {
558
558
  }
559
559
 
560
560
  /**
561
- * Format a list of lines as `HASH:content` rows.
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
@@ -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 `HASH:content` form. The anchor (prefix + HASH_LENGTH chars
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.
@@ -63,8 +63,8 @@ function parseAnchorRef(ref: string): Anchor {
63
63
  }
64
64
 
65
65
  /**
66
- * Parse a hash anchor. Accepts `HASH` (e.g. `"aB3x"`) only. The
67
- * `HASH:content` disambiguator from earlier versions is gone — the hash
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
  *
@@ -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
- * `HASH:content` disambiguator on the wire)
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 `HASH:` prefixes in `lines` are almost always a model mistake — the
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 `HASH:content` form. We reject with `[E_BARE_HASH_PREFIX]`
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 `HASH:content`
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