editcodewithai 2.6.0 → 2.9.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 CHANGED
@@ -204,9 +204,11 @@ for (const warning of result.warnings ?? []) {
204
204
  | `NO_EDITS_PARSED` | The model response contained no recognizable edits. |
205
205
 
206
206
  Matching is deterministic: only harmless formatting drift is absorbed (CRLF vs
207
- LF, a missing/extra trailing newline, trailing whitespace). Anything ambiguous or
208
- unmatched is skipped rather than guessed. Successful normalized matches produce
209
- no warnings.
207
+ LF, a missing/extra trailing newline, trailing whitespace) plus filename drift —
208
+ model-emitted decoration (backticks, bold, headings, a `File:` label, a trailing
209
+ colon, a leading `./`) is stripped, and a target resolves by normalized name,
210
+ case-insensitively, or by a unique basename. Anything ambiguous or unmatched is
211
+ skipped rather than guessed. Successful normalized matches produce no warnings.
210
212
 
211
213
  `warnings` is optional, so existing consumers continue to work unchanged.
212
214
 
@@ -265,6 +267,7 @@ import {
265
267
 
266
268
  // --- Diff parsing (manual use) ---
267
269
  parseDiffs, // Parse search/replace blocks (diff format)
270
+ normalizeDiffFileName, // Normalize a model-emitted filename (strip decoration/path noise)
268
271
  applyDiffs, // Apply parsed diffs to a file set (throws on unmatched edits)
269
272
  applyDiffsSafe, // Best-effort apply: returns { files, warnings } instead of throwing
270
273
  parseDiffFenced, // Parse search/replace blocks (diff-fenced format)
@@ -6,23 +6,44 @@ const matching_1 = require("./matching");
6
6
  * Normalizes a filename for comparison: trims, converts Windows separators to
7
7
  * "/", collapses duplicate separators, and strips leading "./" and "/".
8
8
  *
9
- * Case is intentionally preserved — case-insensitive matching is not part of
10
- * the deterministic behavior and is deferred.
9
+ * Case is intentionally preserved here; callers that need to tolerate case
10
+ * drift fall back to case-insensitive and basename matching after this exact
11
+ * normalized comparison fails.
11
12
  */
12
13
  function normalizeFileName(name) {
13
14
  return name
14
15
  .trim()
15
16
  .replace(/\\/g, "/")
16
17
  .replace(/\/+/g, "/")
17
- .replace(/^\.\//, "")
18
+ .replace(/^(?:\.\/)+/, "")
18
19
  .replace(/^\/+/, "");
19
20
  }
21
+ function normalizeForBasename(name) {
22
+ const normalized = normalizeFileName(name).toLowerCase();
23
+ return normalized.substring(normalized.lastIndexOf("/") + 1);
24
+ }
20
25
  function resolveFileId(files, rawName) {
21
26
  const target = normalizeFileName(rawName);
22
- const matches = Object.keys(files).filter((id) => normalizeFileName(files[id].name) === target);
23
- if (matches.length === 1)
24
- return { kind: "found", id: matches[0] };
25
- if (matches.length > 1)
27
+ const ids = Object.keys(files);
28
+ // 1. Exact match on the normalized name.
29
+ const exact = ids.filter((id) => normalizeFileName(files[id].name) === target);
30
+ if (exact.length === 1)
31
+ return { kind: "found", id: exact[0] };
32
+ if (exact.length > 1)
33
+ return { kind: "ambiguous" };
34
+ // 2. Case-insensitive match on the normalized name.
35
+ const lowerTarget = target.toLowerCase();
36
+ const caseInsensitive = ids.filter((id) => normalizeFileName(files[id].name).toLowerCase() === lowerTarget);
37
+ if (caseInsensitive.length === 1)
38
+ return { kind: "found", id: caseInsensitive[0] };
39
+ if (caseInsensitive.length > 1)
40
+ return { kind: "ambiguous" };
41
+ // 3. Basename match (substring after the last "/"), case-insensitive.
42
+ const basename = lowerTarget.substring(lowerTarget.lastIndexOf("/") + 1);
43
+ const byBasename = ids.filter((id) => normalizeForBasename(files[id].name) === basename);
44
+ if (byBasename.length === 1)
45
+ return { kind: "found", id: byBasename[0] };
46
+ if (byBasename.length > 1)
26
47
  return { kind: "ambiguous" };
27
48
  return { kind: "none" };
28
49
  }
@@ -25,6 +25,15 @@ export interface Diff {
25
25
  search: string;
26
26
  replace: string;
27
27
  }
28
+ /**
29
+ * Normalizes a model-emitted diff filename line, tolerating common
30
+ * decoration drift (markdown headings, list markers, labels, backticks,
31
+ * bold/italic asterisks, trailing colons) as well as path noise.
32
+ *
33
+ * Decoration-stripping rules are applied repeatedly until the string stops
34
+ * changing, so combinations (e.g. `- **`index.js`**:`) fully normalize.
35
+ */
36
+ export declare function normalizeDiffFileName(raw: string): string;
28
37
  export declare function parseDiffs(responseText: string): Diff[];
29
38
  export declare function applyDiffs(originalFiles: VizFiles, diffs: Diff[]): VizFiles;
30
39
  export declare function parseDiffFenced(responseText: string): Diff[];
package/dist/fileUtils.js CHANGED
@@ -4,6 +4,7 @@ exports.shouldDeleteFile = shouldDeleteFile;
4
4
  exports.isImageFile = isImageFile;
5
5
  exports.prepareFilesForPrompt = prepareFilesForPrompt;
6
6
  exports.mergeFileChanges = mergeFileChanges;
7
+ exports.normalizeDiffFileName = normalizeDiffFileName;
7
8
  exports.parseDiffs = parseDiffs;
8
9
  exports.applyDiffs = applyDiffs;
9
10
  exports.parseDiffFenced = parseDiffFenced;
@@ -88,6 +89,35 @@ function mergeFileChanges(originalFiles, parsedFiles) {
88
89
  });
89
90
  return changedFiles;
90
91
  }
92
+ /**
93
+ * Normalizes a model-emitted diff filename line, tolerating common
94
+ * decoration drift (markdown headings, list markers, labels, backticks,
95
+ * bold/italic asterisks, trailing colons) as well as path noise.
96
+ *
97
+ * Decoration-stripping rules are applied repeatedly until the string stops
98
+ * changing, so combinations (e.g. `- **`index.js`**:`) fully normalize.
99
+ */
100
+ function normalizeDiffFileName(raw) {
101
+ let name = raw;
102
+ let previous;
103
+ do {
104
+ previous = name;
105
+ name = name.trim();
106
+ name = name.replace(/^#{1,6}\s+/, "");
107
+ name = name.replace(/^[-*+]\s+/, "");
108
+ name = name.replace(/^(?:file|path)\s*:\s*/i, "");
109
+ name = name.replace(/^`+|`+$/g, "");
110
+ name = name.replace(/^\*+|\*+$/g, "");
111
+ name = name.replace(/:\s*$/, "");
112
+ name = name.trim();
113
+ } while (name !== previous);
114
+ return name
115
+ .replace(/\\/g, "/")
116
+ .replace(/\/+/g, "/")
117
+ .replace(/^(?:\.\/)+/, "")
118
+ .replace(/^\/+/, "")
119
+ .trim();
120
+ }
91
121
  function parseDiffs(responseText) {
92
122
  const diffs = [];
93
123
  // This regex captures the file path, and the content of the SEARCH and REPLACE blocks.
@@ -96,7 +126,7 @@ function parseDiffs(responseText) {
96
126
  for (const match of matches) {
97
127
  const [_, fileName, search, replace] = match;
98
128
  diffs.push({
99
- fileName: fileName.trim(),
129
+ fileName: normalizeDiffFileName(fileName),
100
130
  search,
101
131
  replace,
102
132
  });
@@ -127,7 +157,7 @@ function parseDiffFenced(responseText) {
127
157
  for (const match of matches) {
128
158
  const [_, fileName, search, replace] = match;
129
159
  diffs.push({
130
- fileName: fileName.trim(),
160
+ fileName: normalizeDiffFileName(fileName),
131
161
  search,
132
162
  replace,
133
163
  });
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { PerformAiEditParams, PerformAiEditResult } from "./types";
2
2
  import { PROMPT_TEMPLATE_VERSION, assembleFullPrompt, FORMAT_INSTRUCTIONS } from "./prompt";
3
3
  import { getGenerationMetadata } from "./metadata";
4
- import { prepareFilesForPrompt, mergeFileChanges, parseDiffs, applyDiffs, applyDiffsSafe, parseDiffFenced, parseUdiffs, applyUdiffs, applyUdiffsSafe, applyHybridEdits, applyHybridEditsSafe, isImageFile } from "./fileUtils";
4
+ import { prepareFilesForPrompt, mergeFileChanges, parseDiffs, applyDiffs, applyDiffsSafe, parseDiffFenced, normalizeDiffFileName, parseUdiffs, applyUdiffs, applyUdiffsSafe, applyHybridEdits, applyHybridEditsSafe, isImageFile } from "./fileUtils";
5
5
  export type { LlmFunction, PerformAiEditParams, PerformAiEditResult, EditFormat, ApplyWarning, ApplyWarningCode, ApplyResult, ParsedEdit, } from "./types";
6
- export { FORMAT_INSTRUCTIONS, mergeFileChanges, prepareFilesForPrompt, isImageFile, parseDiffs, applyDiffs, applyDiffsSafe, parseDiffFenced, parseUdiffs, applyUdiffs, applyUdiffsSafe, applyHybridEdits, applyHybridEditsSafe, assembleFullPrompt, getGenerationMetadata, PROMPT_TEMPLATE_VERSION, };
6
+ export { FORMAT_INSTRUCTIONS, mergeFileChanges, prepareFilesForPrompt, isImageFile, parseDiffs, applyDiffs, applyDiffsSafe, parseDiffFenced, normalizeDiffFileName, parseUdiffs, applyUdiffs, applyUdiffsSafe, applyHybridEdits, applyHybridEditsSafe, assembleFullPrompt, getGenerationMetadata, PROMPT_TEMPLATE_VERSION, };
7
7
  /**
8
8
  * Core AI logic for:
9
9
  * - Building the prompt (including context)
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PROMPT_TEMPLATE_VERSION = exports.getGenerationMetadata = exports.assembleFullPrompt = exports.applyHybridEditsSafe = exports.applyHybridEdits = exports.applyUdiffsSafe = exports.applyUdiffs = exports.parseUdiffs = exports.parseDiffFenced = exports.applyDiffsSafe = exports.applyDiffs = exports.parseDiffs = exports.isImageFile = exports.prepareFilesForPrompt = exports.mergeFileChanges = exports.FORMAT_INSTRUCTIONS = void 0;
3
+ exports.PROMPT_TEMPLATE_VERSION = exports.getGenerationMetadata = exports.assembleFullPrompt = exports.applyHybridEditsSafe = exports.applyHybridEdits = exports.applyUdiffsSafe = exports.applyUdiffs = exports.parseUdiffs = exports.normalizeDiffFileName = exports.parseDiffFenced = exports.applyDiffsSafe = exports.applyDiffs = exports.parseDiffs = exports.isImageFile = exports.prepareFilesForPrompt = exports.mergeFileChanges = exports.FORMAT_INSTRUCTIONS = void 0;
4
4
  exports.performAiEdit = performAiEdit;
5
5
  const llm_code_format_1 = require("llm-code-format");
6
6
  const prompt_1 = require("./prompt");
@@ -16,6 +16,7 @@ Object.defineProperty(exports, "parseDiffs", { enumerable: true, get: function (
16
16
  Object.defineProperty(exports, "applyDiffs", { enumerable: true, get: function () { return fileUtils_1.applyDiffs; } });
17
17
  Object.defineProperty(exports, "applyDiffsSafe", { enumerable: true, get: function () { return fileUtils_1.applyDiffsSafe; } });
18
18
  Object.defineProperty(exports, "parseDiffFenced", { enumerable: true, get: function () { return fileUtils_1.parseDiffFenced; } });
19
+ Object.defineProperty(exports, "normalizeDiffFileName", { enumerable: true, get: function () { return fileUtils_1.normalizeDiffFileName; } });
19
20
  Object.defineProperty(exports, "parseUdiffs", { enumerable: true, get: function () { return fileUtils_1.parseUdiffs; } });
20
21
  Object.defineProperty(exports, "applyUdiffs", { enumerable: true, get: function () { return fileUtils_1.applyUdiffs; } });
21
22
  Object.defineProperty(exports, "applyUdiffsSafe", { enumerable: true, get: function () { return fileUtils_1.applyUdiffsSafe; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "editcodewithai",
3
- "version": "2.6.0",
3
+ "version": "2.9.0",
4
4
  "description": "Edit Code With AI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -41,21 +41,21 @@
41
41
  "homepage": "https://github.com/vizhub-core/editcodewithai#readme",
42
42
  "dependencies": {
43
43
  "@vizhub/viz-types": "^0.5.0",
44
- "@vizhub/viz-utils": "^1.5.0",
45
- "dotenv": "^17.2.2",
46
- "llm-code-format": "^3.0.0"
44
+ "@vizhub/viz-utils": "^1.6.0",
45
+ "dotenv": "^18.0.6",
46
+ "llm-code-format": "^3.2.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@langchain/core": "^0.3.73",
50
- "@langchain/openai": "^0.6.11",
51
- "langchain": "^0.3.32",
52
- "cors": "^2.8.5",
53
- "express": "^5.1.0",
54
- "npm-check-updates": "^18.0.3",
55
- "prettier": "^3.6.2",
56
- "puppeteer": "^24.19.0",
49
+ "@langchain/core": "^1.2.17",
50
+ "@langchain/openai": "^1.6.2",
51
+ "langchain": "^1.5.15",
52
+ "cors": "^2.8.6",
53
+ "express": "^5.2.1",
54
+ "npm-check-updates": "^23.1.0",
55
+ "prettier": "^3.9.9",
56
+ "puppeteer": "^25.12.0",
57
57
  "ts-node": "^10.9.2",
58
- "typescript": "^5.9.2",
59
- "vitest": "^3.2.4"
58
+ "typescript": "^7.0.2",
59
+ "vitest": "^5.0.3"
60
60
  }
61
61
  }