pi-hashline-edit-pro 0.17.9 → 0.17.10
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/package.json +5 -2
- package/prompts/read-guidelines.md +1 -2
- package/prompts/read-snippet.md +1 -1
- package/prompts/read.md +2 -20
- package/prompts/replace-guidelines.md +2 -6
- package/prompts/replace.md +1 -33
- package/prompts/undo-last-replace-guidelines.md +2 -3
- package/prompts/undo-last-replace.md +1 -1
- package/src/fs-write.ts +8 -0
- package/src/replace-undo.ts +1 -1
- package/src/replace.ts +10 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hashline-edit-pro",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Strict hashline read/replace tool for pi-coding-agent with hash-anchored edits (3-char, 18-bit, perfect hashing)",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -63,6 +63,9 @@
|
|
|
63
63
|
"vitest": "^4.1.8"
|
|
64
64
|
},
|
|
65
65
|
"allowScripts": {
|
|
66
|
-
"better-sqlite3@13.0.1": true
|
|
66
|
+
"better-sqlite3@13.0.1": true,
|
|
67
|
+
"@google/genai@1.52.0": true,
|
|
68
|
+
"koffi@2.16.2": true,
|
|
69
|
+
"protobufjs@7.6.4": true
|
|
67
70
|
}
|
|
68
71
|
}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
-
|
|
2
|
-
- A HASH may contain `-` as a normal alphabet character.
|
|
1
|
+
- `read`: call before `replace` when you need fresh HASH anchors for a file.
|
package/prompts/read-snippet.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Read a
|
|
1
|
+
Read a file; each line returned as HASH│content
|
package/prompts/read.md
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
Read a text file. Each line
|
|
1
|
+
Read a text file. Each line returned as HASH│content (3-char URL-safe base64 hash). No line numbers — use the 3-char HASH to reference lines in replace calls.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
HASH format:
|
|
6
|
-
- The HASH is 3 characters from the URL-safe base64 alphabet `A-Za-z0-9-_` (e.g. `aB3`, `4yN`, `-qk`).
|
|
7
|
-
- The content after the `│` separator is the line verbatim.
|
|
8
|
-
- The line number is not part of the output. Use the HASH to reference lines.
|
|
9
|
-
|
|
10
|
-
Pagination:
|
|
11
|
-
- Large files return a truncated preview with a pagination hint (e.g. `[Showing lines 1-100 of 500. Use offset=101 to continue.]`). Call `read` again with `offset=N` to continue.
|
|
12
|
-
- Default cap: {{DEFAULT_MAX_LINES}} lines or {{DEFAULT_MAX_BYTES}}; output exceeding either is truncated. Pass `limit` to read fewer lines.
|
|
13
|
-
|
|
14
|
-
File kinds:
|
|
15
|
-
- Text files are returned as `HASH│content` lines.
|
|
16
|
-
- Images (JPEG, PNG, GIF, WebP) are returned as visual attachments.
|
|
17
|
-
- Binary files and directories are rejected with a descriptive error.
|
|
18
|
-
- Empty files are returned as a single empty-line hash (`HASH│`). Use replace on that hash to insert content.
|
|
19
|
-
|
|
20
|
-
Non-UTF-8 bytes:
|
|
21
|
-
- UTF-8 byte-order marks (BOM) are stripped. Editing a file with a BOM rewrites it without the BOM.
|
|
3
|
+
Text → HASH│content lines. Images → visual attachments. Binary/directory → rejected. Empty → HASH│ (replace to insert). Pageable with offset/limit. BOM stripped; non-UTF-8 shown as U+FFFD.
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
-
|
|
3
|
-
- On [E_STALE_ANCHOR], call read to get fresh anchors, copy the 3-character HASH of the start and end of the range into hash_range_inclusive, and retry.
|
|
4
|
-
- hash_range_inclusive replaces the ENTIRE range inclusively. Every line from the first anchor through the second anchor is deleted. Only put replacement lines in content_lines — do not include lines that already exist outside the range.
|
|
5
|
-
- Preserve leading whitespace exactly. The content after │ in read output includes all leading spaces and tabs — copy them into content_lines unchanged.
|
|
6
|
-
- content_lines must be a native JSON array of strings, not a JSON string.
|
|
1
|
+
- `replace`: Provide hash_range_inclusive [start_hash, end_hash] targeting lines by their 3-char hashes (replaces every line from first anchor through last anchor inclusively), and content_lines as a native JSON array of strings for the replacement text.
|
|
2
|
+
- `replace`: preserve leading whitespace exactly as it appears after │ in read output.
|
package/prompts/replace.md
CHANGED
|
@@ -1,33 +1 @@
|
|
|
1
|
-
Replace lines in a text file using HASH anchors from
|
|
2
|
-
|
|
3
|
-
Workflow:
|
|
4
|
-
1. Call `read` to get `HASH│content` lines for the file.
|
|
5
|
-
2. Identify the range to replace using the 3-char hashes from the read output.
|
|
6
|
-
3. Call `replace` with `hash_range_inclusive` (the two hashes) and `content_lines` (the new lines).
|
|
7
|
-
4. On `[E_STALE_ANCHOR]`, call `read` again for fresh anchors, then retry.
|
|
8
|
-
5. On success, call `read` to get fresh anchors for follow-up edits.
|
|
9
|
-
|
|
10
|
-
Request structure:
|
|
11
|
-
{{MODE_REQUEST_STRUCTURE}}
|
|
12
|
-
|
|
13
|
-
Fields:
|
|
14
|
-
- content_lines — replacement lines as a JSON array of strings. File content only — no HASH│ prefix.
|
|
15
|
-
- hash_range_inclusive — [start_hash, end_hash] from read output. 3-char base64 hash only — no │ or line content.
|
|
16
|
-
- path — file to edit.
|
|
17
|
-
|
|
18
|
-
Examples:
|
|
19
|
-
{{MODE_EXAMPLES}}
|
|
20
|
-
|
|
21
|
-
Rules:
|
|
22
|
-
- Anchors must be 3-char base64 hashes from the most recent read. Stale anchors fail with [E_STALE_ANCHOR].
|
|
23
|
-
- The range is inclusive: every line from start_hash through end_hash is deleted.
|
|
24
|
-
- Those lines are replaced with content_lines — nothing is inserted, nothing is appended.
|
|
25
|
-
- content_lines is literal file content. Never include the HASH│ prefix — that goes in hash_range_inclusive.
|
|
26
|
-
- content_lines must be a native JSON array of strings, not a serialized string.
|
|
27
|
-
- Preserve leading whitespace exactly as it appears after │ in read output.
|
|
28
|
-
- To delete lines, use content_lines: [].
|
|
29
|
-
- If content_lines matches current content, the edit is a noop (file unchanged).
|
|
30
|
-
{{MODE_RULES}}
|
|
31
|
-
On success, the response shows the change summary. {{AUTO_READ_GUIDANCE}}
|
|
32
|
-
|
|
33
|
-
Recovery: If a replace produces incorrect results, call undo_last_replace with the file path to revert.
|
|
1
|
+
Replace lines in a text file using HASH anchors from read's HASH│content output.
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
-
|
|
2
|
-
-
|
|
3
|
-
- Only the most recent replace per file is tracked — calling undo twice without an intervening replace will produce "no undo history".
|
|
1
|
+
- `undo_last_replace`: call with the file path to revert the last replace on that file.
|
|
2
|
+
- `undo_last_replace`: only the most recent replace per file is tracked.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Undo the last replace
|
|
1
|
+
Undo the last replace on a file, reverting it to its previous state. Use when a replace produced incorrect results (e.g., wrong content, duplicated lines, broken syntax).
|
package/src/fs-write.ts
CHANGED
|
@@ -120,6 +120,14 @@ export async function writeAtomic(
|
|
|
120
120
|
throw error;
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
if (process.platform === "win32" && errCode(error) === "EPERM") {
|
|
124
|
+
try {
|
|
125
|
+
await writeFile(targetPath, content, "utf-8");
|
|
126
|
+
return;
|
|
127
|
+
} finally {
|
|
128
|
+
try { await rm(tempPath, { force: true }); } catch {}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
123
131
|
try { await rm(tempPath, { force: true }); } catch {}
|
|
124
132
|
throw error;
|
|
125
133
|
}
|
package/src/replace-undo.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function regReplaceUndo(pi: ExtensionAPI): void {
|
|
|
40
40
|
promptGuidelines: loadGuide("../prompts/undo-last-replace-guidelines.md"),
|
|
41
41
|
parameters: Type.Object({
|
|
42
42
|
path: Type.String({
|
|
43
|
-
description: "Path to the file to undo
|
|
43
|
+
description: "Path to the file to undo",
|
|
44
44
|
}),
|
|
45
45
|
}),
|
|
46
46
|
|
package/src/replace.ts
CHANGED
|
@@ -46,13 +46,13 @@ import { loadHashStore, type HashStore } from "./hash-store";
|
|
|
46
46
|
|
|
47
47
|
const contentLinesSchema = Type.Array(Type.String(), {
|
|
48
48
|
description:
|
|
49
|
-
"
|
|
49
|
+
"Literal file content, one string per line."
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
const hashRangeInclSchema = Type.Array(
|
|
53
|
-
Type.String({ description: "
|
|
53
|
+
Type.String({ description: "A 3-char HASH from read output" }),
|
|
54
54
|
{
|
|
55
|
-
description: "
|
|
55
|
+
description: "Inclusive [start_hash, end_hash] — pair of 3-char hashes from read output.",
|
|
56
56
|
minItems: 2,
|
|
57
57
|
maxItems: 2,
|
|
58
58
|
},
|
|
@@ -68,8 +68,8 @@ const changeItemSchema = Type.Object(
|
|
|
68
68
|
|
|
69
69
|
export const editToolSchema = Type.Object(
|
|
70
70
|
{
|
|
71
|
-
changes: Type.Array(changeItemSchema, { description: "
|
|
72
|
-
path: Type.String({ description: "
|
|
71
|
+
changes: Type.Array(changeItemSchema, { description: "Array of edits. Each edit pairs content_lines (literal file content, one string per line) with hash_range_inclusive (inclusive [start_hash, end_hash] — pair of 3-char hashes from read output)." }),
|
|
72
|
+
path: Type.String({ description: "Path to edit" }),
|
|
73
73
|
},
|
|
74
74
|
{ additionalProperties: false },
|
|
75
75
|
);
|
|
@@ -78,7 +78,7 @@ export const flatEditToolSchema = Type.Object(
|
|
|
78
78
|
{
|
|
79
79
|
content_lines: contentLinesSchema,
|
|
80
80
|
hash_range_inclusive: hashRangeInclSchema,
|
|
81
|
-
path: Type.String({ description: "
|
|
81
|
+
path: Type.String({ description: "Path to edit" }),
|
|
82
82
|
},
|
|
83
83
|
{ additionalProperties: false },
|
|
84
84
|
);
|
|
@@ -297,8 +297,7 @@ const MODE_CFG = {
|
|
|
297
297
|
requestStructure: [
|
|
298
298
|
"Flat mode:", "```json", "{ \"content_lines\": [...], \"hash_range_inclusive\": [\"aB3\", \"xY7\"], \"path\": \"...\" }", "```",
|
|
299
299
|
].join("\n"),
|
|
300
|
-
prefix: "one edit per call
|
|
301
|
-
guidePrefix: "- Use `replace` with HASH anchors for all file changes. Only one edit per call.",
|
|
300
|
+
prefix: "performing one edit per call",
|
|
302
301
|
},
|
|
303
302
|
bulk: {
|
|
304
303
|
desc: "\n\nPut all operations on one file in a single `replace` call. Stack every region into the `changes` array, even when they are far apart. Anchors within one call must all come from the same pre-edit read; the runtime applies them atomically against that one snapshot.",
|
|
@@ -310,34 +309,21 @@ const MODE_CFG = {
|
|
|
310
309
|
"Bulk mode (default):", "```json", "{ \"changes\": [{ \"content_lines\": [...], \"hash_range_inclusive\": [\"aB3\", \"xY7\"] }], \"path\": \"...\" }", "```",
|
|
311
310
|
].join("\n"),
|
|
312
311
|
prefix: "batching all changes to a file in one call",
|
|
313
|
-
guidePrefix: "- Use `replace` with HASH anchors for all file changes; batch every change to one file into a single `replace` call.",
|
|
314
312
|
},
|
|
315
313
|
} as const;
|
|
316
314
|
|
|
317
315
|
export function buildToolDef(opts: { flat: boolean; autoRead?: boolean }): ToolDef {
|
|
318
316
|
const autoRead = opts.autoRead ?? false;
|
|
319
|
-
const readGuidance = autoRead
|
|
320
|
-
? "Anchors are provided automatically after write and replace operations when auto-read is enabled."
|
|
321
|
-
: "Call `read` to get fresh anchors for follow-up edits.";
|
|
322
317
|
|
|
323
318
|
const cfg = MODE_CFG[opts.flat ? "flat" : "bulk"];
|
|
324
319
|
|
|
325
|
-
const E_DESC = loadP("../prompts/replace.md"
|
|
326
|
-
MODE_DESCRIPTION: cfg.desc,
|
|
327
|
-
MODE_EXAMPLES: cfg.examples,
|
|
328
|
-
MODE_RULES: cfg.rules,
|
|
329
|
-
MODE_REQUEST_STRUCTURE: cfg.requestStructure,
|
|
330
|
-
AUTO_READ_GUIDANCE: readGuidance,
|
|
331
|
-
});
|
|
320
|
+
const E_DESC = loadP("../prompts/replace.md");
|
|
332
321
|
const E_SNIPPET = loadP("../prompts/replace-snippet.md", {
|
|
333
322
|
MODE_PREFIX: cfg.prefix,
|
|
334
323
|
});
|
|
335
|
-
const E_GUIDE = loadGuide("../prompts/replace-guidelines.md"
|
|
336
|
-
MODE_PREFIX: cfg.guidePrefix,
|
|
337
|
-
AUTO_READ_GUIDANCE: readGuidance,
|
|
338
|
-
});
|
|
324
|
+
const E_GUIDE = loadGuide("../prompts/replace-guidelines.md");
|
|
339
325
|
|
|
340
|
-
const parameters = editToolSchema;
|
|
326
|
+
const parameters = opts.flat ? flatEditToolSchema : editToolSchema;
|
|
341
327
|
|
|
342
328
|
return {
|
|
343
329
|
name: "replace",
|