pi-hashline-edit-pro 1.0.7 → 1.1.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 +13 -11
- package/index.ts +55 -51
- package/package.json +1 -1
- package/prompts/read-guidelines.md +1 -1
- package/prompts/replace-guidelines.md +2 -1
- package/src/file-kind.ts +0 -20
- package/src/fs-write.ts +0 -16
- package/src/hash-store.ts +0 -4
- package/src/hashline/parse.ts +6 -0
- package/src/read.ts +4 -7
- package/src/replace-response.ts +1 -1
- package/src/replace-undo.ts +2 -0
- package/src/replace.ts +1 -2
- package/src/validation.ts +0 -5
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Fork of [pi-hashline-edit](https://github.com/RimuruW/pi-hashline-edit) by Rimur
|
|
|
11
11
|
- **Stable anchors.** Editing one part of a file leaves the hashes of untouched lines unchanged, so anchors from earlier reads stay valid.
|
|
12
12
|
- **Autocorrection with warnings.** Unambiguous copy-paste mistakes — hash prefixes, diff-preview rows, reversed ranges — are fixed automatically and reported.
|
|
13
13
|
- **Safe writes.** Atomic temp-file-then-rename writes preserve permissions, BOMs, line endings, symlinks, and hard links.
|
|
14
|
-
- **Auto-read.** Fresh anchors are appended to the result of every `write
|
|
14
|
+
- **Auto-read.** Fresh anchors are appended to the result of every `write` that changes the file; after `replace` and `undo_last_replace`, the post-edit diff is shown instead.
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
@@ -47,7 +47,7 @@ kQm│}
|
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
3. Keep editing. Anchors for untouched lines remain valid across edits, so hashes from earlier reads keep working; changed lines get fresh anchors, which auto-read appends
|
|
50
|
+
3. Keep editing. Anchors for untouched lines remain valid across edits, so hashes from earlier reads keep working; changed lines get fresh anchors, which auto-read appends after each `write`.
|
|
51
51
|
|
|
52
52
|
## The `read` tool
|
|
53
53
|
|
|
@@ -90,7 +90,7 @@ Exactly one edit per call, with `hash_range_inclusive` and `content_lines` at th
|
|
|
90
90
|
| Field | Description |
|
|
91
91
|
| --- | --- |
|
|
92
92
|
| `hash_range_inclusive` | Pair of 3-char hashes from `read` output marking the first and last line of the range to replace (inclusive). |
|
|
93
|
-
| `content_lines` | Replacement content, one string per line. Use `[]` to delete the range. |
|
|
93
|
+
| `content_lines` | Replacement content, one string per line; entries must not contain line breaks. Use `[]` to delete the range. |
|
|
94
94
|
|
|
95
95
|
Behavior:
|
|
96
96
|
|
|
@@ -102,7 +102,7 @@ Behavior:
|
|
|
102
102
|
- A reversed range (start hash after end hash) is swapped and applied.
|
|
103
103
|
- A duplicated boundary line — the classic `}`, `});`, or `} else {` pasted twice — is silently removed; the duplicate never reaches the file.
|
|
104
104
|
- `file_path` is accepted as an alias for `path`; a JSON-string `content_lines` is parsed into an array.
|
|
105
|
-
- **Response.**
|
|
105
|
+
- **Response.** With auto-read enabled (the default), a successful edit returns the post-edit diff — the same `+HASH│` / `- │` / ` HASH│` rows the user sees — instead of the summary. With auto-read disabled, the edit reports `Successfully replaced in {path}. Added X line(s), removed Y line(s).` plus any warnings, and no diff is shown to the model. Warnings are appended in both modes. An edit that produces identical content reports `No changes made` and never rotates anchors. The post-edit diff is exposed to the host UI via `details.diff` — the TUI always shows it — and reaches the model-visible text only while auto-read is on.
|
|
106
106
|
- **Undo.** Every successful replace is undoable once via `undo_last_replace` — see [Undo](#undo).
|
|
107
107
|
|
|
108
108
|
## Anchor stability
|
|
@@ -118,13 +118,15 @@ A no-op replace never changes the file, so anchors remain valid. On first run af
|
|
|
118
118
|
|
|
119
119
|
## Auto-read
|
|
120
120
|
|
|
121
|
-
Enabled by default. After a successful `write
|
|
121
|
+
Enabled by default. After a successful `write` that changes the file, the extension reads the file and appends an `--- Auto-read (hashline anchors) ---` block to the result, so the model gets immediate `HASH│content` anchors without a separate `read` call.
|
|
122
122
|
|
|
123
|
-
-
|
|
123
|
+
- A no-op `replace` produces no diff — the file is unchanged, so existing anchors remain valid.
|
|
124
|
+
- After `replace` / `undo_last_replace`, the success summary is replaced by the post-edit diff (the same `+HASH│` / `- │` / ` HASH│` rows used for replace) plus any warnings, so the model sees the change like a git diff instead of line counts; no anchor block is appended — call `read` for fresh anchors.
|
|
125
|
+
- With auto-read disabled, `replace` / `undo_last_replace` results keep the plain summary in the model-visible text — no diff and no anchor block reach the model (the post-edit diff is still shown to the user).
|
|
124
126
|
- After `write`, the block dumps from the top of the file. For files over 2000 lines, the dump is truncated with a pagination hint — use `read` with `offset` to continue.
|
|
125
127
|
- Auto-read keeps a 50KB display budget: lines over 50KB are skipped with a marker instead of their content (use `read` for lines up to 200KB).
|
|
126
128
|
- Toggle at runtime with `/toggle-auto-read`; the setting persists across sessions.
|
|
127
|
-
- If the auto-read itself fails (e.g. the file was deleted between the
|
|
129
|
+
- If the auto-read itself fails (e.g. the file was deleted between the write and the read), a short `--- Auto-read failed: ... ---` notice is appended instead of the anchor block, so the model knows the anchors are missing.
|
|
128
130
|
|
|
129
131
|
## Undo
|
|
130
132
|
|
|
@@ -134,14 +136,14 @@ Enabled by default. After a successful `write`, `replace`, or `undo_last_replace
|
|
|
134
136
|
- History is persisted in the hash store (`~/.config/pi-hashline-edit-pro/hash-store.sqlite`) and survives session restarts; a failed `write` does not clear it.
|
|
135
137
|
- **Undo is a precondition, not a convenience.** The undo record is persisted *before* the edit is written; if it cannot be persisted, the `replace` is refused with `[E_UNDO_UNAVAILABLE]` and the file is not touched, so every applied edit is undoable. If the file write itself then fails, the previous undo record is restored, so a refused edit never destroys earlier undo history.
|
|
136
138
|
- A successful `write` clears the history for that file.
|
|
137
|
-
-
|
|
139
|
+
- With auto-read enabled, the model sees the post-edit diff after an undo, just like a replace; with auto-read disabled it sees the plain summary. No anchors are appended after an undo — call `read` to get fresh anchors for follow-up edits.
|
|
138
140
|
- **Safety guard.** If the file was modified or deleted since the last replace, `undo_last_replace` refuses with `[E_UNDO_STALE]` rather than overwriting those changes.
|
|
139
141
|
|
|
140
142
|
## Commands and configuration
|
|
141
143
|
|
|
142
144
|
| Command | Description |
|
|
143
145
|
| --- | --- |
|
|
144
|
-
| `/toggle-auto-read` | Toggle automatic hashline anchors after write and replace operations. Persists across sessions. |
|
|
146
|
+
| `/toggle-auto-read` | Toggle automatic hashline anchors after write and post-edit diffs after replace and undo_last_replace operations. Persists across sessions. |
|
|
145
147
|
|
|
146
148
|
Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created automatically when a setting is toggled:
|
|
147
149
|
|
|
@@ -155,7 +157,7 @@ Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created automatic
|
|
|
155
157
|
|
|
156
158
|
| Code | Meaning |
|
|
157
159
|
| --- | --- |
|
|
158
|
-
| `[E_BAD_SHAPE]` | Request envelope or edit item has unknown, missing, or wrongly-typed fields. |
|
|
160
|
+
| `[E_BAD_SHAPE]` | Request envelope or edit item has unknown, missing, or wrongly-typed fields, or a `content_lines` entry contains a line break. |
|
|
159
161
|
| `[E_BAD_REF]` | An anchor in `hash_range_inclusive` is not a bare 3-char hash. |
|
|
160
162
|
| `[E_STALE_ANCHOR]` | An anchor does not match any line in the current file; call `read` for fresh anchors. |
|
|
161
163
|
| `[E_AMBIGUOUS_ANCHOR]` | An anchor matches multiple lines; call `read` for fresh anchors. |
|
|
@@ -183,7 +185,7 @@ The alphabet is sized for an LLM consumer: the model tokenizes rather than squin
|
|
|
183
185
|
|
|
184
186
|
- **Stale anchors fail, per line.** A hash mismatch means that line's content changed since the last `read`. The error says so and, when only one anchor of a pair is stale, shows the current lines around the still-valid anchor so the range can be re-located without a full re-read. Mismatched anchors are never silently relocated to a "close enough" line — correctness over convenience.
|
|
185
187
|
- **Autocorrection only when the intent is unambiguous**, and always visible: hash-prefix and diff-row stripping produce a warning; the boundary-duplication fix is silent because the duplicate never reaches the file. Literal content is never silently altered when the intent is ambiguous (numbered deletion rows and unified-diff lines are written verbatim).
|
|
186
|
-
- **Byte-exact preservation.** UTF-8 BOMs, CRLF, LF, and CR-only line endings, file permissions, and trailing newlines survive edits and undo.
|
|
188
|
+
- **Byte-exact preservation.** UTF-8 BOMs, CRLF, LF, and CR-only line endings, file permissions, and trailing newlines survive edits and undo; files with mixed line endings are normalized to a single line ending on edit.
|
|
187
189
|
- **Atomic and ordered writes.** Files are written via temp-file-then-rename; symlink chains are resolved so the target is updated without replacing the symlink; hard-linked files are updated in place; concurrent edits to the same underlying file serialize through a per-target mutation queue.
|
|
188
190
|
- **One edit per call.** The request shape stays `{path, hash_range_inclusive, content_lines}` from schema through validation to application; there is no batching dialect.
|
|
189
191
|
|
package/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { regReplace } from "./src/replace";
|
|
|
5
5
|
import { regReplaceUndo, clearUndo } from "./src/replace-undo";
|
|
6
6
|
import { regRead, fmtReadPreview } from "./src/read";
|
|
7
7
|
import type { RMetrics } from "./src/replace-response";
|
|
8
|
-
import {
|
|
8
|
+
import { extractWarnings } from "./src/replace-render";
|
|
9
9
|
import { MAX_HASH_LINES } from "./src/hashline";
|
|
10
10
|
import {
|
|
11
11
|
readConfig,
|
|
@@ -17,7 +17,7 @@ import { toCwd } from "./src/paths";
|
|
|
17
17
|
import { resolveTarget } from "./src/fs-write";
|
|
18
18
|
|
|
19
19
|
export default function (pi: ExtensionAPI): void {
|
|
20
|
-
regRead(pi
|
|
20
|
+
regRead(pi);
|
|
21
21
|
|
|
22
22
|
regReplace(pi);
|
|
23
23
|
regReplaceUndo(pi);
|
|
@@ -37,25 +37,23 @@ export default function (pi: ExtensionAPI): void {
|
|
|
37
37
|
}
|
|
38
38
|
const config = await readConfig();
|
|
39
39
|
autoRead = config.autoRead;
|
|
40
|
-
regRead(pi, { autoRead });
|
|
41
|
-
|
|
42
40
|
if (debugValue === "1" || debugValue === "true") {
|
|
43
41
|
ctx.ui.notify(`Hashline Edit mode active`, "info");
|
|
44
42
|
}
|
|
45
43
|
});
|
|
46
44
|
|
|
47
45
|
pi.registerCommand("toggle-auto-read", {
|
|
48
|
-
description: "Toggle automatic hashline anchors after write and replace operations",
|
|
46
|
+
description: "Toggle automatic hashline anchors after write and post-edit diffs after replace and undo_last_replace operations",
|
|
49
47
|
handler: async (_args, ctx) => {
|
|
50
48
|
autoRead = await toggleAutoRead();
|
|
51
|
-
regRead(pi, { autoRead });
|
|
52
49
|
const state = autoRead ? "enabled" : "disabled";
|
|
53
|
-
ctx.ui.notify(`Auto-read
|
|
50
|
+
ctx.ui.notify(`Auto-read anchors (write) and post-edit diffs (replace/undo): ${state}`, "info");
|
|
54
51
|
},
|
|
55
52
|
});
|
|
56
53
|
|
|
57
54
|
pi.on("tool_result", async (event, ctx) => {
|
|
58
55
|
if (event.isError) return;
|
|
56
|
+
|
|
59
57
|
if (event.toolName === "write") {
|
|
60
58
|
const writtenPath = (event.input as Record<string, unknown>)?.path;
|
|
61
59
|
if (typeof writtenPath === "string") {
|
|
@@ -65,58 +63,64 @@ export default function (pi: ExtensionAPI): void {
|
|
|
65
63
|
console.error("Failed to clear undo after write:", error);
|
|
66
64
|
}
|
|
67
65
|
}
|
|
66
|
+
if (!autoRead) return;
|
|
67
|
+
if (typeof writtenPath !== "string") return;
|
|
68
|
+
try {
|
|
69
|
+
const { normalized, fileHashes, absolutePath } = await readNormFile(
|
|
70
|
+
writtenPath, ctx.cwd, { maxLines: MAX_HASH_LINES },
|
|
71
|
+
);
|
|
72
|
+
const preview = await fmtReadPreview(
|
|
73
|
+
normalized,
|
|
74
|
+
{},
|
|
75
|
+
fileHashes,
|
|
76
|
+
absolutePath,
|
|
77
|
+
DEFAULT_MAX_BYTES,
|
|
78
|
+
);
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
...(event.content ?? []),
|
|
82
|
+
{ type: "text", text: `\n\n--- Auto-read (hashline anchors) ---\n${preview.text}` },
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error("Auto-read after write failed:", error);
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
...(event.content ?? []),
|
|
91
|
+
{ type: "text", text: `\n\n--- Auto-read failed: ${message} ---` },
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
68
95
|
}
|
|
69
|
-
|
|
96
|
+
|
|
70
97
|
if (
|
|
71
|
-
event.toolName !== "write" &&
|
|
72
98
|
event.toolName !== "replace" &&
|
|
73
99
|
event.toolName !== "undo_last_replace"
|
|
74
100
|
) return;
|
|
75
|
-
|
|
76
|
-
if (typeof filePath !== "string") return;
|
|
101
|
+
if (!autoRead) return;
|
|
77
102
|
|
|
78
103
|
const metrics = (event.details as { metrics?: RMetrics } | undefined)?.metrics;
|
|
79
|
-
if (
|
|
104
|
+
if (metrics?.classification === "noop") return;
|
|
80
105
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
filePath, ctx.cwd, { maxLines: MAX_HASH_LINES },
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const changedLines =
|
|
87
|
-
event.toolName === "replace" || event.toolName === "undo_last_replace"
|
|
88
|
-
? metrics?.changed_lines
|
|
89
|
-
: undefined;
|
|
90
|
-
let offset: number | undefined;
|
|
91
|
-
let limit = AUTO_READ_MAX;
|
|
92
|
-
if (changedLines) {
|
|
93
|
-
offset = Math.max(1, changedLines.first - 2);
|
|
94
|
-
limit = Math.min(changedLines.last + 2 - offset + 1, AUTO_READ_MAX);
|
|
95
|
-
}
|
|
106
|
+
const diff = (event.details as { diff?: string } | undefined)?.diff;
|
|
107
|
+
if (!diff) return;
|
|
96
108
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
114
|
-
return {
|
|
115
|
-
content: [
|
|
116
|
-
...(event.content ?? []),
|
|
117
|
-
{ type: "text", text: `\n\n--- Auto-read failed: ${message} ---` },
|
|
118
|
-
],
|
|
119
|
-
};
|
|
120
|
-
}
|
|
109
|
+
const rendered = (event.content ?? [])
|
|
110
|
+
.filter(
|
|
111
|
+
(entry): entry is { type: "text"; text: string } =>
|
|
112
|
+
entry.type === "text" && typeof entry.text === "string",
|
|
113
|
+
)
|
|
114
|
+
.map((entry) => entry.text)
|
|
115
|
+
.join("\n");
|
|
116
|
+
const warnings = extractWarnings(rendered);
|
|
117
|
+
return {
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: "text",
|
|
121
|
+
text: warnings ? `${diff}\n\n${warnings}` : diff,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
|
121
125
|
});
|
|
122
126
|
}
|
package/package.json
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
- `read`: call before `replace` when you need fresh HASH anchors for a file.
|
|
2
|
-
|
|
2
|
+
- `read`: call again after any edit to that file — changed lines get new anchors.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
- `replace`: hash_range_inclusive must use only anchors from the most recent read of the same file.
|
|
2
2
|
- `replace`: content_lines is a native JSON array of strings — never a serialized JSON string. When copying a line from read output, remove its HASH│ prefix and keep the leading whitespace exactly as shown.
|
|
3
|
-
- `replace`: minimize the replaced range — anchor only the lines that actually change; for insertions use a single-line range (e.g. the line after the insertion point) instead of a whole block, so fewer unchanged lines must be reproduced byte-exact.
|
|
3
|
+
- `replace`: minimize the replaced range — anchor only the lines that actually change; for insertions use a single-line range (e.g. the line after the insertion point) instead of a whole block, so fewer unchanged lines must be reproduced byte-exact.
|
|
4
|
+
- `replace`: content_lines entries are single lines — never embed a line break inside an entry; pass each line as its own array entry.
|
package/src/file-kind.ts
CHANGED
|
@@ -39,12 +39,6 @@ function isTextType(mimeType: string): boolean {
|
|
|
39
39
|
return mimeType.startsWith("text/") || TEXT_TYPES.has(mimeType);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export type FKind =
|
|
43
|
-
| { kind: "directory" }
|
|
44
|
-
| { kind: "image"; mimeType: string }
|
|
45
|
-
| { kind: "text" }
|
|
46
|
-
| { kind: "binary"; description: string };
|
|
47
|
-
|
|
48
42
|
export type LFile =
|
|
49
43
|
| { kind: "directory" }
|
|
50
44
|
| { kind: "image"; mimeType: string }
|
|
@@ -149,17 +143,3 @@ export async function loadFileKindAndText(
|
|
|
149
143
|
await fileHandle.close();
|
|
150
144
|
}
|
|
151
145
|
}
|
|
152
|
-
|
|
153
|
-
export async function classifyFileKind(filePath: string): Promise<FKind> {
|
|
154
|
-
const loaded = await loadFileKindAndText(filePath);
|
|
155
|
-
switch (loaded.kind) {
|
|
156
|
-
case "directory":
|
|
157
|
-
return loaded;
|
|
158
|
-
case "image":
|
|
159
|
-
return loaded;
|
|
160
|
-
case "binary":
|
|
161
|
-
return loaded;
|
|
162
|
-
case "text":
|
|
163
|
-
return { kind: "text" };
|
|
164
|
-
}
|
|
165
|
-
}
|
package/src/fs-write.ts
CHANGED
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
rm,
|
|
10
10
|
stat,
|
|
11
11
|
writeFile,
|
|
12
|
-
copyFile,
|
|
13
|
-
chmod,
|
|
14
12
|
} from "fs/promises";
|
|
15
13
|
import { dirname, join, parse, resolve, sep } from "path";
|
|
16
14
|
import { errCode } from "./utils";
|
|
@@ -153,20 +151,6 @@ export async function writeAtomic(
|
|
|
153
151
|
await rename(tempPath, targetPath);
|
|
154
152
|
await syncDir(dir);
|
|
155
153
|
} catch (error: unknown) {
|
|
156
|
-
if (errCode(error) === "EXDEV") {
|
|
157
|
-
try {
|
|
158
|
-
await copyFile(tempPath, targetPath);
|
|
159
|
-
if (existingStats) {
|
|
160
|
-
await chmod(targetPath, existingStats.mode & 0o7777);
|
|
161
|
-
}
|
|
162
|
-
await rm(tempPath, { force: true });
|
|
163
|
-
await syncDir(dir);
|
|
164
|
-
return;
|
|
165
|
-
} catch {
|
|
166
|
-
try { await rm(tempPath, { force: true }); } catch {}
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
154
|
if (process.platform === "win32" && errCode(error) === "EPERM") {
|
|
171
155
|
try {
|
|
172
156
|
await writeFile(targetPath, content, "utf-8");
|
package/src/hash-store.ts
CHANGED
|
@@ -324,10 +324,6 @@ export function upsertSnapshot(
|
|
|
324
324
|
store.stmts.upsert(path, checksum, lineCount, JSON.stringify(hashes), Date.now());
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
export function deleteSnapshot(store: HashStore, path: string): void {
|
|
328
|
-
store.stmts.deleteOne(path);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
327
|
export function upsertUndo(store: HashStore, path: string, entry: UndoRecord): void {
|
|
332
328
|
store.stmts.undoUpsert(
|
|
333
329
|
path,
|
package/src/hashline/parse.ts
CHANGED
|
@@ -46,5 +46,11 @@ export function parseText(edit: string[] | string | null): string[] {
|
|
|
46
46
|
if (typeof edit === "string") {
|
|
47
47
|
throw new Error(CONTENT_LINES_NOT_STRING_MSG);
|
|
48
48
|
}
|
|
49
|
+
const lineBreakIndex = edit.findIndex((line) => /[\r\n]/.test(line));
|
|
50
|
+
if (lineBreakIndex >= 0) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`[E_BAD_SHAPE] "content_lines" entry at index ${lineBreakIndex} contains a \\r or \\n line break. Pass each line as its own array entry.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
49
55
|
return edit;
|
|
50
56
|
}
|
package/src/read.ts
CHANGED
|
@@ -21,11 +21,8 @@ const R_DESC = loadP("../prompts/read.md");
|
|
|
21
21
|
|
|
22
22
|
const R_SNIPPET = loadP("../prompts/read-snippet.md");
|
|
23
23
|
|
|
24
|
-
function readGuide(
|
|
25
|
-
|
|
26
|
-
return loadGuide("../prompts/read-guidelines.md", {
|
|
27
|
-
AUTO_READ_NOTE: autoRead ? "" : note,
|
|
28
|
-
});
|
|
24
|
+
function readGuide(): string[] {
|
|
25
|
+
return loadGuide("../prompts/read-guidelines.md");
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
function normPosInt(
|
|
@@ -148,13 +145,13 @@ export async function fmtReadPreview(
|
|
|
148
145
|
};
|
|
149
146
|
}
|
|
150
147
|
|
|
151
|
-
export function regRead(pi: ExtensionAPI
|
|
148
|
+
export function regRead(pi: ExtensionAPI): void {
|
|
152
149
|
pi.registerTool({
|
|
153
150
|
name: "read",
|
|
154
151
|
label: "Read",
|
|
155
152
|
description: R_DESC,
|
|
156
153
|
promptSnippet: R_SNIPPET,
|
|
157
|
-
promptGuidelines: readGuide(
|
|
154
|
+
promptGuidelines: readGuide(),
|
|
158
155
|
parameters: Type.Object({
|
|
159
156
|
path: Type.String({
|
|
160
157
|
description: "Path to the file to read (relative or absolute)",
|
package/src/replace-response.ts
CHANGED
|
@@ -126,7 +126,7 @@ export function buildChanged(input: SuccessInput): TResult {
|
|
|
126
126
|
const { path, result, warnings, snapshotId, originalNormalized, editMeta, resultHashes } = input;
|
|
127
127
|
|
|
128
128
|
const resultLines = visLines(result);
|
|
129
|
-
const diffResult = genDiff(originalNormalized, result,
|
|
129
|
+
const diffResult = genDiff(originalNormalized, result, 1, resultHashes);
|
|
130
130
|
const addedLines = editMeta.addedLines;
|
|
131
131
|
const removedLines = editMeta.removedLines;
|
|
132
132
|
const warningsBlock = warnBlock(warnings);
|
package/src/replace-undo.ts
CHANGED
|
@@ -157,6 +157,7 @@ export function regReplaceUndo(pi: ExtensionAPI): void {
|
|
|
157
157
|
const linesAddedByReplace = cntDiff(diffResult.diff, "+");
|
|
158
158
|
const linesRemovedByReplace = cntDiff(diffResult.diff, "-");
|
|
159
159
|
const restoredRange = changedRange(currentNormalized, undo.content);
|
|
160
|
+
const undoDiff = genDiff(currentNormalized, undo.content, 1, undo.hashes).diff;
|
|
160
161
|
|
|
161
162
|
await writeAtomic(
|
|
162
163
|
mutationTargetPath,
|
|
@@ -192,6 +193,7 @@ export function regReplaceUndo(pi: ExtensionAPI): void {
|
|
|
192
193
|
},
|
|
193
194
|
],
|
|
194
195
|
details: {
|
|
196
|
+
diff: undoDiff,
|
|
195
197
|
metrics: buildMetrics({
|
|
196
198
|
classification: "applied",
|
|
197
199
|
editsAttempted: 1,
|
package/src/replace.ts
CHANGED
|
@@ -47,7 +47,7 @@ import { loadHashStore, type HashStore } from "./hash-store";
|
|
|
47
47
|
|
|
48
48
|
const contentLinesSchema = Type.Array(Type.String(), {
|
|
49
49
|
description:
|
|
50
|
-
"Replacement content, one string per line. Use [] to delete the range."
|
|
50
|
+
"Replacement content, one string per line; entries must not contain line breaks. Use [] to delete the range."
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
const hashRangeInclSchema = Type.Array(
|
|
@@ -78,7 +78,6 @@ export type ReplaceDetails = {
|
|
|
78
78
|
firstChangedLine?: number;
|
|
79
79
|
snapshotId?: string;
|
|
80
80
|
classification?: "noop";
|
|
81
|
-
structureOutline?: string[];
|
|
82
81
|
metrics?: RMetrics;
|
|
83
82
|
};
|
|
84
83
|
|
package/src/validation.ts
CHANGED