@osovv/vv-opencode 1.2.7 → 1.2.8

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +29 -2
  3. package/dist/commands/patch-provider.js +2 -0
  4. package/dist/commands/patch-provider.js.map +1 -1
  5. package/dist/commands/plugin-list.js +8 -3
  6. package/dist/commands/plugin-list.js.map +1 -1
  7. package/dist/commands/plugin-toggle.js +8 -1
  8. package/dist/commands/plugin-toggle.js.map +1 -1
  9. package/dist/lib/plugin-toggle-config.d.ts +5 -1
  10. package/dist/lib/plugin-toggle-config.js +15 -10
  11. package/dist/lib/plugin-toggle-config.js.map +1 -1
  12. package/dist/lib/vvoc-config.d.ts +33 -1
  13. package/dist/lib/vvoc-config.js +49 -1
  14. package/dist/lib/vvoc-config.js.map +1 -1
  15. package/dist/plugins/hashline-edit/diff-summary.d.ts +7 -0
  16. package/dist/plugins/hashline-edit/diff-summary.js +79 -0
  17. package/dist/plugins/hashline-edit/diff-summary.js.map +1 -0
  18. package/dist/plugins/hashline-edit/edit-operation-primitives.js +22 -3
  19. package/dist/plugins/hashline-edit/edit-operation-primitives.js.map +1 -1
  20. package/dist/plugins/hashline-edit/index.js +315 -76
  21. package/dist/plugins/hashline-edit/index.js.map +1 -1
  22. package/dist/plugins/hashline-edit/normalize-edits.js +29 -25
  23. package/dist/plugins/hashline-edit/normalize-edits.js.map +1 -1
  24. package/dist/plugins/hashline-edit/replace-engine.d.ts +24 -0
  25. package/dist/plugins/hashline-edit/replace-engine.js +246 -0
  26. package/dist/plugins/hashline-edit/replace-engine.js.map +1 -0
  27. package/dist/plugins/hashline-edit/routing.d.ts +21 -0
  28. package/dist/plugins/hashline-edit/routing.js +113 -0
  29. package/dist/plugins/hashline-edit/routing.js.map +1 -0
  30. package/dist/plugins/hashline-edit/session-state.d.ts +23 -0
  31. package/dist/plugins/hashline-edit/session-state.js +70 -0
  32. package/dist/plugins/hashline-edit/session-state.js.map +1 -0
  33. package/dist/plugins/hashline-edit/str-replace-editor.d.ts +50 -0
  34. package/dist/plugins/hashline-edit/str-replace-editor.js +285 -0
  35. package/dist/plugins/hashline-edit/str-replace-editor.js.map +1 -0
  36. package/dist/plugins/hashline-edit/tool-description.d.ts +2 -1
  37. package/dist/plugins/hashline-edit/tool-description.js +31 -10
  38. package/dist/plugins/hashline-edit/tool-description.js.map +1 -1
  39. package/dist/plugins/hashline-edit/validation.d.ts +1 -0
  40. package/dist/plugins/hashline-edit/validation.js +13 -3
  41. package/dist/plugins/hashline-edit/validation.js.map +1 -1
  42. package/package.json +1 -1
  43. package/schemas/vvoc/v3.json +29 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.js","sourceRoot":"","sources":["../../../src/plugins/hashline-edit/routing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface SessionModelInfo {
2
+ providerID: string;
3
+ modelID: string;
4
+ }
5
+ export declare class SessionModelCache {
6
+ #private;
7
+ set(sessionID: string, model: SessionModelInfo): void;
8
+ get(sessionID: string): SessionModelInfo | undefined;
9
+ delete(sessionID: string): void;
10
+ clear(): void;
11
+ }
12
+ export interface FileSnapshot {
13
+ mtimeMs: number;
14
+ size: number;
15
+ }
16
+ export type FileCacheVerdict = "unread" | "fresh" | "drifted";
17
+ export declare class SessionFileCache {
18
+ #private;
19
+ record(sessionID: string, filePath: string, snapshot: FileSnapshot): void;
20
+ check(sessionID: string, filePath: string, current: FileSnapshot | undefined): FileCacheVerdict;
21
+ forget(sessionID: string, filePath: string): void;
22
+ clear(): void;
23
+ }
@@ -0,0 +1,70 @@
1
+ // FILE: src/plugins/hashline-edit/session-state.ts
2
+ // VERSION: 0.1.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Track per-session model identity and per-session file snapshots for edit-mode routing and stale-write protection.
5
+ // SCOPE: Session model cache keyed by sessionID and session file cache comparing recorded mtime/size snapshots against current file state.
6
+ // DEPENDS: [none]
7
+ // LINKS: [M-PLUGIN-HASHLINE-EDIT]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // SessionModelInfo - Provider and model identifiers resolved for a session.
14
+ // SessionModelCache - Per-session model identity store populated from chat.message.
15
+ // FileSnapshot - Recorded mtime and size for a file at read or view time.
16
+ // FileCacheVerdict - Outcome of comparing a current file state against a recorded snapshot.
17
+ // SessionFileCache - Per-session file snapshot store used for prior-read and view-freshness checks.
18
+ // END_MODULE_MAP
19
+ //
20
+ // START_CHANGE_SUMMARY
21
+ // LAST_CHANGE: [v0.1.0 - Established session model cache and session file cache with drift detection.]
22
+ // END_CHANGE_SUMMARY
23
+ export class SessionModelCache {
24
+ #models = new Map();
25
+ set(sessionID, model) {
26
+ this.#models.set(sessionID, { providerID: model.providerID, modelID: model.modelID });
27
+ }
28
+ get(sessionID) {
29
+ return this.#models.get(sessionID);
30
+ }
31
+ delete(sessionID) {
32
+ this.#models.delete(sessionID);
33
+ }
34
+ clear() {
35
+ this.#models.clear();
36
+ }
37
+ }
38
+ export class SessionFileCache {
39
+ #files = new Map();
40
+ #key(sessionID, filePath) {
41
+ return `${sessionID}\u0000${filePath}`;
42
+ }
43
+ record(sessionID, filePath, snapshot) {
44
+ this.#files.set(this.#key(sessionID, filePath), {
45
+ mtimeMs: snapshot.mtimeMs,
46
+ size: snapshot.size,
47
+ });
48
+ }
49
+ check(sessionID, filePath, current) {
50
+ const recorded = this.#files.get(this.#key(sessionID, filePath));
51
+ if (recorded === undefined) {
52
+ return "unread";
53
+ }
54
+ if (current === undefined) {
55
+ return "drifted";
56
+ }
57
+ if (current.mtimeMs !== recorded.mtimeMs || current.size !== recorded.size) {
58
+ return "drifted";
59
+ }
60
+ return "fresh";
61
+ }
62
+ forget(sessionID, filePath) {
63
+ this.#files.delete(this.#key(sessionID, filePath));
64
+ }
65
+ clear() {
66
+ this.#files.clear();
67
+ }
68
+ }
69
+ // END_BLOCK_FILE_CACHE
70
+ //# sourceMappingURL=session-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-state.js","sourceRoot":"","sources":["../../../src/plugins/hashline-edit/session-state.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,iBAAiB;AACjB,wBAAwB;AACxB,+HAA+H;AAC/H,6IAA6I;AAC7I,oBAAoB;AACpB,oCAAoC;AACpC,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,8EAA8E;AAC9E,sFAAsF;AACtF,4EAA4E;AAC5E,8FAA8F;AAC9F,sGAAsG;AACtG,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,yGAAyG;AACzG,qBAAqB;AAQrB,MAAM,OAAO,iBAAiB;IACnB,OAAO,GAAG,IAAI,GAAG,EAA4B,CAAC;IAEvD,GAAG,CAAC,SAAiB,EAAE,KAAuB;QAC5C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,GAAG,CAAC,SAAiB;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,CAAC,SAAiB;QACtB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;CACF;AAWD,MAAM,OAAO,gBAAgB;IAClB,MAAM,GAAG,IAAI,GAAG,EAAwB,CAAC;IAElD,IAAI,CAAC,SAAiB,EAAE,QAAgB;QACtC,OAAO,GAAG,SAAS,SAAS,QAAQ,EAAE,CAAC;IACzC,CAAC;IAED,MAAM,CAAC,SAAiB,EAAE,QAAgB,EAAE,QAAsB;QAChE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE;YAC9C,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,IAAI,EAAE,QAAQ,CAAC,IAAI;SACpB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,SAAiB,EAAE,QAAgB,EAAE,OAAiC;QAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;QACjE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,OAAO,CAAC,OAAO,KAAK,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC3E,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,CAAC,SAAiB,EAAE,QAAgB;QACxC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;CACF;AACD,uBAAuB"}
@@ -0,0 +1,50 @@
1
+ import type { FileCacheVerdict, FileSnapshot } from "./session-state.js";
2
+ export declare const STR_REPLACE_TRUNCATION_MARKER = "<response clipped><NOTE>To save on context only part of this file was been shown to you. You should retry this tool after you have searched inside the file with `grep -n` in order to find the line numbers of what you are looking for.</NOTE>";
3
+ export declare const STR_REPLACE_EDITOR_DESCRIPTION: string;
4
+ export interface StrReplaceEditorArgs {
5
+ command: "view" | "create" | "str_replace" | "insert";
6
+ path: string;
7
+ file_text?: string;
8
+ old_str?: string;
9
+ new_str?: string;
10
+ insert_line?: number;
11
+ view_range?: number[];
12
+ }
13
+ export type StrReplaceEditorResult = {
14
+ ok: true;
15
+ output: string;
16
+ } | {
17
+ ok: false;
18
+ error: string;
19
+ };
20
+ export interface StrReplaceEditorFsEntry {
21
+ name: string;
22
+ type: "file" | "directory";
23
+ }
24
+ export interface StrReplaceEditorFs {
25
+ exists(path: string): Promise<boolean>;
26
+ isDirectory(path: string): Promise<boolean>;
27
+ readText(path: string): Promise<string>;
28
+ writeText(path: string, content: string): Promise<void>;
29
+ listDir(path: string): Promise<StrReplaceEditorFsEntry[]>;
30
+ stat(path: string): Promise<FileSnapshot | undefined>;
31
+ }
32
+ export interface StrReplaceEditorOptions {
33
+ fs: StrReplaceEditorFs;
34
+ maxOutputChars?: number;
35
+ onViewed?: (path: string, snapshot: FileSnapshot) => void;
36
+ checkFreshness?: (path: string, current: FileSnapshot | undefined) => FileCacheVerdict;
37
+ }
38
+ export declare class StrReplaceEditor {
39
+ private readonly options;
40
+ private readonly maxOutputChars;
41
+ constructor(options: StrReplaceEditorOptions);
42
+ execute(args: StrReplaceEditorArgs): Promise<StrReplaceEditorResult>;
43
+ private statExisting;
44
+ private view;
45
+ private formatFileView;
46
+ private listDirectory;
47
+ private create;
48
+ private strReplace;
49
+ private insert;
50
+ }
@@ -0,0 +1,285 @@
1
+ // FILE: src/plugins/hashline-edit/str-replace-editor.ts
2
+ // VERSION: 0.1.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Implement the DeepSeek dsh str_replace_editor contract (view/create/str_replace/insert) as a native edit profile.
5
+ // SCOPE: Command dispatch, dsh-verbatim view formatting and error texts, exact-verbatim str_replace matching with occurrence line numbers, insert_line validation, create guard, absolute-path hint, directory listing, output truncation, and view-cache freshness checks.
6
+ // DEPENDS: [src/plugins/hashline-edit/session-state.ts]
7
+ // LINKS: [M-PLUGIN-HASHLINE-EDIT]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // STR_REPLACE_EDITOR_DESCRIPTION - Model-facing dsh tool description (verbatim, MIT attribution).
14
+ // STR_REPLACE_TRUNCATION_MARKER - dsh response-clipped marker appended to truncated view output.
15
+ // StrReplaceEditorArgs - Tool-facing command arguments.
16
+ // StrReplaceEditorResult - Ok output or fail-closed error text.
17
+ // StrReplaceEditorFs - Filesystem seam used by the editor (node:fs-backed in production, fake in tests).
18
+ // StrReplaceEditorFsEntry - Directory entry name and type returned by the filesystem seam.
19
+ // StrReplaceEditorOptions - Editor options: filesystem seam, output cap, and view-cache callbacks.
20
+ // StrReplaceEditor - Executes str_replace_editor commands with dsh semantics and view-cache freshness checks.
21
+ // END_MODULE_MAP
22
+ //
23
+ // START_CHANGE_SUMMARY
24
+ // LAST_CHANGE: [v0.1.0 - Ported the dsh str_replace_editor contract (deepseek-ai/deepseek-harness @ 47f94385, MIT) with an mtime/size view-cache as the replaceIfVersion CAS equivalent.]
25
+ // END_CHANGE_SUMMARY
26
+ // START_BLOCK_CONSTANTS
27
+ // Verbatim from deepseek-ai/deepseek-harness (MIT).
28
+ export const STR_REPLACE_TRUNCATION_MARKER = "<response clipped><NOTE>To save on context only part of this file was been shown to you. You should retry this tool after you have searched inside the file with `grep -n` in order to find the line numbers of what you are looking for.</NOTE>";
29
+ export const STR_REPLACE_EDITOR_DESCRIPTION = `
30
+ Custom editing tool for viewing, creating and editing files
31
+ * State is persistent across command calls and discussions with the user
32
+ * If \`path\` is a file, \`view\` displays the result of applying \`cat -n\`. If \`path\` is a directory, \`view\` lists non-hidden files and directories up to 2 levels deep
33
+ * The \`create\` command cannot be used if the specified \`path\` already exists as a file
34
+ * If a \`command\` generates a long output, it will be truncated and marked with \`<response clipped>\`
35
+
36
+ Notes for using the \`str_replace\` command:
37
+ * The \`old_str\` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!
38
+ * If the \`old_str\` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in \`old_str\` to make it unique
39
+ * The \`new_str\` parameter should contain the edited lines that should replace the \`old_str\`
40
+ `.trim();
41
+ const DEFAULT_MAX_OUTPUT_CHARS = 16_000;
42
+ // END_BLOCK_TYPES
43
+ // START_BLOCK_HELPERS
44
+ function ok(output) {
45
+ return { ok: true, output };
46
+ }
47
+ function fail(error) {
48
+ return { ok: false, error };
49
+ }
50
+ function maybeTruncate(content, maxOutputChars) {
51
+ return content.length <= maxOutputChars
52
+ ? content
53
+ : content.slice(0, maxOutputChars) + STR_REPLACE_TRUNCATION_MARKER;
54
+ }
55
+ function matchOffsets(content, search) {
56
+ const offsets = [];
57
+ let offset = 0;
58
+ while (true) {
59
+ const match = content.indexOf(search, offset);
60
+ if (match < 0) {
61
+ return offsets;
62
+ }
63
+ offsets.push(match);
64
+ offset = match + search.length;
65
+ }
66
+ }
67
+ function lineNumbersAt(content, offsets) {
68
+ let line = 1;
69
+ let cursor = 0;
70
+ return offsets.map((offset) => {
71
+ while (cursor < offset) {
72
+ if (content[cursor] === "\n") {
73
+ line += 1;
74
+ }
75
+ cursor += 1;
76
+ }
77
+ return line;
78
+ });
79
+ }
80
+ function codepointCompare(left, right) {
81
+ return left < right ? -1 : left > right ? 1 : 0;
82
+ }
83
+ // END_BLOCK_HELPERS
84
+ // START_BLOCK_EDITOR
85
+ export class StrReplaceEditor {
86
+ options;
87
+ maxOutputChars;
88
+ constructor(options) {
89
+ this.options = options;
90
+ this.maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
91
+ }
92
+ async execute(args) {
93
+ const path = args.path;
94
+ if (typeof path !== "string" || path.trim().length === 0) {
95
+ return fail("path must be a non-empty string");
96
+ }
97
+ if (!path.startsWith("/")) {
98
+ return fail(`The path ${path} is not an absolute path, it should start with \`/\`. Maybe you meant /${path}?`);
99
+ }
100
+ switch (args.command) {
101
+ case "view":
102
+ return this.view(path, args.view_range);
103
+ case "create":
104
+ return this.create(path, args.file_text);
105
+ case "str_replace":
106
+ return this.strReplace(path, args.old_str, args.new_str);
107
+ case "insert":
108
+ return this.insert(path, args.insert_line, args.new_str);
109
+ default:
110
+ return fail(`Unknown command: ${String(args.command)}`);
111
+ }
112
+ }
113
+ async statExisting(path, command) {
114
+ const exists = await this.options.fs.exists(path);
115
+ if (!exists) {
116
+ return fail(`The path ${path} does not exist. Please provide a valid path.`);
117
+ }
118
+ const isDir = await this.options.fs.isDirectory(path);
119
+ if (isDir && command !== "view") {
120
+ return fail(`The path ${path} is a directory and only the \`view\` command can be used on directories`);
121
+ }
122
+ const snapshot = (await this.options.fs.stat(path)) ?? { mtimeMs: 0, size: 0 };
123
+ return { snapshot, isDir };
124
+ }
125
+ async view(path, viewRange) {
126
+ const stated = await this.statExisting(path, "view");
127
+ if ("ok" in stated) {
128
+ return stated;
129
+ }
130
+ if (stated.isDir) {
131
+ if (viewRange !== undefined) {
132
+ return fail("The `view_range` parameter is not allowed when `path` points to a directory.");
133
+ }
134
+ return this.listDirectory(path);
135
+ }
136
+ const content = await this.options.fs.readText(path);
137
+ let formatted;
138
+ try {
139
+ formatted = this.formatFileView(path, content, viewRange);
140
+ }
141
+ catch (error) {
142
+ return fail(error instanceof Error ? error.message : String(error));
143
+ }
144
+ this.options.onViewed?.(path, stated.snapshot);
145
+ return ok(formatted);
146
+ }
147
+ formatFileView(path, content, viewRange) {
148
+ const allLines = content.split("\n");
149
+ let lines = allLines;
150
+ let initialLine = 1;
151
+ let finalLine;
152
+ let prompt = `Here's the content of ${path} with line numbers (which has a total of ${allLines.length} lines)`;
153
+ if (viewRange !== undefined) {
154
+ const [requestedInitialLine, requestedFinalLine] = viewRange;
155
+ if (viewRange.length !== 2 ||
156
+ requestedInitialLine === undefined ||
157
+ requestedFinalLine === undefined ||
158
+ !viewRange.every(Number.isInteger)) {
159
+ throw new Error("Invalid `view_range`. It should be a list of two integers.");
160
+ }
161
+ initialLine = requestedInitialLine;
162
+ finalLine = requestedFinalLine;
163
+ if (initialLine < 1 || initialLine > allLines.length) {
164
+ throw new Error(`Invalid \`view_range\`: [${viewRange.join(", ")}]. Its first element \`${initialLine}\` should be within the range of lines of the file: [1, ${allLines.length}]`);
165
+ }
166
+ if (finalLine > allLines.length) {
167
+ throw new Error(`Invalid \`view_range\`: [${viewRange.join(", ")}]. Its second element \`${finalLine}\` should be smaller than the number of lines in the file: \`${allLines.length}\``);
168
+ }
169
+ if (finalLine !== -1 && finalLine < initialLine) {
170
+ throw new Error(`Invalid \`view_range\`: [${viewRange.join(", ")}]. Its second element \`${finalLine}\` should be larger or equal than its first \`${initialLine}\``);
171
+ }
172
+ lines =
173
+ finalLine === -1
174
+ ? allLines.slice(initialLine - 1)
175
+ : allLines.slice(initialLine - 1, finalLine);
176
+ prompt += ` with view_range=[${initialLine}, ${finalLine}]`;
177
+ }
178
+ const numbered = lines
179
+ .map((line, index) => `${String(initialLine + index).padStart(6, " ")} ${line}`)
180
+ .join("\n");
181
+ return maybeTruncate(`${prompt}:\n${numbered}\n`, this.maxOutputChars);
182
+ }
183
+ async listDirectory(path) {
184
+ const visit = async (dir, depth) => {
185
+ const entries = await this.options.fs.listDir(dir);
186
+ const rows = [];
187
+ for (const entry of entries.filter((candidate) => !candidate.name.startsWith(".") &&
188
+ candidate.name !== "node_modules" &&
189
+ candidate.name !== "__pycache__")) {
190
+ const entryPath = `${dir}/${entry.name}`;
191
+ const type = entry.type === "directory" ? "d" : "f";
192
+ rows.push(`${type}\t${entryPath}`);
193
+ if (entry.type === "directory" && depth < 2) {
194
+ rows.push(...(await visit(entryPath, depth + 1)));
195
+ }
196
+ }
197
+ return rows;
198
+ };
199
+ const rows = [`d\t${path}`, ...(await visit(path, 1))];
200
+ rows.sort((left, right) => codepointCompare(left.slice(left.indexOf("\t") + 1), right.slice(right.indexOf("\t") + 1)));
201
+ const listing = maybeTruncate(rows.join("\n") + "\n", this.maxOutputChars);
202
+ return ok(`Here're the files and directories up to 2 levels deep in ${path}, excluding hidden items, node_modules, and Python cache directories:\n${listing}\n`);
203
+ }
204
+ async create(path, fileText) {
205
+ if (fileText === undefined) {
206
+ return fail("Parameter `file_text` is required for command: create");
207
+ }
208
+ if (await this.options.fs.exists(path)) {
209
+ return fail(`File already exists at: ${path}. Cannot overwrite files using command \`create\`.`);
210
+ }
211
+ await this.options.fs.writeText(path, fileText);
212
+ const snapshot = await this.options.fs.stat(path);
213
+ if (snapshot) {
214
+ this.options.onViewed?.(path, snapshot);
215
+ }
216
+ return ok(`New file created successfully at: ${path}`);
217
+ }
218
+ async strReplace(path, oldStr, newStr) {
219
+ if (oldStr === undefined) {
220
+ return fail("Parameter `old_str` is required for command: str_replace");
221
+ }
222
+ if (oldStr.length === 0) {
223
+ return fail("Parameter `old_str` is empty for command: str_replace");
224
+ }
225
+ const stated = await this.statExisting(path, "str_replace");
226
+ if ("ok" in stated) {
227
+ return stated;
228
+ }
229
+ const drift = this.options.checkFreshness?.(path, stated.snapshot);
230
+ if (drift === "drifted") {
231
+ return fail(`The file ${path} has changed since it was last viewed. Run the view command again before editing.`);
232
+ }
233
+ const before = await this.options.fs.readText(path);
234
+ const offsets = matchOffsets(before, oldStr);
235
+ if (offsets.length === 0) {
236
+ return fail(`No replacement was performed, old_str \`${oldStr}\` did not appear verbatim in ${path}.`);
237
+ }
238
+ if (offsets.length > 1) {
239
+ const lines = lineNumbersAt(before, offsets);
240
+ return fail(`No replacement was performed. Multiple occurrences of old_str \`${oldStr}\` in lines [${lines.join(", ")}]. Please ensure it is unique`);
241
+ }
242
+ const offset = offsets[0];
243
+ const newValue = newStr ?? "";
244
+ await this.options.fs.writeText(path, before.slice(0, offset) + newValue + before.slice(offset + oldStr.length));
245
+ const snapshot = await this.options.fs.stat(path);
246
+ if (snapshot) {
247
+ this.options.onViewed?.(path, snapshot);
248
+ }
249
+ return ok(`The file ${path} has been edited successfully.`);
250
+ }
251
+ async insert(path, insertLine, newStr) {
252
+ if (insertLine === undefined) {
253
+ return fail("Parameter `insert_line` is required for command: insert");
254
+ }
255
+ if (newStr === undefined) {
256
+ return fail("Parameter `new_str` is required for command: insert");
257
+ }
258
+ const stated = await this.statExisting(path, "insert");
259
+ if ("ok" in stated) {
260
+ return stated;
261
+ }
262
+ const drift = this.options.checkFreshness?.(path, stated.snapshot);
263
+ if (drift === "drifted") {
264
+ return fail(`The file ${path} has changed since it was last viewed. Run the view command again before editing.`);
265
+ }
266
+ const before = await this.options.fs.readText(path);
267
+ const lines = before.split("\n");
268
+ if (!Number.isInteger(insertLine) || insertLine < 0 || insertLine > lines.length) {
269
+ return fail(`Invalid \`insert_line\` parameter: ${insertLine}. It should be within the range of lines of the file: [0, ${lines.length}]`);
270
+ }
271
+ const after = [
272
+ ...lines.slice(0, insertLine),
273
+ ...newStr.split("\n"),
274
+ ...lines.slice(insertLine),
275
+ ].join("\n");
276
+ await this.options.fs.writeText(path, after);
277
+ const snapshot = await this.options.fs.stat(path);
278
+ if (snapshot) {
279
+ this.options.onViewed?.(path, snapshot);
280
+ }
281
+ return ok(`The file ${path} has been edited successfully.`);
282
+ }
283
+ }
284
+ // END_BLOCK_EDITOR
285
+ //# sourceMappingURL=str-replace-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"str-replace-editor.js","sourceRoot":"","sources":["../../../src/plugins/hashline-edit/str-replace-editor.ts"],"names":[],"mappings":"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"}
@@ -1 +1,2 @@
1
- export declare const HASHLINE_EDIT_DESCRIPTION = "Edit files using exact hash-anchored line references from the latest Read output.\n\n<must>\n1. SNAPSHOT: All edits in one call reference the ORIGINAL file state. Do NOT adjust line numbers for prior edits in the same batch \u2014 the system applies them bottom-up automatically.\n2. replace replaces ONE line at pos. It does NOT accept end. For multi-line replacement, use replace_range.\n3. replace_range with pos+end replaces ALL lines FROM pos THROUGH end (BOTH INCLUSIVE). The end line WILL BE replaced. If you set end to a line that belongs to the next function/method/statement, that line is DELETED.\n CORRECT: pos on the first line to replace, end on the LAST line to replace \u2014 not the line after.\n4. lines must contain ONLY the content that belongs inside the replaced range. Lines AFTER end survive unchanged \u2014 do NOT include them in lines. If you do, they will appear twice.\n INSERTION SAFETY: To add a function, handler, statement, or block NEXT TO existing code, use append/prepend anchored to a surviving line. To add code AFTER a closed block, append after its final structural closing line. Do NOT replace that closing line merely to reproduce it and add content after it.\n STRUCTURAL CLOSURES: If replace_range intentionally consumes lines containing closing syntax such as }, });, ], ), );, or </tag>, lines MUST include every closure required by the resulting code. Never assume the tool reconstructs omitted closing syntax.\n5. Tags MUST be copied exactly from read output or >>> mismatch output. NEVER guess or reconstruct tags.\n6. Batch = multiple operations in edits[], NOT one big replace covering everything. Each operation targets the smallest possible change.\n7. lines must contain plain replacement text only (no LINE#HASH#ANCHOR| prefixes, no diff + markers).\n8. CRITICAL: Double-quote characters inside lines strings MUST be escaped as \\\". Unescaped \\\" inside a JSON string will break the parser. Example: [\"const x = \\\"hello\\\";\"] \u2014 NOT [\"const x = \"hello\";\"]. Single quotes and backticks do NOT need escaping.\n9. LITERAL APPLICATION: payloads are applied byte-for-byte. There is NO merging, splitting, indentation fixing, or other content rewriting. The only automatic removals are exact duplicate echoes, and each one is reported as a Warning line in the result.\n10. PHYSICAL LINES: every string[] entry in lines must be exactly one physical line. Entries with embedded newlines are rejected. A string value is split on newlines.\n11. DELETION: to delete lines use lines: [] or lines: null with replace/replace_range. lines: [\"\"] is REJECTED because a single blank line is ambiguous \u2014 use append/prepend when you really want to insert a blank line.\n12. VERIFY: successful results include a bounded diff (@@ block) of what changed. Check that it matches your intent before moving on.\n</must>\n\n<operations>\nANCHOR FORMAT:\n Each anchor is `{line_number}#{hash_id}#{anchor_hash}` from read output like `42#VK#AB|content`.\n Backward-compatible `{line}#{hash}` also accepted.\n\nOPERATION CHOICE:\n replace with pos -> replace ONE line at pos (end is rejected)\n replace_range with pos+end -> replace range pos..end INCLUSIVE (both lines replaced); use only when those existing lines must change or be removed\n append with pos -> insert lines AFTER the anchored line; preferred for adding code after a block's final }, });, ], or other closing line\n prepend with pos -> insert lines BEFORE the anchored line; preferred for adding code before an existing declaration or block\n append/prepend without pos -> EOF/BOF insertion (also creates missing files)\n\nCONTENT FORMAT:\n lines: string (single line) or string[] (multi-line, preferred); each array entry is exactly one physical line\n lines: null or lines: [] with replace/replace_range -> DELETE those lines\n lines: [\"\"] with replace/replace_range -> REJECTED (ambiguous); use [] / null to delete, append/prepend to insert blank lines\n\nFILE MODES:\n delete=true deletes file and requires edits=[] with no rename\n rename moves final content to a new path and removes old path\n</operations>\n\n<examples>\nGiven this file after read:\n 10#VK#AB|function hello() {\n 11#XJ#CD| console.log(\"hi\");\n 12#MB#EF| console.log(\"bye\");\n 13#QR#GH|} // end of hello()\n\nSingle-line replace (change line 11):\n { op: \"replace\", pos: \"11#XJ#CD\", lines: [\" console.log(\\\"hello\\\");\"] }\n Result: line 11 replaced. Lines 10, 12-13 unchanged.\n\nRange replace (replace lines 11-12, function body):\n { op: \"replace_range\", pos: \"11#XJ#CD\", end: \"12#MB#EF\", lines: [\" return \\\"hello world\\\";\"] }\n Result: lines 11-12 removed, replaced by 1 new line. Lines 10, 13 unchanged.\n\nBAD - end is one line too far (DELETES closing brace):\n { op: \"replace_range\", pos: \"11#XJ#CD\", end: \"13#QR#GH\", lines: [\" return \\\"hello world\\\";\"] }\n Result: line 13 (closing brace) is REPLACED too \u2014 function is broken!\n CORRECT: use end: \"12#MB#EF\" \u2014 only replace lines 11-12, keep line 13 unchanged.\n\nBAD - lines extend past end (DUPLICATES line 13):\n { op: \"replace_range\", pos: \"11#XJ#CD\", end: \"12#MB#EF\", lines: [\" return \\\"hi\\\";\", \"}\"] }\n Line 13 is \"}\" which already exists after end. Including it in lines duplicates it.\n CORRECT: { op: \"replace_range\", pos: \"11#XJ#CD\", end: \"12#MB#EF\", lines: [\" return \\\"hi\\\";\"] }\n\nAppend after a line (insert between functions):\n { op: \"append\", pos: \"13#QR#GH\", lines: [\"\", \"function added() {\", \" return true;\", \"}\"] }\n Result: 4 lines inserted after line 13. All existing lines unchanged.\n\nBAD - replacing a closing line only to insert after it:\n { op: \"replace_range\", pos: \"13#QR#GH\", end: \"13#QR#GH\", lines: [\"} // end of hello()\", \"\", \"function added() {\", \"}\"] }\n This unnecessarily consumes the existing closing line. If that closure is omitted or altered, hello() becomes unbalanced.\n CORRECT: { op: \"append\", pos: \"13#QR#GH\", lines: [\"\", \"function added() {\", \"}\"] }\n\nWhen a structural rewrite really changes both opening and closing syntax:\n Use replace_range covering the complete structure, and include the required replacement closures explicitly. Re-read the edited region or run the narrow syntax/type check immediately afterward.\n</examples>\n\n<auto>\nBuilt-in normalization (you do NOT need to handle these):\n BOM and CRLF line endings are preserved automatically.\n Hashline prefixes and diff + markers copied into text are auto-stripped.\n Exact duplicate echoes are dropped and reported as Warning lines: a payload line identical to the append/prepend anchor line, or a range payload longer than its range whose first/last line exactly duplicates a surviving neighbor.\nEverything else is applied literally \u2014 no merging, splitting, or indentation fixes.\n</auto>\n\nRecovery:\n- If you get a hash mismatch error, copy the updated anchors shown in that error or re-read the file before retrying.";
1
+ export declare const HASHLINE_EDIT_DESCRIPTION = "Edit files using exact hash-anchored line references from the latest Read output.\n\n<must>\n1. SNAPSHOT: All edits in one call reference the ORIGINAL file state. Do NOT adjust line numbers for prior edits in the same batch \u2014 the system applies them bottom-up automatically.\n2. replace with pos replaces ONE line. Add an end anchor to replace the INCLUSIVE range pos..end in a single operation (replace_range is an alias that requires pos + end).\n3. For range replacement, the end line WILL BE replaced. If you set end to a line that belongs to the next function/method/statement, that line is DELETED.\n CORRECT: pos on the first line to replace, end on the LAST line to replace \u2014 not the line after.\n4. lines must contain ONLY the content that belongs inside the replaced range. Lines AFTER end survive unchanged \u2014 do NOT include them in lines. If you do, they will appear twice.\n INSERTION SAFETY: To add a function, handler, statement, or block NEXT TO existing code, use append/prepend anchored to a surviving line. To add code AFTER a closed block, append after its final structural closing line. Do NOT replace that closing line merely to reproduce it and add content after it.\n STRUCTURAL CLOSURES: If replace_range intentionally consumes lines containing closing syntax such as }, });, ], ), );, or </tag>, lines MUST include every closure required by the resulting code. Never assume the tool reconstructs omitted closing syntax.\n5. Tags MUST be copied exactly from read output or >>> mismatch output. NEVER guess or reconstruct tags.\n6. Batch = multiple operations in edits[], NOT one big replace covering everything. Each operation targets the smallest possible change.\n7. lines must contain plain replacement text only (no LINE#HASH#ANCHOR| prefixes, no diff + markers).\n8. CRITICAL: Double-quote characters inside lines strings MUST be escaped as \\\". Unescaped \\\" inside a JSON string will break the parser. Example: [\"const x = \\\"hello\\\";\"] \u2014 NOT [\"const x = \"hello\";\"]. Single quotes and backticks do NOT need escaping.\n9. LITERAL APPLICATION: payloads are applied byte-for-byte. There is NO merging, splitting, indentation fixing, or other content rewriting. The only automatic removals are exact duplicate echoes, and each one is reported as a Warning line in the result.\n10. PHYSICAL LINES: every string[] entry in lines must be exactly one physical line. Entries with embedded newlines are rejected. A string value is split on newlines.\n11. DELETION: to delete lines use lines: [] or lines: null with replace/replace_range. lines: [\"\"] is REJECTED because a single blank line is ambiguous \u2014 use append/prepend when you really want to insert a blank line.\n12. VERIFY: successful results include a bounded diff (@@ block) of what changed. Check that it matches your intent before moving on.\n</must>\n\n<operations>\nANCHOR FORMAT:\n Each anchor is `{line_number}#{hash_id}#{anchor_hash}` from read output like `42#VK#AB|content`.\n Anchors MUST be the full three-part triple copied exactly from read output. Two-part `{line}#{hash}` references are REJECTED for edits.\n\nOPERATION CHOICE:\n replace with pos -> replace ONE line at pos\n replace with pos + end -> replace range pos..end INCLUSIVE (both lines replaced)\n replace_range with pos+end -> alias for replace with end; use only when those existing lines must change or be removed\n append with pos -> insert lines AFTER the anchored line; preferred for adding code after a block's final }, });, ], or other closing line\n prepend with pos -> insert lines BEFORE the anchored line; preferred for adding code before an existing declaration or block\n append/prepend without pos -> EOF/BOF insertion (also creates missing files)\n\nCONTENT FORMAT:\n lines: string (single line) or string[] (multi-line, preferred); each array entry is exactly one physical line\n lines: null or lines: [] with replace/replace_range -> DELETE those lines\n lines: [\"\"] with replace/replace_range -> REJECTED (ambiguous); use [] / null to delete, append/prepend to insert blank lines\n\nFILE MODES:\n delete=true deletes file and requires edits=[] with no rename\n rename moves final content to a new path and removes old path\n</operations>\n\n<examples>\nGiven this file after read:\n 10#VK#AB|function hello() {\n 11#XJ#CD| console.log(\"hi\");\n 12#MB#EF| console.log(\"bye\");\n 13#QR#GH|} // end of hello()\n\nSingle-line replace (change line 11):\n { op: \"replace\", pos: \"11#XJ#CD\", lines: [\" console.log(\\\"hello\\\");\"] }\n Result: line 11 replaced. Lines 10, 12-13 unchanged.\n\nRange replace (replace lines 11-12, function body):\n { op: \"replace_range\", pos: \"11#XJ#CD\", end: \"12#MB#EF\", lines: [\" return \\\"hello world\\\";\"] }\n Result: lines 11-12 removed, replaced by 1 new line. Lines 10, 13 unchanged.\n\nBAD - end is one line too far (DELETES closing brace):\n { op: \"replace_range\", pos: \"11#XJ#CD\", end: \"13#QR#GH\", lines: [\" return \\\"hello world\\\";\"] }\n Result: line 13 (closing brace) is REPLACED too \u2014 function is broken!\n CORRECT: use end: \"12#MB#EF\" \u2014 only replace lines 11-12, keep line 13 unchanged.\n\nBAD - lines extend past end (DUPLICATES line 13):\n { op: \"replace_range\", pos: \"11#XJ#CD\", end: \"12#MB#EF\", lines: [\" return \\\"hi\\\";\", \"}\"] }\n Line 13 is \"}\" which already exists after end. Including it in lines duplicates it.\n CORRECT: { op: \"replace_range\", pos: \"11#XJ#CD\", end: \"12#MB#EF\", lines: [\" return \\\"hi\\\";\"] }\n\nAppend after a line (insert between functions):\n { op: \"append\", pos: \"13#QR#GH\", lines: [\"\", \"function added() {\", \" return true;\", \"}\"] }\n Result: 4 lines inserted after line 13. All existing lines unchanged.\n\nBAD - replacing a closing line only to insert after it:\n { op: \"replace_range\", pos: \"13#QR#GH\", end: \"13#QR#GH\", lines: [\"} // end of hello()\", \"\", \"function added() {\", \"}\"] }\n This unnecessarily consumes the existing closing line. If that closure is omitted or altered, hello() becomes unbalanced.\n CORRECT: { op: \"append\", pos: \"13#QR#GH\", lines: [\"\", \"function added() {\", \"}\"] }\n\nWhen a structural rewrite really changes both opening and closing syntax:\n Use replace_range covering the complete structure, and include the required replacement closures explicitly. Re-read the edited region or run the narrow syntax/type check immediately afterward.\n</examples>\n\n<auto>\nBuilt-in normalization (you do NOT need to handle these):\n BOM and CRLF line endings are preserved automatically.\n Hashline prefixes and diff + markers copied into text are auto-stripped.\n Exact duplicate echoes are dropped and reported as Warning lines: a payload line identical to the append/prepend anchor line, or a range payload longer than its range whose first/last line exactly duplicates a surviving neighbor.\nEverything else is applied literally \u2014 no merging, splitting, or indentation fixes.\n</auto>\n\nRecovery:\n- If you get a hash mismatch error, copy the updated anchors shown in that error or re-read the file before retrying.";
2
+ export declare const REPLACE_EDIT_DESCRIPTION = "Edit a file by replacing an exact text span with new text (oldString/newString).\n\n<must>\n1. READ FIRST: Read the target file before editing. Edits against a file that was never read, or that changed since the last read, are rejected.\n2. oldString MUST be the exact literal text to replace, including whitespace and indentation. Copy it from the Read output (without the line-number prefix).\n3. oldString must match exactly ONE place. If it appears more than once, add surrounding lines to make it unique, or set replaceAll to change every occurrence.\n4. newString is the exact replacement text. It must differ from oldString.\n5. To create a new file, call edit on a missing path with an empty oldString and the full file content as newString.\n6. LITERAL APPLICATION: no merging, indentation fixing, or content rewriting is applied. Limited visible fallbacks exist only for typography (unicode confusables) and trailing whitespace; each is reported as a Warning.\n</must>\n\n<params>\nfilePath (or file_path): absolute path to the file.\noldString (or old_string): exact text to replace; empty only when creating a missing file.\nnewString (or new_string): replacement text.\nreplaceAll (or replace_all): optional, replace every occurrence instead of requiring a unique match.\n</params>\n\nA successful result includes a bounded diff (@@ block) and a +A/-R stat line; verify it matches your intent before moving on.";
@@ -1,8 +1,8 @@
1
1
  // FILE: src/plugins/hashline-edit/tool-description.ts
2
- // VERSION: 0.8.0
2
+ // VERSION: 0.9.0
3
3
  // START_MODULE_CONTRACT
4
- // PURPOSE: Provide the LLM-facing tool description for the hash-anchored edit override.
5
- // SCOPE: Stable instructions for read-then-edit workflow, anchor usage, operation choice, literal payload semantics, and stale-anchor recovery.
4
+ // PURPOSE: Provide the LLM-facing tool descriptions for the hash-anchored edit tool and the replace-profile edit tool.
5
+ // SCOPE: Stable instructions for read-then-edit workflow, three-part anchor usage, unified replace operation choice, literal payload semantics, stale-anchor recovery, and replace-profile exact-match semantics.
6
6
  // DEPENDS: []
7
7
  // LINKS: [M-PLUGIN-HASHLINE-EDIT]
8
8
  // ROLE: RUNTIME
@@ -10,18 +10,19 @@
10
10
  // END_MODULE_CONTRACT
11
11
  //
12
12
  // START_MODULE_MAP
13
- // HASHLINE_EDIT_DESCRIPTION - Canonical LLM-facing description for the hashline-backed `edit` tool.
13
+ // HASHLINE_EDIT_DESCRIPTION - Canonical LLM-facing description for the hashline-backed `hashline_edit` tool.
14
+ // REPLACE_EDIT_DESCRIPTION - LLM-facing description for the replace-profile `edit` tool.
14
15
  // END_MODULE_MAP
15
16
  //
16
17
  // START_CHANGE_SUMMARY
17
- // LAST_CHANGE: [v0.8.0 - Documented literal application, physical single-line entries, blank-payload rejection, exact echo trimming with warnings, and the post-edit diff summary.]
18
+ // LAST_CHANGE: [v0.9.0 - Documented mandatory three-part anchors and unified replace with optional end for hashline, and added the replace-profile edit description.]
18
19
  // END_CHANGE_SUMMARY
19
20
  export const HASHLINE_EDIT_DESCRIPTION = `Edit files using exact hash-anchored line references from the latest Read output.
20
21
 
21
22
  <must>
22
23
  1. SNAPSHOT: All edits in one call reference the ORIGINAL file state. Do NOT adjust line numbers for prior edits in the same batch — the system applies them bottom-up automatically.
23
- 2. replace replaces ONE line at pos. It does NOT accept end. For multi-line replacement, use replace_range.
24
- 3. replace_range with pos+end replaces ALL lines FROM pos THROUGH end (BOTH INCLUSIVE). The end line WILL BE replaced. If you set end to a line that belongs to the next function/method/statement, that line is DELETED.
24
+ 2. replace with pos replaces ONE line. Add an end anchor to replace the INCLUSIVE range pos..end in a single operation (replace_range is an alias that requires pos + end).
25
+ 3. For range replacement, the end line WILL BE replaced. If you set end to a line that belongs to the next function/method/statement, that line is DELETED.
25
26
  CORRECT: pos on the first line to replace, end on the LAST line to replace — not the line after.
26
27
  4. lines must contain ONLY the content that belongs inside the replaced range. Lines AFTER end survive unchanged — do NOT include them in lines. If you do, they will appear twice.
27
28
  INSERTION SAFETY: To add a function, handler, statement, or block NEXT TO existing code, use append/prepend anchored to a surviving line. To add code AFTER a closed block, append after its final structural closing line. Do NOT replace that closing line merely to reproduce it and add content after it.
@@ -39,11 +40,12 @@ export const HASHLINE_EDIT_DESCRIPTION = `Edit files using exact hash-anchored l
39
40
  <operations>
40
41
  ANCHOR FORMAT:
41
42
  Each anchor is \`{line_number}#{hash_id}#{anchor_hash}\` from read output like \`42#VK#AB|content\`.
42
- Backward-compatible \`{line}#{hash}\` also accepted.
43
+ Anchors MUST be the full three-part triple copied exactly from read output. Two-part \`{line}#{hash}\` references are REJECTED for edits.
43
44
 
44
45
  OPERATION CHOICE:
45
- replace with pos -> replace ONE line at pos (end is rejected)
46
- replace_range with pos+end -> replace range pos..end INCLUSIVE (both lines replaced); use only when those existing lines must change or be removed
46
+ replace with pos -> replace ONE line at pos
47
+ replace with pos + end -> replace range pos..end INCLUSIVE (both lines replaced)
48
+ replace_range with pos+end -> alias for replace with end; use only when those existing lines must change or be removed
47
49
  append with pos -> insert lines AFTER the anchored line; preferred for adding code after a block's final }, });, ], or other closing line
48
50
  prepend with pos -> insert lines BEFORE the anchored line; preferred for adding code before an existing declaration or block
49
51
  append/prepend without pos -> EOF/BOF insertion (also creates missing files)
@@ -106,4 +108,23 @@ Everything else is applied literally — no merging, splitting, or indentation f
106
108
 
107
109
  Recovery:
108
110
  - If you get a hash mismatch error, copy the updated anchors shown in that error or re-read the file before retrying.`;
111
+ export const REPLACE_EDIT_DESCRIPTION = `Edit a file by replacing an exact text span with new text (oldString/newString).
112
+
113
+ <must>
114
+ 1. READ FIRST: Read the target file before editing. Edits against a file that was never read, or that changed since the last read, are rejected.
115
+ 2. oldString MUST be the exact literal text to replace, including whitespace and indentation. Copy it from the Read output (without the line-number prefix).
116
+ 3. oldString must match exactly ONE place. If it appears more than once, add surrounding lines to make it unique, or set replaceAll to change every occurrence.
117
+ 4. newString is the exact replacement text. It must differ from oldString.
118
+ 5. To create a new file, call edit on a missing path with an empty oldString and the full file content as newString.
119
+ 6. LITERAL APPLICATION: no merging, indentation fixing, or content rewriting is applied. Limited visible fallbacks exist only for typography (unicode confusables) and trailing whitespace; each is reported as a Warning.
120
+ </must>
121
+
122
+ <params>
123
+ filePath (or file_path): absolute path to the file.
124
+ oldString (or old_string): exact text to replace; empty only when creating a missing file.
125
+ newString (or new_string): replacement text.
126
+ replaceAll (or replace_all): optional, replace every occurrence instead of requiring a unique match.
127
+ </params>
128
+
129
+ A successful result includes a bounded diff (@@ block) and a +A/-R stat line; verify it matches your intent before moving on.`;
109
130
  //# sourceMappingURL=tool-description.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-description.js","sourceRoot":"","sources":["../../../src/plugins/hashline-edit/tool-description.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,iBAAiB;AACjB,wBAAwB;AACxB,0FAA0F;AAC1F,kJAAkJ;AAClJ,gBAAgB;AAChB,oCAAoC;AACpC,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,sGAAsG;AACtG,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,sLAAsL;AACtL,qBAAqB;AACrB,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sHAyF6E,CAAC"}
1
+ {"version":3,"file":"tool-description.js","sourceRoot":"","sources":["../../../src/plugins/hashline-edit/tool-description.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,iBAAiB;AACjB,wBAAwB;AACxB,yHAAyH;AACzH,oNAAoN;AACpN,gBAAgB;AAChB,oCAAoC;AACpC,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,+GAA+G;AAC/G,2FAA2F;AAC3F,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,wKAAwK;AACxK,qBAAqB;AACrB,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sHA0F6E,CAAC;AAEvH,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;8HAkBsF,CAAC"}
@@ -10,6 +10,7 @@ interface HashMismatch {
10
10
  }
11
11
  export declare function normalizeLineRef(ref: string): string;
12
12
  export declare function parseLineRef(ref: string): LineRef;
13
+ export declare function requireThreePartRef(ref: string, context: string): LineRef;
13
14
  export declare class HashlineMismatchError extends Error {
14
15
  private readonly mismatches;
15
16
  private readonly fileLines;