@probelabs/probe 0.6.0-rc254 → 0.6.0-rc255

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 (47) hide show
  1. package/README.md +166 -3
  2. package/bin/binaries/probe-v0.6.0-rc255-aarch64-apple-darwin.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc255-aarch64-unknown-linux-musl.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc255-x86_64-apple-darwin.tar.gz +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc255-x86_64-pc-windows-msvc.zip +0 -0
  6. package/bin/binaries/probe-v0.6.0-rc255-x86_64-unknown-linux-musl.tar.gz +0 -0
  7. package/build/agent/ProbeAgent.d.ts +1 -1
  8. package/build/agent/ProbeAgent.js +24 -16
  9. package/build/agent/acp/tools.js +2 -1
  10. package/build/agent/acp/tools.test.js +2 -1
  11. package/build/agent/index.js +1425 -410
  12. package/build/agent/tools.js +0 -28
  13. package/build/delegate.js +3 -0
  14. package/build/index.js +2 -0
  15. package/build/tools/common.js +6 -5
  16. package/build/tools/edit.js +457 -65
  17. package/build/tools/fileTracker.js +318 -0
  18. package/build/tools/fuzzyMatch.js +271 -0
  19. package/build/tools/hashline.js +131 -0
  20. package/build/tools/lineEditHeuristics.js +138 -0
  21. package/build/tools/symbolEdit.js +119 -0
  22. package/build/tools/vercel.js +40 -9
  23. package/cjs/agent/ProbeAgent.cjs +1528 -514
  24. package/cjs/index.cjs +1556 -540
  25. package/index.d.ts +189 -1
  26. package/package.json +1 -1
  27. package/src/agent/ProbeAgent.d.ts +1 -1
  28. package/src/agent/ProbeAgent.js +24 -16
  29. package/src/agent/acp/tools.js +2 -1
  30. package/src/agent/acp/tools.test.js +2 -1
  31. package/src/agent/index.js +14 -3
  32. package/src/agent/tools.js +0 -28
  33. package/src/delegate.js +3 -0
  34. package/src/index.js +2 -0
  35. package/src/tools/common.js +6 -5
  36. package/src/tools/edit.js +457 -65
  37. package/src/tools/fileTracker.js +318 -0
  38. package/src/tools/fuzzyMatch.js +271 -0
  39. package/src/tools/hashline.js +131 -0
  40. package/src/tools/lineEditHeuristics.js +138 -0
  41. package/src/tools/symbolEdit.js +119 -0
  42. package/src/tools/vercel.js +40 -9
  43. package/bin/binaries/probe-v0.6.0-rc254-aarch64-apple-darwin.tar.gz +0 -0
  44. package/bin/binaries/probe-v0.6.0-rc254-aarch64-unknown-linux-musl.tar.gz +0 -0
  45. package/bin/binaries/probe-v0.6.0-rc254-x86_64-apple-darwin.tar.gz +0 -0
  46. package/bin/binaries/probe-v0.6.0-rc254-x86_64-pc-windows-msvc.zip +0 -0
  47. package/bin/binaries/probe-v0.6.0-rc254-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -2377,7 +2377,7 @@ async function waitForFileLock(lockPath, binaryPath) {
2377
2377
  }
2378
2378
  } catch {
2379
2379
  }
2380
- await new Promise((resolve8) => setTimeout(resolve8, LOCK_POLL_INTERVAL_MS));
2380
+ await new Promise((resolve9) => setTimeout(resolve9, LOCK_POLL_INTERVAL_MS));
2381
2381
  }
2382
2382
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2383
2383
  console.log(`Timeout waiting for file lock`);
@@ -3718,7 +3718,7 @@ Cwd: ${cwd}`;
3718
3718
  }
3719
3719
  }
3720
3720
  function extractWithStdin(binaryPath, cliArgs, content, options, cwd) {
3721
- return new Promise((resolve8, reject2) => {
3721
+ return new Promise((resolve9, reject2) => {
3722
3722
  const childProcess = spawn(binaryPath, ["extract", ...cliArgs], {
3723
3723
  stdio: ["pipe", "pipe", "pipe"],
3724
3724
  cwd
@@ -3741,7 +3741,7 @@ function extractWithStdin(binaryPath, cliArgs, content, options, cwd) {
3741
3741
  }
3742
3742
  try {
3743
3743
  const result = processExtractOutput(stdout, options);
3744
- resolve8(result);
3744
+ resolve9(result);
3745
3745
  } catch (error) {
3746
3746
  reject2(error);
3747
3747
  }
@@ -3849,6 +3849,7 @@ async function delegate({
3849
3849
  model = null,
3850
3850
  enableBash = false,
3851
3851
  bashConfig = null,
3852
+ allowEdit = false,
3852
3853
  architectureFileName = null,
3853
3854
  promptType = "code-researcher",
3854
3855
  allowedTools = null,
@@ -3928,6 +3929,8 @@ async function delegate({
3928
3929
  // Inherit from parent
3929
3930
  bashConfig,
3930
3931
  // Inherit from parent
3932
+ allowEdit,
3933
+ // Inherit from parent
3931
3934
  architectureFileName,
3932
3935
  allowedTools,
3933
3936
  disableTools,
@@ -4112,7 +4115,7 @@ var init_delegate = __esm({
4112
4115
  if (debug) {
4113
4116
  console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length}, timeout: ${effectiveTimeout}ms)`);
4114
4117
  }
4115
- return new Promise((resolve8, reject2) => {
4118
+ return new Promise((resolve9, reject2) => {
4116
4119
  const entry = {
4117
4120
  resolve: null,
4118
4121
  // Will be wrapped below
@@ -4128,7 +4131,7 @@ var init_delegate = __esm({
4128
4131
  if (settled) return;
4129
4132
  settled = true;
4130
4133
  if (entry.timeoutId) clearTimeout(entry.timeoutId);
4131
- resolve8(value);
4134
+ resolve9(value);
4132
4135
  };
4133
4136
  entry.reject = (error) => {
4134
4137
  if (settled) return;
@@ -4178,7 +4181,7 @@ var init_delegate = __esm({
4178
4181
  while (this.waitQueue.length > 0 && this.globalActive < this.maxConcurrent) {
4179
4182
  const next = this.waitQueue.shift();
4180
4183
  if (!next) break;
4181
- const { resolve: resolve8, reject: reject2, parentSessionId, queuedAt } = next;
4184
+ const { resolve: resolve9, reject: reject2, parentSessionId, queuedAt } = next;
4182
4185
  if (parentSessionId) {
4183
4186
  const sessionData = this.sessionDelegations.get(parentSessionId);
4184
4187
  const sessionCount = sessionData?.count || 0;
@@ -4195,12 +4198,12 @@ var init_delegate = __esm({
4195
4198
  const waitTime = Date.now() - queuedAt;
4196
4199
  console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
4197
4200
  }
4198
- toResolve.push(resolve8);
4201
+ toResolve.push(resolve9);
4199
4202
  }
4200
4203
  if (toResolve.length > 0 || toReject.length > 0) {
4201
4204
  setImmediate(() => {
4202
- for (const resolve8 of toResolve) {
4203
- resolve8(true);
4205
+ for (const resolve9 of toResolve) {
4206
+ resolve9(true);
4204
4207
  }
4205
4208
  for (const { reject: reject2, error } of toReject) {
4206
4209
  reject2(error);
@@ -8835,6 +8838,400 @@ var init_zod = __esm({
8835
8838
  }
8836
8839
  });
8837
8840
 
8841
+ // src/tools/fuzzyMatch.js
8842
+ function findFuzzyMatch(content, searchString) {
8843
+ if (!searchString || searchString.trim().length === 0) {
8844
+ return null;
8845
+ }
8846
+ const normalizedContent = content.replace(/\r\n/g, "\n");
8847
+ const normalizedSearch = searchString.replace(/\r\n/g, "\n");
8848
+ const contentLines = normalizedContent.split("\n");
8849
+ const searchLines = normalizedSearch.split("\n");
8850
+ const trimmed = lineTrimmedMatch(contentLines, searchLines);
8851
+ if (trimmed) return { ...trimmed, strategy: "line-trimmed" };
8852
+ const normalized = whitespaceNormalizedMatch(normalizedContent, normalizedSearch);
8853
+ if (normalized) return { ...normalized, strategy: "whitespace-normalized" };
8854
+ const indentFlex = indentFlexibleMatch(contentLines, searchLines);
8855
+ if (indentFlex) return { ...indentFlex, strategy: "indent-flexible" };
8856
+ return null;
8857
+ }
8858
+ function lineTrimmedMatch(contentLines, searchLines) {
8859
+ if (searchLines.length === 0) return null;
8860
+ const trimmedSearchLines = searchLines.map((line) => line.trim());
8861
+ if (trimmedSearchLines.every((line) => line === "")) return null;
8862
+ const windowSize = searchLines.length;
8863
+ const matches = [];
8864
+ for (let i = 0; i <= contentLines.length - windowSize; i++) {
8865
+ let allMatch = true;
8866
+ for (let j = 0; j < windowSize; j++) {
8867
+ if (contentLines[i + j].trim() !== trimmedSearchLines[j]) {
8868
+ allMatch = false;
8869
+ break;
8870
+ }
8871
+ }
8872
+ if (allMatch) {
8873
+ const matchedText = contentLines.slice(i, i + windowSize).join("\n");
8874
+ matches.push(matchedText);
8875
+ }
8876
+ }
8877
+ if (matches.length === 0) return null;
8878
+ return {
8879
+ matchedText: matches[0],
8880
+ count: matches.length
8881
+ };
8882
+ }
8883
+ function whitespaceNormalizedMatch(content, search2) {
8884
+ if (!search2 || search2.trim().length === 0) return null;
8885
+ const { normalized: normContent, indexMap: contentMap } = buildNormalizedMap(content);
8886
+ const { normalized: normSearch } = buildNormalizedMap(search2);
8887
+ if (normSearch.length === 0) return null;
8888
+ const matches = [];
8889
+ let searchStart = 0;
8890
+ while (searchStart <= normContent.length - normSearch.length) {
8891
+ const idx = normContent.indexOf(normSearch, searchStart);
8892
+ if (idx === -1) break;
8893
+ const originalStart = contentMap[idx];
8894
+ const originalEnd = contentMap[idx + normSearch.length - 1];
8895
+ let actualEnd = originalEnd + 1;
8896
+ while (actualEnd < content.length && /[ \t]/.test(content[actualEnd]) && (actualEnd === originalEnd + 1 || /[ \t]/.test(content[actualEnd - 1]))) {
8897
+ if (contentMap.indexOf(actualEnd) > idx + normSearch.length - 1 || contentMap.indexOf(actualEnd) === -1) {
8898
+ break;
8899
+ }
8900
+ actualEnd++;
8901
+ }
8902
+ const matchedText = content.substring(originalStart, actualEnd);
8903
+ matches.push(matchedText);
8904
+ searchStart = idx + 1;
8905
+ }
8906
+ if (matches.length === 0) return null;
8907
+ return {
8908
+ matchedText: matches[0],
8909
+ count: matches.length
8910
+ };
8911
+ }
8912
+ function buildNormalizedMap(str) {
8913
+ const normalized = [];
8914
+ const indexMap = [];
8915
+ let i = 0;
8916
+ while (i < str.length) {
8917
+ const ch = str[i];
8918
+ if (ch === " " || ch === " ") {
8919
+ normalized.push(" ");
8920
+ indexMap.push(i);
8921
+ while (i < str.length && (str[i] === " " || str[i] === " ")) {
8922
+ i++;
8923
+ }
8924
+ } else {
8925
+ normalized.push(ch);
8926
+ indexMap.push(i);
8927
+ i++;
8928
+ }
8929
+ }
8930
+ return {
8931
+ normalized: normalized.join(""),
8932
+ indexMap
8933
+ };
8934
+ }
8935
+ function indentFlexibleMatch(contentLines, searchLines) {
8936
+ if (searchLines.length === 0) return null;
8937
+ if (searchLines.every((line) => line.trim() === "")) return null;
8938
+ const searchMinIndent = getMinIndent(searchLines);
8939
+ const strippedSearch = searchLines.map((line) => stripIndent(line, searchMinIndent));
8940
+ const windowSize = searchLines.length;
8941
+ const matches = [];
8942
+ for (let i = 0; i <= contentLines.length - windowSize; i++) {
8943
+ const windowLines = contentLines.slice(i, i + windowSize);
8944
+ const windowMinIndent = getMinIndent(windowLines);
8945
+ const strippedWindow = windowLines.map((line) => stripIndent(line, windowMinIndent));
8946
+ let allMatch = true;
8947
+ for (let j = 0; j < windowSize; j++) {
8948
+ if (strippedWindow[j] !== strippedSearch[j]) {
8949
+ allMatch = false;
8950
+ break;
8951
+ }
8952
+ }
8953
+ if (allMatch) {
8954
+ const matchedText = windowLines.join("\n");
8955
+ matches.push(matchedText);
8956
+ }
8957
+ }
8958
+ if (matches.length === 0) return null;
8959
+ return {
8960
+ matchedText: matches[0],
8961
+ count: matches.length
8962
+ };
8963
+ }
8964
+ function getMinIndent(lines) {
8965
+ let min = Infinity;
8966
+ for (const line of lines) {
8967
+ if (line.trim() === "") continue;
8968
+ const match2 = line.match(/^([ \t]*)/);
8969
+ if (match2) {
8970
+ min = Math.min(min, match2[1].length);
8971
+ }
8972
+ }
8973
+ return min === Infinity ? 0 : min;
8974
+ }
8975
+ function stripIndent(line, amount) {
8976
+ if (line.trim() === "") return "";
8977
+ if (amount <= 0) return line;
8978
+ return line.substring(Math.min(amount, line.length));
8979
+ }
8980
+ var init_fuzzyMatch = __esm({
8981
+ "src/tools/fuzzyMatch.js"() {
8982
+ "use strict";
8983
+ }
8984
+ });
8985
+
8986
+ // src/tools/symbolEdit.js
8987
+ async function findSymbol(filePath, symbolName, cwd) {
8988
+ try {
8989
+ const result = await extract({
8990
+ files: [`${filePath}#${symbolName}`],
8991
+ format: "json",
8992
+ json: true,
8993
+ cwd
8994
+ });
8995
+ if (!result || !result.results || result.results.length === 0) {
8996
+ return null;
8997
+ }
8998
+ const match2 = result.results[0];
8999
+ return {
9000
+ startLine: match2.lines[0],
9001
+ // 1-indexed
9002
+ endLine: match2.lines[1],
9003
+ // 1-indexed
9004
+ code: match2.code,
9005
+ nodeType: match2.node_type,
9006
+ file: match2.file
9007
+ };
9008
+ } catch (error) {
9009
+ if (process.env.DEBUG === "1") {
9010
+ console.error(`[SymbolEdit] findSymbol error for "${symbolName}" in ${filePath}: ${error.message}`);
9011
+ }
9012
+ return null;
9013
+ }
9014
+ }
9015
+ async function findAllSymbols(filePath, symbolName, cwd) {
9016
+ try {
9017
+ const result = await extract({
9018
+ files: [`${filePath}#${symbolName}`],
9019
+ format: "json",
9020
+ json: true,
9021
+ cwd
9022
+ });
9023
+ if (!result || !result.results || result.results.length === 0) {
9024
+ return [];
9025
+ }
9026
+ return result.results.map((match2) => ({
9027
+ startLine: match2.lines[0],
9028
+ endLine: match2.lines[1],
9029
+ code: match2.code,
9030
+ nodeType: match2.node_type,
9031
+ file: match2.file,
9032
+ qualifiedName: match2.symbol_signature || symbolName
9033
+ }));
9034
+ } catch (error) {
9035
+ if (process.env.DEBUG === "1") {
9036
+ console.error(`[SymbolEdit] findAllSymbols error for "${symbolName}" in ${filePath}: ${error.message}`);
9037
+ }
9038
+ return [];
9039
+ }
9040
+ }
9041
+ function detectBaseIndent(code) {
9042
+ const lines = code.split("\n");
9043
+ for (const line of lines) {
9044
+ if (line.trim().length > 0) {
9045
+ const match2 = line.match(/^(\s*)/);
9046
+ return match2 ? match2[1] : "";
9047
+ }
9048
+ }
9049
+ return "";
9050
+ }
9051
+ function reindent(newContent, targetIndent) {
9052
+ const lines = newContent.split("\n");
9053
+ const sourceIndent = detectBaseIndent(newContent);
9054
+ return lines.map((line) => {
9055
+ if (line.trim().length === 0) {
9056
+ return "";
9057
+ }
9058
+ if (line.startsWith(sourceIndent)) {
9059
+ return targetIndent + line.slice(sourceIndent.length);
9060
+ }
9061
+ return line;
9062
+ }).join("\n");
9063
+ }
9064
+ var init_symbolEdit = __esm({
9065
+ "src/tools/symbolEdit.js"() {
9066
+ "use strict";
9067
+ init_extract();
9068
+ }
9069
+ });
9070
+
9071
+ // src/tools/hashline.js
9072
+ function computeLineHash(line) {
9073
+ const stripped = (line || "").replace(/\s+/g, "");
9074
+ let h = 5381;
9075
+ for (let i = 0; i < stripped.length; i++) {
9076
+ h = (h << 5) + h + stripped.charCodeAt(i) & 4294967295;
9077
+ }
9078
+ return ((h >>> 0) % 256).toString(16).padStart(2, "0");
9079
+ }
9080
+ function parseLineRef(ref2) {
9081
+ if (ref2 === void 0 || ref2 === null) return null;
9082
+ const str = String(ref2).trim();
9083
+ if (!str) return null;
9084
+ const hashMatch = str.match(/^(\d+):([0-9a-fA-F]{2})$/);
9085
+ if (hashMatch) {
9086
+ const line = parseInt(hashMatch[1], 10);
9087
+ if (line < 1 || !isFinite(line)) return null;
9088
+ return { line, hash: hashMatch[2].toLowerCase() };
9089
+ }
9090
+ const lineMatch = str.match(/^(\d+)$/);
9091
+ if (lineMatch) {
9092
+ const line = parseInt(lineMatch[1], 10);
9093
+ if (line < 1 || !isFinite(line)) return null;
9094
+ return { line, hash: null };
9095
+ }
9096
+ return null;
9097
+ }
9098
+ function validateLineHash(lineNum, hash, fileLines) {
9099
+ const idx = lineNum - 1;
9100
+ if (idx < 0 || idx >= fileLines.length) {
9101
+ return { valid: false, actualHash: "", actualContent: "" };
9102
+ }
9103
+ const actualContent = fileLines[idx];
9104
+ const actualHash = computeLineHash(actualContent);
9105
+ return {
9106
+ valid: actualHash === hash.toLowerCase(),
9107
+ actualHash,
9108
+ actualContent
9109
+ };
9110
+ }
9111
+ function annotateOutputWithHashes(output) {
9112
+ if (!output || typeof output !== "string") return output;
9113
+ return output.split("\n").map((line) => {
9114
+ const cleanLine = line.endsWith("\r") ? line.slice(0, -1) : line;
9115
+ const match2 = cleanLine.match(/^(\s*)(\d+)(\s*\|)(.*)$/);
9116
+ if (!match2) return line;
9117
+ const [, prefix, lineNum, pipeSection, content] = match2;
9118
+ const hash = computeLineHash(content);
9119
+ const cr = line.endsWith("\r") ? "\r" : "";
9120
+ return `${prefix}${lineNum}:${hash}${pipeSection}${content}${cr}`;
9121
+ }).join("\n");
9122
+ }
9123
+ function stripHashlinePrefixes(text) {
9124
+ if (!text || typeof text !== "string") return { cleaned: text || "", stripped: false };
9125
+ const lines = text.split("\n");
9126
+ if (lines.length === 0) return { cleaned: "", stripped: false };
9127
+ const nonEmptyLines = lines.filter((l) => l.trim().length > 0);
9128
+ if (nonEmptyLines.length === 0) return { cleaned: text, stripped: false };
9129
+ const prefixPattern = /^\s*\d+(?::[0-9a-fA-F]{2})?\s*\|\s?/;
9130
+ const matchCount = nonEmptyLines.filter((l) => prefixPattern.test(l)).length;
9131
+ if (matchCount / nonEmptyLines.length <= 0.5) {
9132
+ return { cleaned: text, stripped: false };
9133
+ }
9134
+ const cleaned = lines.map((line) => {
9135
+ if (line.trim().length === 0) return line;
9136
+ return line.replace(prefixPattern, "");
9137
+ }).join("\n");
9138
+ return { cleaned, stripped: true };
9139
+ }
9140
+ var init_hashline = __esm({
9141
+ "src/tools/hashline.js"() {
9142
+ "use strict";
9143
+ }
9144
+ });
9145
+
9146
+ // src/tools/lineEditHeuristics.js
9147
+ function stripEchoedBoundaries(newStr, fileLines, startLine, endLine, position) {
9148
+ const modifications = [];
9149
+ let lines = newStr.split("\n");
9150
+ if (lines.length === 0) return { result: newStr, modifications };
9151
+ if (position === "after") {
9152
+ const anchorIdx = startLine - 1;
9153
+ if (anchorIdx >= 0 && anchorIdx < fileLines.length) {
9154
+ const anchorTrimmed = fileLines[anchorIdx].trim();
9155
+ if (anchorTrimmed.length > 0 && lines.length > 0 && lines[0].trim() === anchorTrimmed) {
9156
+ lines = lines.slice(1);
9157
+ modifications.push("stripped echoed anchor line (insert-after)");
9158
+ }
9159
+ }
9160
+ } else if (position === "before") {
9161
+ const anchorIdx = startLine - 1;
9162
+ if (anchorIdx >= 0 && anchorIdx < fileLines.length) {
9163
+ const anchorTrimmed = fileLines[anchorIdx].trim();
9164
+ if (anchorTrimmed.length > 0 && lines.length > 0 && lines[lines.length - 1].trim() === anchorTrimmed) {
9165
+ lines = lines.slice(0, -1);
9166
+ modifications.push("stripped echoed anchor line (insert-before)");
9167
+ }
9168
+ }
9169
+ } else {
9170
+ const beforeIdx = startLine - 2;
9171
+ if (beforeIdx >= 0 && beforeIdx < fileLines.length) {
9172
+ const beforeTrimmed = fileLines[beforeIdx].trim();
9173
+ if (beforeTrimmed.length > 0 && lines.length > 0 && lines[0].trim() === beforeTrimmed) {
9174
+ lines = lines.slice(1);
9175
+ modifications.push("stripped echoed line before range");
9176
+ }
9177
+ }
9178
+ const afterIdx = endLine;
9179
+ if (afterIdx >= 0 && afterIdx < fileLines.length) {
9180
+ const afterTrimmed = fileLines[afterIdx].trim();
9181
+ if (afterTrimmed.length > 0 && lines.length > 0 && lines[lines.length - 1].trim() === afterTrimmed) {
9182
+ lines = lines.slice(0, -1);
9183
+ modifications.push("stripped echoed line after range");
9184
+ }
9185
+ }
9186
+ }
9187
+ return { result: lines.join("\n"), modifications };
9188
+ }
9189
+ function restoreIndentation(newStr, originalLines) {
9190
+ const modifications = [];
9191
+ if (!newStr || !originalLines || originalLines.length === 0) {
9192
+ return { result: newStr || "", modifications };
9193
+ }
9194
+ const originalCode = originalLines.join("\n");
9195
+ const targetIndent = detectBaseIndent(originalCode);
9196
+ const newIndent = detectBaseIndent(newStr);
9197
+ if (targetIndent !== newIndent) {
9198
+ const reindented = reindent(newStr, targetIndent);
9199
+ if (reindented !== newStr) {
9200
+ modifications.push(`reindented from "${newIndent}" to "${targetIndent}"`);
9201
+ return { result: reindented, modifications };
9202
+ }
9203
+ }
9204
+ return { result: newStr, modifications };
9205
+ }
9206
+ function cleanNewString(newStr, fileLines, startLine, endLine, position) {
9207
+ const modifications = [];
9208
+ if (!newStr && newStr !== "") return { cleaned: "", modifications };
9209
+ const { cleaned: afterPrefixes, stripped } = stripHashlinePrefixes(newStr);
9210
+ if (stripped) modifications.push("stripped line-number prefixes");
9211
+ const { result: afterEchoes, modifications: echoMods } = stripEchoedBoundaries(
9212
+ afterPrefixes,
9213
+ fileLines,
9214
+ startLine,
9215
+ endLine,
9216
+ position
9217
+ );
9218
+ modifications.push(...echoMods);
9219
+ if (!position) {
9220
+ const originalLines = fileLines.slice(startLine - 1, endLine);
9221
+ const { result: afterIndent, modifications: indentMods } = restoreIndentation(afterEchoes, originalLines);
9222
+ modifications.push(...indentMods);
9223
+ return { cleaned: afterIndent, modifications };
9224
+ }
9225
+ return { cleaned: afterEchoes, modifications };
9226
+ }
9227
+ var init_lineEditHeuristics = __esm({
9228
+ "src/tools/lineEditHeuristics.js"() {
9229
+ "use strict";
9230
+ init_symbolEdit();
9231
+ init_hashline();
9232
+ }
9233
+ });
9234
+
8838
9235
  // src/tools/edit.js
8839
9236
  import { tool } from "ai";
8840
9237
  import { promises as fs6 } from "fs";
@@ -8862,29 +9259,204 @@ function parseFileToolOptions(options = {}) {
8862
9259
  workspaceRoot: options.workspaceRoot || options.cwd || allowedFolders.length > 0 && allowedFolders[0] || process.cwd()
8863
9260
  };
8864
9261
  }
9262
+ async function handleSymbolEdit({ resolvedPath, file_path, symbol, new_string, position, debug, cwd, fileTracker }) {
9263
+ if (typeof symbol !== "string" || symbol.trim() === "") {
9264
+ return 'Error editing file: Invalid symbol - must be a non-empty string. Provide the name of a function, class, method, or other named code definition (e.g. "myFunction" or "MyClass.myMethod"). To edit by text matching instead, use old_string + new_string.';
9265
+ }
9266
+ if (position !== void 0 && position !== null && position !== "before" && position !== "after") {
9267
+ return 'Error editing file: Invalid position - must be "before" or "after". Use position="before" to insert code above the symbol, or position="after" to insert code below it. Omit position entirely to replace the symbol with new_string.';
9268
+ }
9269
+ const allMatches = await findAllSymbols(resolvedPath, symbol, cwd || process.cwd());
9270
+ if (allMatches.length === 0) {
9271
+ return `Error editing file: Symbol "${symbol}" not found in ${file_path}. Verify the symbol name matches a top-level function, class, method, or other named definition exactly as declared in the source. Use 'search' or 'extract' to inspect the file and find the correct symbol name. Alternatively, use old_string + new_string for text-based editing instead.`;
9272
+ }
9273
+ if (allMatches.length > 1) {
9274
+ const suggestions = allMatches.map(
9275
+ (m) => ` - ${m.qualifiedName} (${m.nodeType}, line ${m.startLine})`
9276
+ ).join("\n");
9277
+ return `Error editing ${file_path}: Found ${allMatches.length} symbols named "${symbol}". Use a qualified name to specify which one:
9278
+ ${suggestions}
9279
+
9280
+ Example: <edit><file_path>${file_path}</file_path><symbol>${allMatches[0].qualifiedName}</symbol><new_string>...</new_string></edit>`;
9281
+ }
9282
+ const symbolInfo = allMatches[0];
9283
+ if (fileTracker) {
9284
+ const check = fileTracker.checkSymbolContent(resolvedPath, symbol, symbolInfo.code);
9285
+ if (!check.ok && check.reason === "stale") {
9286
+ return `Error editing ${file_path}: Symbol "${symbol}" has changed since you last read it. Use extract to re-read the current content, then retry.
9287
+
9288
+ Example: <extract><targets>${file_path}#${symbol}</targets></extract>`;
9289
+ }
9290
+ }
9291
+ const content = await fs6.readFile(resolvedPath, "utf-8");
9292
+ const lines = content.split("\n");
9293
+ if (position) {
9294
+ const refIndent = detectBaseIndent(symbolInfo.code);
9295
+ const reindented = reindent(new_string, refIndent);
9296
+ const newLines = reindented.split("\n");
9297
+ if (position === "after") {
9298
+ lines.splice(symbolInfo.endLine, 0, "", ...newLines);
9299
+ } else {
9300
+ lines.splice(symbolInfo.startLine - 1, 0, ...newLines, "");
9301
+ }
9302
+ await fs6.writeFile(resolvedPath, lines.join("\n"), "utf-8");
9303
+ if (fileTracker) {
9304
+ const updated = await findSymbol(resolvedPath, symbol, cwd || process.cwd());
9305
+ if (updated) {
9306
+ fileTracker.trackSymbolAfterWrite(resolvedPath, symbol, updated.code, updated.startLine, updated.endLine);
9307
+ }
9308
+ fileTracker.markFileSeen(resolvedPath);
9309
+ }
9310
+ const insertLine = position === "after" ? symbolInfo.endLine + 1 : symbolInfo.startLine;
9311
+ if (debug) {
9312
+ console.error(`[Edit] Successfully inserted ${newLines.length} lines ${position} "${symbol}" at line ${insertLine} in ${resolvedPath}`);
9313
+ }
9314
+ return `Successfully inserted ${newLines.length} lines ${position} symbol "${symbol}" in ${file_path} (at line ${insertLine})`;
9315
+ } else {
9316
+ const originalIndent = detectBaseIndent(symbolInfo.code);
9317
+ const reindented = reindent(new_string, originalIndent);
9318
+ const newLines = reindented.split("\n");
9319
+ lines.splice(symbolInfo.startLine - 1, symbolInfo.endLine - symbolInfo.startLine + 1, ...newLines);
9320
+ await fs6.writeFile(resolvedPath, lines.join("\n"), "utf-8");
9321
+ if (fileTracker) {
9322
+ const updated = await findSymbol(resolvedPath, symbol, cwd || process.cwd());
9323
+ if (updated) {
9324
+ fileTracker.trackSymbolAfterWrite(resolvedPath, symbol, updated.code, updated.startLine, updated.endLine);
9325
+ }
9326
+ fileTracker.markFileSeen(resolvedPath);
9327
+ }
9328
+ if (debug) {
9329
+ console.error(`[Edit] Successfully replaced symbol "${symbol}" in ${resolvedPath} (lines ${symbolInfo.startLine}-${symbolInfo.endLine})`);
9330
+ }
9331
+ return `Successfully replaced symbol "${symbol}" in ${file_path} (was lines ${symbolInfo.startLine}-${symbolInfo.endLine}, now ${newLines.length} lines)`;
9332
+ }
9333
+ }
9334
+ function buildLineEditResponse(file_path, startLine, endLine, newLineCount, updatedLines, insertOffset, action, heuristicMods) {
9335
+ const contextBefore = 1;
9336
+ const contextAfter = 1;
9337
+ const contextStart = Math.max(0, insertOffset - contextBefore);
9338
+ const contextEnd = Math.min(updatedLines.length, insertOffset + newLineCount + contextAfter);
9339
+ let context = "Context:\n";
9340
+ for (let i = contextStart; i < contextEnd; i++) {
9341
+ const lineNum = i + 1;
9342
+ const hash = computeLineHash(updatedLines[i]);
9343
+ const isNew = i >= insertOffset && i < insertOffset + newLineCount;
9344
+ const marker = isNew ? ">" : " ";
9345
+ context += `${marker} ${lineNum}:${hash} | ${updatedLines[i]}
9346
+ `;
9347
+ }
9348
+ let msg = `Successfully edited ${file_path} (${action})`;
9349
+ if (heuristicMods.length > 0) {
9350
+ msg += ` [auto-corrected: ${heuristicMods.join(", ")}]`;
9351
+ }
9352
+ msg += "\n" + context;
9353
+ return msg;
9354
+ }
9355
+ async function handleLineEdit({ resolvedPath, file_path, start_line, end_line, new_string, position, debug, fileTracker }) {
9356
+ const startRef = parseLineRef(start_line);
9357
+ if (!startRef) {
9358
+ return `Error editing file: Invalid start_line '${start_line}'. Use a line number (e.g. "42") or line:hash (e.g. "42:ab"). Line numbers are 1-indexed.`;
9359
+ }
9360
+ let endRef = null;
9361
+ if (end_line !== void 0 && end_line !== null) {
9362
+ endRef = parseLineRef(end_line);
9363
+ if (!endRef) {
9364
+ return `Error editing file: Invalid end_line '${end_line}'. Use a line number (e.g. "55") or line:hash (e.g. "55:cd"). Must be >= start_line.`;
9365
+ }
9366
+ }
9367
+ const startLine = startRef.line;
9368
+ const endLine = endRef ? endRef.line : startLine;
9369
+ if (endLine < startLine) {
9370
+ return `Error editing file: end_line (${endLine}) must be >= start_line (${startLine}).`;
9371
+ }
9372
+ if (position !== void 0 && position !== null && position !== "before" && position !== "after") {
9373
+ return 'Error editing file: Invalid position - must be "before" or "after". Use position="before" to insert before the line, or position="after" to insert after it.';
9374
+ }
9375
+ const content = await fs6.readFile(resolvedPath, "utf-8");
9376
+ const fileLines = content.split("\n");
9377
+ if (startLine > fileLines.length) {
9378
+ return `Error editing file: Line ${startLine} is beyond file length (${fileLines.length} lines). Use 'extract' to read the current file content.`;
9379
+ }
9380
+ if (endLine > fileLines.length) {
9381
+ return `Error editing file: Line ${endLine} is beyond file length (${fileLines.length} lines). Use 'extract' to read the current file content.`;
9382
+ }
9383
+ if (startRef.hash) {
9384
+ const validation = validateLineHash(startLine, startRef.hash, fileLines);
9385
+ if (!validation.valid) {
9386
+ return `Error editing file: Line ${startLine} has changed since last read. Expected hash '${startRef.hash}' but content is now: ${startLine}:${validation.actualHash} | ${validation.actualContent}. Use '${startLine}:${validation.actualHash}' instead.`;
9387
+ }
9388
+ }
9389
+ if (endRef && endRef.hash) {
9390
+ const validation = validateLineHash(endLine, endRef.hash, fileLines);
9391
+ if (!validation.valid) {
9392
+ return `Error editing file: Line ${endLine} has changed since last read. Expected hash '${endRef.hash}' but content is now: ${endLine}:${validation.actualHash} | ${validation.actualContent}. Use '${endLine}:${validation.actualHash}' instead.`;
9393
+ }
9394
+ }
9395
+ const { cleaned, modifications } = cleanNewString(new_string, fileLines, startLine, endLine, position);
9396
+ if (debug) {
9397
+ if (modifications.length > 0) {
9398
+ console.error(`[Edit] Heuristic corrections: ${modifications.join(", ")}`);
9399
+ }
9400
+ }
9401
+ const newLines = cleaned === "" ? [] : cleaned.split("\n");
9402
+ if (position === "after") {
9403
+ fileLines.splice(startLine, 0, ...newLines);
9404
+ await fs6.writeFile(resolvedPath, fileLines.join("\n"), "utf-8");
9405
+ if (fileTracker) await fileTracker.trackFileAfterWrite(resolvedPath);
9406
+ const action = `${newLines.length} line${newLines.length !== 1 ? "s" : ""} inserted after line ${startLine}`;
9407
+ return buildLineEditResponse(file_path, startLine, startLine, newLines.length, fileLines, startLine, action, modifications);
9408
+ } else if (position === "before") {
9409
+ fileLines.splice(startLine - 1, 0, ...newLines);
9410
+ await fs6.writeFile(resolvedPath, fileLines.join("\n"), "utf-8");
9411
+ if (fileTracker) await fileTracker.trackFileAfterWrite(resolvedPath);
9412
+ const action = `${newLines.length} line${newLines.length !== 1 ? "s" : ""} inserted before line ${startLine}`;
9413
+ return buildLineEditResponse(file_path, startLine, startLine, newLines.length, fileLines, startLine - 1, action, modifications);
9414
+ } else {
9415
+ const replacedCount = endLine - startLine + 1;
9416
+ fileLines.splice(startLine - 1, replacedCount, ...newLines);
9417
+ await fs6.writeFile(resolvedPath, fileLines.join("\n"), "utf-8");
9418
+ if (fileTracker) await fileTracker.trackFileAfterWrite(resolvedPath);
9419
+ let action;
9420
+ if (newLines.length === 0) {
9421
+ action = `${replacedCount} line${replacedCount !== 1 ? "s" : ""} deleted (lines ${startLine}-${endLine})`;
9422
+ } else if (startLine === endLine) {
9423
+ action = `line ${startLine} replaced with ${newLines.length} line${newLines.length !== 1 ? "s" : ""}`;
9424
+ } else {
9425
+ action = `lines ${startLine}-${endLine} replaced with ${newLines.length} line${newLines.length !== 1 ? "s" : ""}`;
9426
+ }
9427
+ return buildLineEditResponse(file_path, startLine, endLine, newLines.length, fileLines, startLine - 1, action, modifications);
9428
+ }
9429
+ }
8865
9430
  var editTool, createTool, editSchema, createSchema, editDescription, createDescription, editToolDefinition, createToolDefinition;
8866
9431
  var init_edit = __esm({
8867
9432
  "src/tools/edit.js"() {
8868
9433
  "use strict";
8869
9434
  init_path_validation();
9435
+ init_fuzzyMatch();
9436
+ init_symbolEdit();
9437
+ init_hashline();
9438
+ init_lineEditHeuristics();
8870
9439
  editTool = (options = {}) => {
8871
9440
  const { debug, allowedFolders, cwd, workspaceRoot } = parseFileToolOptions(options);
8872
9441
  return tool({
8873
9442
  name: "edit",
8874
- description: `Edit files using exact string replacement (Claude Code style).
9443
+ description: `Edit files using text replacement, AST-aware symbol operations, or line-targeted editing.
8875
9444
 
8876
- This tool performs exact string replacements in files. It requires the old_string to match exactly what's in the file, including all whitespace and indentation.
9445
+ Modes:
9446
+ 1. Text edit: Provide old_string + new_string to find and replace text (with fuzzy matching fallback)
9447
+ 2. Symbol replace: Provide symbol + new_string to replace an entire function/class/method by name
9448
+ 3. Symbol insert: Provide symbol + new_string + position to insert code before/after a symbol
9449
+ 4. Line-targeted edit: Provide start_line + new_string to edit by line number (from extract/search output)
8877
9450
 
8878
9451
  Parameters:
8879
9452
  - file_path: Path to the file to edit (absolute or relative)
8880
- - old_string: Exact text to find and replace (must be unique in the file unless replace_all is true)
8881
- - new_string: Text to replace with
8882
- - replace_all: (optional) Replace all occurrences instead of requiring uniqueness
8883
-
8884
- Important:
8885
- - The old_string must match EXACTLY including whitespace
8886
- - If old_string appears multiple times and replace_all is false, the edit will fail
8887
- - Use larger context around the string to ensure uniqueness when needed`,
9453
+ - new_string: Replacement text or new code content
9454
+ - old_string: (optional) Text to find and replace. If omitted, symbol or start_line must be provided.
9455
+ - replace_all: (optional) Replace all occurrences (text mode only)
9456
+ - symbol: (optional) Symbol name for AST-aware editing (e.g. "myFunction", "MyClass.myMethod")
9457
+ - position: (optional) "before" or "after" \u2014 insert code near a symbol or line instead of replacing it
9458
+ - start_line: (optional) Line reference (e.g. "42" or "42:ab") for line-targeted editing
9459
+ - end_line: (optional) End of line range, inclusive (e.g. "55" or "55:cd")`,
8888
9460
  inputSchema: {
8889
9461
  type: "object",
8890
9462
  properties: {
@@ -8894,30 +9466,44 @@ Important:
8894
9466
  },
8895
9467
  old_string: {
8896
9468
  type: "string",
8897
- description: "Exact text to find and replace"
9469
+ description: "Text to find and replace (for text-based editing)"
8898
9470
  },
8899
9471
  new_string: {
8900
9472
  type: "string",
8901
- description: "Text to replace with"
9473
+ description: "Replacement text or new code content"
8902
9474
  },
8903
9475
  replace_all: {
8904
9476
  type: "boolean",
8905
- description: "Replace all occurrences (default: false)",
9477
+ description: "Replace all occurrences (default: false, text mode only)",
8906
9478
  default: false
9479
+ },
9480
+ symbol: {
9481
+ type: "string",
9482
+ description: 'Symbol name for AST-aware editing (e.g. "myFunction", "MyClass.myMethod")'
9483
+ },
9484
+ position: {
9485
+ type: "string",
9486
+ enum: ["before", "after"],
9487
+ description: "Insert before/after symbol or line (requires symbol or start_line, omit to replace)"
9488
+ },
9489
+ start_line: {
9490
+ type: "string",
9491
+ description: 'Line reference for line-targeted editing (e.g. "42" or "42:ab" with hash)'
9492
+ },
9493
+ end_line: {
9494
+ type: "string",
9495
+ description: 'End of line range, inclusive (e.g. "55" or "55:cd"). Defaults to start_line.'
8907
9496
  }
8908
9497
  },
8909
- required: ["file_path", "old_string", "new_string"]
9498
+ required: ["file_path", "new_string"]
8910
9499
  },
8911
- execute: async ({ file_path, old_string, new_string, replace_all = false }) => {
9500
+ execute: async ({ file_path, old_string, new_string, replace_all = false, symbol, position, start_line, end_line }) => {
8912
9501
  try {
8913
9502
  if (!file_path || typeof file_path !== "string" || file_path.trim() === "") {
8914
- return `Error editing file: Invalid file_path - must be a non-empty string`;
8915
- }
8916
- if (old_string === void 0 || old_string === null || typeof old_string !== "string") {
8917
- return `Error editing file: Invalid old_string - must be a string`;
9503
+ return `Error editing file: Invalid file_path - must be a non-empty string. Provide an absolute path or a path relative to the working directory (e.g. "src/main.js").`;
8918
9504
  }
8919
9505
  if (new_string === void 0 || new_string === null || typeof new_string !== "string") {
8920
- return `Error editing file: Invalid new_string - must be a string`;
9506
+ return `Error editing file: Invalid new_string - must be a string. Provide the replacement content as a string value (empty string "" is valid for deletions).`;
8921
9507
  }
8922
9508
  const resolvedPath = isAbsolute(file_path) ? file_path : resolve(cwd || process.cwd(), file_path);
8923
9509
  if (debug) {
@@ -8925,34 +9511,64 @@ Important:
8925
9511
  }
8926
9512
  if (!isPathAllowed(resolvedPath, allowedFolders)) {
8927
9513
  const relativePath = toRelativePath(resolvedPath, workspaceRoot);
8928
- return `Error editing file: Permission denied - ${relativePath} is outside allowed directories`;
9514
+ return `Error editing file: Permission denied - ${relativePath} is outside allowed directories. Use a file path within the project workspace.`;
8929
9515
  }
8930
9516
  if (!existsSync(resolvedPath)) {
8931
- return `Error editing file: File not found - ${file_path}`;
9517
+ return `Error editing file: File not found - ${file_path}. Verify the path is correct and the file exists. Use 'search' to find files by name, or 'create' to make a new file.`;
9518
+ }
9519
+ if (options.fileTracker && !options.fileTracker.isFileSeen(resolvedPath)) {
9520
+ const displayPath = toRelativePath(resolvedPath, workspaceRoot);
9521
+ return `Error editing ${displayPath}: This file has not been read yet in this session. Use 'extract' to read the file first, then retry your edit. This ensures you are working with the current file content.
9522
+
9523
+ Example: <extract><targets>${displayPath}</targets></extract>`;
9524
+ }
9525
+ if (symbol !== void 0 && symbol !== null) {
9526
+ return await handleSymbolEdit({ resolvedPath, file_path, symbol, new_string, position, debug, cwd, fileTracker: options.fileTracker });
9527
+ }
9528
+ if (start_line !== void 0 && start_line !== null) {
9529
+ return await handleLineEdit({ resolvedPath, file_path, start_line, end_line, new_string, position, debug, fileTracker: options.fileTracker });
9530
+ }
9531
+ if (old_string === void 0 || old_string === null) {
9532
+ return 'Error editing file: Must provide either old_string (for text edit), symbol (for AST-aware edit), or start_line (for line-targeted edit). For text editing: set old_string to the exact text to find and new_string to its replacement. For symbol editing: set symbol to a function/class/method name (e.g. "myFunction"). For line-targeted editing: set start_line to a line number from extract/search output (e.g. "42" or "42:ab").';
9533
+ }
9534
+ if (typeof old_string !== "string") {
9535
+ return `Error editing file: Invalid old_string - must be a string. Provide the exact text to find in the file, or use the symbol parameter instead for AST-aware editing by name.`;
8932
9536
  }
8933
9537
  const content = await fs6.readFile(resolvedPath, "utf-8");
9538
+ let matchTarget = old_string;
9539
+ let matchStrategy = "exact";
8934
9540
  if (!content.includes(old_string)) {
8935
- return `Error editing file: String not found - the specified old_string was not found in ${file_path}`;
9541
+ const fuzzy = findFuzzyMatch(content, old_string);
9542
+ if (!fuzzy) {
9543
+ return `Error editing file: String not found - the specified old_string was not found in ${file_path}. The text may have changed or differ from what you expected. Try: (1) Use 'search' or 'extract' to read the current file content and copy the exact text. (2) Use the symbol parameter to edit by function/class name instead. (3) Verify the file_path is correct.`;
9544
+ }
9545
+ matchTarget = fuzzy.matchedText;
9546
+ matchStrategy = fuzzy.strategy;
9547
+ if (debug) {
9548
+ console.error(`[Edit] Exact match failed, used ${matchStrategy} matching`);
9549
+ }
8936
9550
  }
8937
- const occurrences = content.split(old_string).length - 1;
9551
+ const occurrences = content.split(matchTarget).length - 1;
8938
9552
  if (!replace_all && occurrences > 1) {
8939
- return `Error editing file: Multiple occurrences found - the old_string appears ${occurrences} times. Use replace_all: true to replace all occurrences, or provide more context to make the string unique.`;
9553
+ return `Error editing file: Multiple occurrences found - the old_string appears ${occurrences} times in ${file_path}. To fix: (1) Set replace_all=true to replace all occurrences, or (2) Include more surrounding lines in old_string to make the match unique (add the full line or adjacent lines for context).`;
8940
9554
  }
8941
9555
  let newContent;
8942
9556
  if (replace_all) {
8943
- newContent = content.replaceAll(old_string, new_string);
9557
+ newContent = content.replaceAll(matchTarget, new_string);
8944
9558
  } else {
8945
- newContent = content.replace(old_string, new_string);
9559
+ newContent = content.replace(matchTarget, new_string);
8946
9560
  }
8947
9561
  if (newContent === content) {
8948
- return `Error editing file: No changes made - old_string and new_string might be the same`;
9562
+ return `Error editing file: No changes made - the replacement result is identical to the original. Verify that old_string and new_string are actually different. If fuzzy matching was used, the matched text may already equal new_string.`;
8949
9563
  }
8950
9564
  await fs6.writeFile(resolvedPath, newContent, "utf-8");
9565
+ if (options.fileTracker) await options.fileTracker.trackFileAfterWrite(resolvedPath);
8951
9566
  const replacedCount = replace_all ? occurrences : 1;
8952
9567
  if (debug) {
8953
9568
  console.error(`[Edit] Successfully edited ${resolvedPath}, replaced ${replacedCount} occurrence(s)`);
8954
9569
  }
8955
- return `Successfully edited ${file_path} (${replacedCount} replacement${replacedCount !== 1 ? "s" : ""})`;
9570
+ const strategyNote = matchStrategy !== "exact" ? `, matched via ${matchStrategy}` : "";
9571
+ return `Successfully edited ${file_path} (${replacedCount} replacement${replacedCount !== 1 ? "s" : ""}${strategyNote})`;
8956
9572
  } catch (error) {
8957
9573
  console.error("[Edit] Error:", error);
8958
9574
  return `Error editing file: ${error.message}`;
@@ -8999,10 +9615,10 @@ Important:
8999
9615
  execute: async ({ file_path, content, overwrite = false }) => {
9000
9616
  try {
9001
9617
  if (!file_path || typeof file_path !== "string" || file_path.trim() === "") {
9002
- return `Error creating file: Invalid file_path - must be a non-empty string`;
9618
+ return `Error creating file: Invalid file_path - must be a non-empty string. Provide an absolute path or a path relative to the working directory (e.g. "src/newFile.js").`;
9003
9619
  }
9004
9620
  if (content === void 0 || content === null || typeof content !== "string") {
9005
- return `Error creating file: Invalid content - must be a string`;
9621
+ return `Error creating file: Invalid content - must be a string. Provide the file content as a string value (empty string "" is valid for an empty file).`;
9006
9622
  }
9007
9623
  const resolvedPath = isAbsolute(file_path) ? file_path : resolve(cwd || process.cwd(), file_path);
9008
9624
  if (debug) {
@@ -9010,15 +9626,17 @@ Important:
9010
9626
  }
9011
9627
  if (!isPathAllowed(resolvedPath, allowedFolders)) {
9012
9628
  const relativePath = toRelativePath(resolvedPath, workspaceRoot);
9013
- return `Error creating file: Permission denied - ${relativePath} is outside allowed directories`;
9629
+ return `Error creating file: Permission denied - ${relativePath} is outside allowed directories. Use a file path within the project workspace.`;
9014
9630
  }
9015
9631
  if (existsSync(resolvedPath) && !overwrite) {
9016
9632
  return `Error creating file: File already exists - ${file_path}. Use overwrite: true to replace it.`;
9017
9633
  }
9634
+ const existed = existsSync(resolvedPath);
9018
9635
  const dir = dirname(resolvedPath);
9019
9636
  await fs6.mkdir(dir, { recursive: true });
9020
9637
  await fs6.writeFile(resolvedPath, content, "utf-8");
9021
- const action = existsSync(resolvedPath) && overwrite ? "overwrote" : "created";
9638
+ if (options.fileTracker) await options.fileTracker.trackFileAfterWrite(resolvedPath);
9639
+ const action = existed && overwrite ? "overwrote" : "created";
9022
9640
  const bytes = Buffer.byteLength(content, "utf-8");
9023
9641
  if (debug) {
9024
9642
  console.error(`[Create] Successfully ${action} ${resolvedPath}`);
@@ -9040,18 +9658,35 @@ Important:
9040
9658
  },
9041
9659
  old_string: {
9042
9660
  type: "string",
9043
- description: "Exact text to find and replace"
9661
+ description: "Text to find and replace (for text-based editing)"
9044
9662
  },
9045
9663
  new_string: {
9046
9664
  type: "string",
9047
- description: "Text to replace with"
9665
+ description: "Replacement text or new code content"
9048
9666
  },
9049
9667
  replace_all: {
9050
9668
  type: "boolean",
9051
- description: "Replace all occurrences (default: false)"
9669
+ description: "Replace all occurrences (default: false, text mode only)"
9670
+ },
9671
+ symbol: {
9672
+ type: "string",
9673
+ description: 'Symbol name for AST-aware editing (e.g. "myFunction", "MyClass.myMethod")'
9674
+ },
9675
+ position: {
9676
+ type: "string",
9677
+ enum: ["before", "after"],
9678
+ description: "Insert before/after symbol or line (requires symbol or start_line, omit to replace)"
9679
+ },
9680
+ start_line: {
9681
+ type: "string",
9682
+ description: 'Line reference for line-targeted editing (e.g. "42" or "42:ab" with hash)'
9683
+ },
9684
+ end_line: {
9685
+ type: "string",
9686
+ description: 'End of line range, inclusive (e.g. "55" or "55:cd"). Defaults to start_line.'
9052
9687
  }
9053
9688
  },
9054
- required: ["file_path", "old_string", "new_string"]
9689
+ required: ["file_path", "new_string"]
9055
9690
  };
9056
9691
  createSchema = {
9057
9692
  type: "object",
@@ -9071,50 +9706,123 @@ Important:
9071
9706
  },
9072
9707
  required: ["file_path", "content"]
9073
9708
  };
9074
- editDescription = "Edit files using exact string replacement. Requires exact match including whitespace.";
9709
+ editDescription = "Edit files using text replacement, AST-aware symbol operations, or line-targeted editing. Supports fuzzy matching for text edits and optional hash-based integrity verification for line edits.";
9075
9710
  createDescription = "Create new files with specified content. Will create parent directories if needed.";
9076
9711
  editToolDefinition = `
9077
9712
  ## edit
9078
9713
  Description: ${editDescription}
9079
9714
 
9080
- When to use:
9081
- - For precise, surgical edits to existing files
9082
- - When you need to change specific lines or blocks of code
9083
- - For renaming functions, variables, or updating configuration values
9084
- - When the exact text to replace is known and unique (or use replace_all for multiple occurrences)
9715
+ Four editing modes \u2014 choose based on the scope of your change:
9085
9716
 
9086
- When NOT to use:
9087
- - For creating new files (use 'create' tool instead)
9088
- - When you cannot determine the exact text to replace
9089
- - When changes span multiple locations that would be better handled together
9717
+ 1. **Text edit** (old_string + new_string): For small, precise changes \u2014 fix a condition, rename a variable, update a value. Provide old_string copied verbatim from the file and new_string with the replacement. Fuzzy matching handles minor whitespace/indentation differences automatically, but always try to copy the exact text.
9718
+
9719
+ 2. **Symbol replace** (symbol + new_string): For replacing an entire function, class, or method by name. No need to quote the old code \u2014 just provide the symbol name and the full new implementation. Indentation is automatically adjusted to match the original. Prefer this mode when rewriting whole definitions.
9720
+
9721
+ 3. **Symbol insert** (symbol + new_string + position): For adding new code before or after an existing symbol. Set position to "before" or "after".
9722
+
9723
+ 4. **Line-targeted edit** (start_line + new_string): For precise edits using line numbers from extract/search output. Use start_line with a line number (e.g. "42") or line:hash (e.g. "42:ab") for integrity verification. Add end_line for multi-line ranges. Use position="before" or "after" to insert instead of replace.
9090
9724
 
9091
9725
  Parameters:
9092
9726
  - file_path: (required) Path to the file to edit
9093
- - old_string: (required) Exact text to find and replace (must match including whitespace, newlines, and indentation)
9094
- - new_string: (required) Text to replace with
9095
- - replace_all: (optional, default: false) Replace all occurrences if the string appears multiple times
9096
-
9097
- Important notes:
9098
- - The old_string MUST match EXACTLY, including all whitespace, indentation, and line breaks
9099
- - If old_string appears multiple times and replace_all is false, the tool will fail
9100
- - Always verify the exact formatting of the text you want to replace
9727
+ - new_string: (required) Replacement text or new code content
9728
+ - old_string: (optional) Text to find and replace \u2014 copy verbatim from the file, do not paraphrase or reformat
9729
+ - replace_all: (optional, default: false) Replace all occurrences of old_string (text mode only)
9730
+ - symbol: (optional) Name of a code symbol (e.g. "myFunction", "MyClass.myMethod") \u2014 must match a function, class, or method definition
9731
+ - position: (optional) "before" or "after" \u2014 insert new_string near the symbol or line instead of replacing it
9732
+ - start_line: (optional) Line reference for line-targeted editing (e.g. "42" or "42:ab")
9733
+ - end_line: (optional) End of line range, inclusive (e.g. "55" or "55:cd"). Defaults to start_line.
9734
+
9735
+ Mode selection rules (priority order):
9736
+ - If symbol is provided, symbol mode is used (old_string and start_line are ignored)
9737
+ - If start_line is provided (without symbol), line-targeted mode is used
9738
+ - If old_string is provided (without symbol or start_line), text mode is used
9739
+ - If none are provided, the tool returns an error with guidance
9740
+
9741
+ When to use each mode:
9742
+ - Small edits (a line or a few lines): use text mode with old_string
9743
+ - Replacing entire functions/classes/methods: use symbol mode \u2014 no exact text matching needed
9744
+ - Editing specific lines from extract/search output: use line-targeted mode with start_line
9745
+ - Editing inside large functions without rewriting them entirely: first use extract with the symbol target (e.g. "file.js#myFunction") to see the function with line numbers, then use start_line/end_line to edit specific lines within it
9746
+
9747
+ Error handling:
9748
+ - If an edit fails, read the error message carefully \u2014 it contains specific instructions for how to fix the call and retry
9749
+ - Common fixes: use 'search'/'extract' to get exact file content, add more context to old_string, switch between text and symbol modes
9750
+ - Line-targeted hash mismatch: the file changed since last read; the error provides updated line:hash references
9101
9751
 
9102
9752
  Examples:
9753
+
9754
+ Text edit (find and replace):
9103
9755
  <edit>
9104
9756
  <file_path>src/main.js</file_path>
9105
- <old_string>function oldName() {
9106
- return 42;
9107
- }</old_string>
9108
- <new_string>function newName() {
9109
- return 42;
9110
- }</new_string>
9757
+ <old_string>return false;</old_string>
9758
+ <new_string>return true;</new_string>
9111
9759
  </edit>
9112
9760
 
9761
+ Text edit with replace_all:
9113
9762
  <edit>
9114
9763
  <file_path>config.json</file_path>
9115
9764
  <old_string>"debug": false</old_string>
9116
9765
  <new_string>"debug": true</new_string>
9117
9766
  <replace_all>true</replace_all>
9767
+ </edit>
9768
+
9769
+ Symbol replace (rewrite entire function by name):
9770
+ <edit>
9771
+ <file_path>src/utils.js</file_path>
9772
+ <symbol>calculateTotal</symbol>
9773
+ <new_string>function calculateTotal(items) {
9774
+ return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
9775
+ }</new_string>
9776
+ </edit>
9777
+
9778
+ Symbol insert (add new function after existing one):
9779
+ <edit>
9780
+ <file_path>src/utils.js</file_path>
9781
+ <symbol>calculateTotal</symbol>
9782
+ <position>after</position>
9783
+ <new_string>function calculateTax(total, rate) {
9784
+ return total * rate;
9785
+ }</new_string>
9786
+ </edit>
9787
+
9788
+ Line-targeted edit (replace a line):
9789
+ <edit>
9790
+ <file_path>src/main.js</file_path>
9791
+ <start_line>42</start_line>
9792
+ <new_string> return processItems(order.items);</new_string>
9793
+ </edit>
9794
+
9795
+ Line-targeted edit (replace a range of lines):
9796
+ <edit>
9797
+ <file_path>src/main.js</file_path>
9798
+ <start_line>42</start_line>
9799
+ <end_line>55</end_line>
9800
+ <new_string> // simplified implementation
9801
+ return processItems(order.items);</new_string>
9802
+ </edit>
9803
+
9804
+ Line-targeted edit with hash verification:
9805
+ <edit>
9806
+ <file_path>src/main.js</file_path>
9807
+ <start_line>42:ab</start_line>
9808
+ <end_line>55:cd</end_line>
9809
+ <new_string> return processItems(order.items);</new_string>
9810
+ </edit>
9811
+
9812
+ Line-targeted insert (add code after a line):
9813
+ <edit>
9814
+ <file_path>src/main.js</file_path>
9815
+ <start_line>42</start_line>
9816
+ <position>after</position>
9817
+ <new_string> const validated = validate(input);</new_string>
9818
+ </edit>
9819
+
9820
+ Line-targeted delete (remove lines):
9821
+ <edit>
9822
+ <file_path>src/main.js</file_path>
9823
+ <start_line>42</start_line>
9824
+ <end_line>45</end_line>
9825
+ <new_string></new_string>
9118
9826
  </edit>`;
9119
9827
  createToolDefinition = `
9120
9828
  ## create
@@ -9652,7 +10360,7 @@ function getValidParamsForTool(toolName) {
9652
10360
  };
9653
10361
  const schema = schemaMap[toolName];
9654
10362
  if (!schema) {
9655
- return ["path", "directory", "pattern", "recursive", "includeHidden", "task", "files", "autoCommits", "result"];
10363
+ return ["path", "directory", "pattern", "recursive", "includeHidden", "task", "files", "result"];
9656
10364
  }
9657
10365
  if (toolName === "attempt_completion") {
9658
10366
  return ["result"];
@@ -9773,7 +10481,6 @@ function detectUnrecognizedToolCall(xmlString, validTools) {
9773
10481
  "listSkills",
9774
10482
  "useSkill",
9775
10483
  "readImage",
9776
- "implement",
9777
10484
  "edit",
9778
10485
  "create",
9779
10486
  "delegate",
@@ -10147,6 +10854,8 @@ User: Read file inside the dependency
10147
10854
  </extract>
10148
10855
 
10149
10856
  </examples>
10857
+
10858
+ **Edit Integration:** The line numbers shown in extract output (e.g. "42 | code") can be used directly with the edit tool's start_line/end_line parameters for precise line-targeted editing. To edit inside a large function: extract it by symbol name first (e.g. "file.js#myFunction"), then use the line numbers from the output to make surgical edits with start_line/end_line.
10150
10859
  `;
10151
10860
  delegateToolDefinition = `
10152
10861
  ## delegate
@@ -10302,7 +11011,7 @@ Capabilities:
10302
11011
  `;
10303
11012
  searchDescription = "Search code in the repository. Free-form questions are accepted, but Elasticsearch-style keyword queries work best. Use this tool first for any code-related questions.";
10304
11013
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
10305
- extractDescription = "Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files.";
11014
+ extractDescription = "Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files. Line numbers from output can be used with edit start_line/end_line for precise editing.";
10306
11015
  delegateDescription = "Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Used by AI agents to break down complex requests into focused, parallel tasks.";
10307
11016
  analyzeAllDescription = 'Answer questions that require analyzing ALL matching data in the codebase. Use for aggregate questions like "What features exist?", "List all API endpoints", "Count TODO comments". The AI automatically plans the search strategy, processes all results via map-reduce, and synthesizes a comprehensive answer. WARNING: Slower than search - only use when you need complete coverage.';
10308
11017
  DEFAULT_VALID_TOOLS = [
@@ -10317,7 +11026,6 @@ Capabilities:
10317
11026
  "useSkill",
10318
11027
  "listFiles",
10319
11028
  "searchFiles",
10320
- "implement",
10321
11029
  "bash",
10322
11030
  "task",
10323
11031
  "attempt_completion"
@@ -10442,6 +11150,7 @@ var init_vercel = __esm({
10442
11150
  init_analyzeAll();
10443
11151
  init_common();
10444
11152
  init_error_types();
11153
+ init_hashline();
10445
11154
  CODE_SEARCH_SCHEMA = {
10446
11155
  type: "object",
10447
11156
  properties: {
@@ -10460,8 +11169,15 @@ var init_vercel = __esm({
10460
11169
  maxTokens = 2e4,
10461
11170
  debug = false,
10462
11171
  outline = false,
10463
- searchDelegate = false
11172
+ searchDelegate = false,
11173
+ hashLines = false
10464
11174
  } = options;
11175
+ const maybeAnnotate = (result) => {
11176
+ if (hashLines && typeof result === "string") {
11177
+ return annotateOutputWithHashes(result);
11178
+ }
11179
+ return result;
11180
+ };
10465
11181
  return tool2({
10466
11182
  name: "search",
10467
11183
  description: searchDelegate ? `${searchDescription} (delegates code search to a subagent and returns extracted code blocks)` : searchDescription,
@@ -10503,7 +11219,12 @@ var init_vercel = __esm({
10503
11219
  };
10504
11220
  if (!searchDelegate) {
10505
11221
  try {
10506
- return await runRawSearch();
11222
+ const result = maybeAnnotate(await runRawSearch());
11223
+ if (options.fileTracker && typeof result === "string") {
11224
+ options.fileTracker.trackFilesFromOutput(result, options.cwd || ".").catch(() => {
11225
+ });
11226
+ }
11227
+ return result;
10507
11228
  } catch (error) {
10508
11229
  console.error("Error executing search command:", error);
10509
11230
  return formatErrorForAI(error);
@@ -10546,7 +11267,12 @@ var init_vercel = __esm({
10546
11267
  if (debug) {
10547
11268
  console.error("Delegated search returned no targets; falling back to raw search");
10548
11269
  }
10549
- return await runRawSearch();
11270
+ const fallbackResult = maybeAnnotate(await runRawSearch());
11271
+ if (options.fileTracker && typeof fallbackResult === "string") {
11272
+ options.fileTracker.trackFilesFromOutput(fallbackResult, options.cwd || ".").catch(() => {
11273
+ });
11274
+ }
11275
+ return fallbackResult;
10550
11276
  }
10551
11277
  const resolutionBase = searchPaths[0] || options.cwd || ".";
10552
11278
  const resolvedTargets = targets.map((target) => resolveTargetPath(target, resolutionBase));
@@ -10561,13 +11287,18 @@ var init_vercel = __esm({
10561
11287
  const extractResult = await extract(extractOptions);
10562
11288
  if (resolutionBase && typeof extractResult === "string") {
10563
11289
  const wsPrefix = resolutionBase.endsWith("/") ? resolutionBase : resolutionBase + "/";
10564
- return extractResult.split(wsPrefix).join("");
11290
+ return maybeAnnotate(extractResult.split(wsPrefix).join(""));
10565
11291
  }
10566
- return extractResult;
11292
+ return maybeAnnotate(extractResult);
10567
11293
  } catch (error) {
10568
11294
  console.error("Delegated search failed, falling back to raw search:", error);
10569
11295
  try {
10570
- return await runRawSearch();
11296
+ const fallbackResult2 = maybeAnnotate(await runRawSearch());
11297
+ if (options.fileTracker && typeof fallbackResult2 === "string") {
11298
+ options.fileTracker.trackFilesFromOutput(fallbackResult2, options.cwd || ".").catch(() => {
11299
+ });
11300
+ }
11301
+ return fallbackResult2;
10571
11302
  } catch (fallbackError) {
10572
11303
  console.error("Error executing search command:", fallbackError);
10573
11304
  return formatErrorForAI(fallbackError);
@@ -10613,7 +11344,7 @@ var init_vercel = __esm({
10613
11344
  });
10614
11345
  };
10615
11346
  extractTool = (options = {}) => {
10616
- const { debug = false, outline = false } = options;
11347
+ const { debug = false, outline = false, hashLines = false } = options;
10617
11348
  return tool2({
10618
11349
  name: "extract",
10619
11350
  description: extractDescription,
@@ -10630,6 +11361,7 @@ var init_vercel = __esm({
10630
11361
  }
10631
11362
  let tempFilePath = null;
10632
11363
  let extractOptions = { cwd: effectiveCwd };
11364
+ let extractFiles = null;
10633
11365
  if (input_content) {
10634
11366
  const { writeFileSync: writeFileSync2, unlinkSync } = await import("fs");
10635
11367
  const { join: join5 } = await import("path");
@@ -10653,13 +11385,13 @@ var init_vercel = __esm({
10653
11385
  };
10654
11386
  } else if (targets) {
10655
11387
  const parsedTargets = parseTargets(targets);
10656
- const files = parsedTargets.map((target) => resolveTargetPath(target, effectiveCwd));
11388
+ extractFiles = parsedTargets.map((target) => resolveTargetPath(target, effectiveCwd));
10657
11389
  let effectiveFormat = format;
10658
11390
  if (outline && format === "outline-xml") {
10659
11391
  effectiveFormat = "xml";
10660
11392
  }
10661
11393
  extractOptions = {
10662
- files,
11394
+ files: extractFiles,
10663
11395
  cwd: effectiveCwd,
10664
11396
  allowTests: allow_tests ?? true,
10665
11397
  contextLines: context_lines,
@@ -10669,6 +11401,10 @@ var init_vercel = __esm({
10669
11401
  throw new Error("Either targets or input_content must be provided");
10670
11402
  }
10671
11403
  const results = await extract(extractOptions);
11404
+ if (options.fileTracker && extractFiles && extractFiles.length > 0) {
11405
+ options.fileTracker.trackFilesFromExtract(extractFiles, effectiveCwd).catch(() => {
11406
+ });
11407
+ }
10672
11408
  if (tempFilePath) {
10673
11409
  const { unlinkSync } = await import("fs");
10674
11410
  try {
@@ -10680,6 +11416,9 @@ var init_vercel = __esm({
10680
11416
  console.error(`Warning: Failed to remove temporary file: ${cleanupError.message}`);
10681
11417
  }
10682
11418
  }
11419
+ if (hashLines && typeof results === "string") {
11420
+ return annotateOutputWithHashes(results);
11421
+ }
10683
11422
  return results;
10684
11423
  } catch (error) {
10685
11424
  console.error("Error executing extract command:", error);
@@ -12030,7 +12769,7 @@ async function executeBashCommand(command, options = {}) {
12030
12769
  console.log(`[BashExecutor] Working directory: "${cwd}"`);
12031
12770
  console.log(`[BashExecutor] Timeout: ${timeout}ms`);
12032
12771
  }
12033
- return new Promise((resolve8, reject2) => {
12772
+ return new Promise((resolve9, reject2) => {
12034
12773
  const processEnv = {
12035
12774
  ...process.env,
12036
12775
  ...env
@@ -12047,7 +12786,7 @@ async function executeBashCommand(command, options = {}) {
12047
12786
  } else {
12048
12787
  const args = parseCommandForExecution(command);
12049
12788
  if (!args || args.length === 0) {
12050
- resolve8({
12789
+ resolve9({
12051
12790
  success: false,
12052
12791
  error: "Failed to parse command",
12053
12792
  stdout: "",
@@ -12132,7 +12871,7 @@ async function executeBashCommand(command, options = {}) {
12132
12871
  success = false;
12133
12872
  error = `Command exited with code ${code}`;
12134
12873
  }
12135
- resolve8({
12874
+ resolve9({
12136
12875
  success,
12137
12876
  error,
12138
12877
  stdout: stdout.trim(),
@@ -12152,7 +12891,7 @@ async function executeBashCommand(command, options = {}) {
12152
12891
  if (debug) {
12153
12892
  console.log(`[BashExecutor] Spawn error:`, error);
12154
12893
  }
12155
- resolve8({
12894
+ resolve9({
12156
12895
  success: false,
12157
12896
  error: `Failed to execute command: ${error.message}`,
12158
12897
  stdout: "",
@@ -13560,14 +14299,14 @@ var require_executor = __commonJS({
13560
14299
  function asyncDone(callback) {
13561
14300
  let isInstant = false;
13562
14301
  let instant;
13563
- const p = new Promise((resolve8, reject2) => {
14302
+ const p = new Promise((resolve9, reject2) => {
13564
14303
  callback((err, result) => {
13565
14304
  if (err)
13566
14305
  reject2(err);
13567
14306
  else {
13568
14307
  isInstant = true;
13569
14308
  instant = result;
13570
- resolve8({ result });
14309
+ resolve9({ result });
13571
14310
  }
13572
14311
  });
13573
14312
  });
@@ -13590,10 +14329,10 @@ var require_executor = __commonJS({
13590
14329
  }
13591
14330
  async function execAsync4(ticks, tree, scope, context, doneOriginal, inLoopOrSwitch) {
13592
14331
  let done = doneOriginal;
13593
- const p = new Promise((resolve8) => {
14332
+ const p = new Promise((resolve9) => {
13594
14333
  done = (e, r) => {
13595
14334
  doneOriginal(e, r);
13596
- resolve8();
14335
+ resolve9();
13597
14336
  };
13598
14337
  });
13599
14338
  if (!_execNoneRecurse(ticks, tree, scope, context, done, true, inLoopOrSwitch) && utils.isLisp(tree)) {
@@ -22357,33 +23096,31 @@ var init_runtime = __esm({
22357
23096
  }
22358
23097
  });
22359
23098
 
22360
- // node_modules/balanced-match/index.js
22361
- var require_balanced_match = __commonJS({
22362
- "node_modules/balanced-match/index.js"(exports2, module2) {
22363
- "use strict";
22364
- module2.exports = balanced;
22365
- function balanced(a, b, str) {
22366
- if (a instanceof RegExp) a = maybeMatch(a, str);
22367
- if (b instanceof RegExp) b = maybeMatch(b, str);
22368
- var r = range(a, b, str);
23099
+ // node_modules/balanced-match/dist/esm/index.js
23100
+ var balanced, maybeMatch, range;
23101
+ var init_esm = __esm({
23102
+ "node_modules/balanced-match/dist/esm/index.js"() {
23103
+ balanced = (a, b, str) => {
23104
+ const ma = a instanceof RegExp ? maybeMatch(a, str) : a;
23105
+ const mb = b instanceof RegExp ? maybeMatch(b, str) : b;
23106
+ const r = ma !== null && mb != null && range(ma, mb, str);
22369
23107
  return r && {
22370
23108
  start: r[0],
22371
23109
  end: r[1],
22372
23110
  pre: str.slice(0, r[0]),
22373
- body: str.slice(r[0] + a.length, r[1]),
22374
- post: str.slice(r[1] + b.length)
23111
+ body: str.slice(r[0] + ma.length, r[1]),
23112
+ post: str.slice(r[1] + mb.length)
22375
23113
  };
22376
- }
22377
- function maybeMatch(reg, str) {
22378
- var m = str.match(reg);
23114
+ };
23115
+ maybeMatch = (reg, str) => {
23116
+ const m = str.match(reg);
22379
23117
  return m ? m[0] : null;
22380
- }
22381
- balanced.range = range;
22382
- function range(a, b, str) {
22383
- var begs, beg, left, right, result;
22384
- var ai = str.indexOf(a);
22385
- var bi = str.indexOf(b, ai + 1);
22386
- var i = ai;
23118
+ };
23119
+ range = (a, b, str) => {
23120
+ let begs, beg, left, right = void 0, result;
23121
+ let ai = str.indexOf(a);
23122
+ let bi = str.indexOf(b, ai + 1);
23123
+ let i = ai;
22387
23124
  if (ai >= 0 && bi > 0) {
22388
23125
  if (a === b) {
22389
23126
  return [ai, bi];
@@ -22391,14 +23128,16 @@ var require_balanced_match = __commonJS({
22391
23128
  begs = [];
22392
23129
  left = str.length;
22393
23130
  while (i >= 0 && !result) {
22394
- if (i == ai) {
23131
+ if (i === ai) {
22395
23132
  begs.push(i);
22396
23133
  ai = str.indexOf(a, i + 1);
22397
- } else if (begs.length == 1) {
22398
- result = [begs.pop(), bi];
23134
+ } else if (begs.length === 1) {
23135
+ const r = begs.pop();
23136
+ if (r !== void 0)
23137
+ result = [r, bi];
22399
23138
  } else {
22400
23139
  beg = begs.pop();
22401
- if (beg < left) {
23140
+ if (beg !== void 0 && beg < left) {
22402
23141
  left = beg;
22403
23142
  right = bi;
22404
23143
  }
@@ -22406,163 +23145,179 @@ var require_balanced_match = __commonJS({
22406
23145
  }
22407
23146
  i = ai < bi && ai >= 0 ? ai : bi;
22408
23147
  }
22409
- if (begs.length) {
23148
+ if (begs.length && right !== void 0) {
22410
23149
  result = [left, right];
22411
23150
  }
22412
23151
  }
22413
23152
  return result;
22414
- }
23153
+ };
22415
23154
  }
22416
23155
  });
22417
23156
 
22418
- // node_modules/brace-expansion/index.js
22419
- var require_brace_expansion = __commonJS({
22420
- "node_modules/brace-expansion/index.js"(exports2, module2) {
22421
- var balanced = require_balanced_match();
22422
- module2.exports = expandTop;
22423
- var escSlash = "\0SLASH" + Math.random() + "\0";
22424
- var escOpen = "\0OPEN" + Math.random() + "\0";
22425
- var escClose = "\0CLOSE" + Math.random() + "\0";
22426
- var escComma = "\0COMMA" + Math.random() + "\0";
22427
- var escPeriod = "\0PERIOD" + Math.random() + "\0";
22428
- function numeric(str) {
22429
- return parseInt(str, 10) == str ? parseInt(str, 10) : str.charCodeAt(0);
22430
- }
22431
- function escapeBraces(str) {
22432
- return str.split("\\\\").join(escSlash).split("\\{").join(escOpen).split("\\}").join(escClose).split("\\,").join(escComma).split("\\.").join(escPeriod);
22433
- }
22434
- function unescapeBraces(str) {
22435
- return str.split(escSlash).join("\\").split(escOpen).join("{").split(escClose).join("}").split(escComma).join(",").split(escPeriod).join(".");
22436
- }
22437
- function parseCommaParts(str) {
22438
- if (!str)
22439
- return [""];
22440
- var parts = [];
22441
- var m = balanced("{", "}", str);
22442
- if (!m)
22443
- return str.split(",");
22444
- var pre = m.pre;
22445
- var body = m.body;
22446
- var post = m.post;
22447
- var p = pre.split(",");
22448
- p[p.length - 1] += "{" + body + "}";
22449
- var postParts = parseCommaParts(post);
22450
- if (post.length) {
22451
- p[p.length - 1] += postParts.shift();
22452
- p.push.apply(p, postParts);
22453
- }
22454
- parts.push.apply(parts, p);
22455
- return parts;
22456
- }
22457
- function expandTop(str) {
22458
- if (!str)
22459
- return [];
22460
- if (str.substr(0, 2) === "{}") {
22461
- str = "\\{\\}" + str.substr(2);
22462
- }
22463
- return expand2(escapeBraces(str), true).map(unescapeBraces);
22464
- }
22465
- function embrace(str) {
22466
- return "{" + str + "}";
22467
- }
22468
- function isPadded(el) {
22469
- return /^-?0\d/.test(el);
22470
- }
22471
- function lte(i, y) {
22472
- return i <= y;
22473
- }
22474
- function gte(i, y) {
22475
- return i >= y;
23157
+ // node_modules/brace-expansion/dist/esm/index.js
23158
+ function numeric(str) {
23159
+ return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
23160
+ }
23161
+ function escapeBraces(str) {
23162
+ return str.replace(slashPattern, escSlash).replace(openPattern, escOpen).replace(closePattern, escClose).replace(commaPattern, escComma).replace(periodPattern, escPeriod);
23163
+ }
23164
+ function unescapeBraces(str) {
23165
+ return str.replace(escSlashPattern, "\\").replace(escOpenPattern, "{").replace(escClosePattern, "}").replace(escCommaPattern, ",").replace(escPeriodPattern, ".");
23166
+ }
23167
+ function parseCommaParts(str) {
23168
+ if (!str) {
23169
+ return [""];
23170
+ }
23171
+ const parts = [];
23172
+ const m = balanced("{", "}", str);
23173
+ if (!m) {
23174
+ return str.split(",");
23175
+ }
23176
+ const { pre, body, post } = m;
23177
+ const p = pre.split(",");
23178
+ p[p.length - 1] += "{" + body + "}";
23179
+ const postParts = parseCommaParts(post);
23180
+ if (post.length) {
23181
+ ;
23182
+ p[p.length - 1] += postParts.shift();
23183
+ p.push.apply(p, postParts);
23184
+ }
23185
+ parts.push.apply(parts, p);
23186
+ return parts;
23187
+ }
23188
+ function expand(str, options = {}) {
23189
+ if (!str) {
23190
+ return [];
23191
+ }
23192
+ const { max = EXPANSION_MAX } = options;
23193
+ if (str.slice(0, 2) === "{}") {
23194
+ str = "\\{\\}" + str.slice(2);
23195
+ }
23196
+ return expand_(escapeBraces(str), max, true).map(unescapeBraces);
23197
+ }
23198
+ function embrace(str) {
23199
+ return "{" + str + "}";
23200
+ }
23201
+ function isPadded(el) {
23202
+ return /^-?0\d/.test(el);
23203
+ }
23204
+ function lte(i, y) {
23205
+ return i <= y;
23206
+ }
23207
+ function gte(i, y) {
23208
+ return i >= y;
23209
+ }
23210
+ function expand_(str, max, isTop) {
23211
+ const expansions = [];
23212
+ const m = balanced("{", "}", str);
23213
+ if (!m)
23214
+ return [str];
23215
+ const pre = m.pre;
23216
+ const post = m.post.length ? expand_(m.post, max, false) : [""];
23217
+ if (/\$$/.test(m.pre)) {
23218
+ for (let k = 0; k < post.length && k < max; k++) {
23219
+ const expansion = pre + "{" + m.body + "}" + post[k];
23220
+ expansions.push(expansion);
22476
23221
  }
22477
- function expand2(str, isTop) {
22478
- var expansions = [];
22479
- var m = balanced("{", "}", str);
22480
- if (!m) return [str];
22481
- var pre = m.pre;
22482
- var post = m.post.length ? expand2(m.post, false) : [""];
22483
- if (/\$$/.test(m.pre)) {
22484
- for (var k = 0; k < post.length; k++) {
22485
- var expansion = pre + "{" + m.body + "}" + post[k];
22486
- expansions.push(expansion);
22487
- }
22488
- } else {
22489
- var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
22490
- var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
22491
- var isSequence = isNumericSequence || isAlphaSequence;
22492
- var isOptions = m.body.indexOf(",") >= 0;
22493
- if (!isSequence && !isOptions) {
22494
- if (m.post.match(/,(?!,).*\}/)) {
22495
- str = m.pre + "{" + m.body + escClose + m.post;
22496
- return expand2(str);
22497
- }
22498
- return [str];
22499
- }
22500
- var n;
22501
- if (isSequence) {
22502
- n = m.body.split(/\.\./);
22503
- } else {
22504
- n = parseCommaParts(m.body);
22505
- if (n.length === 1) {
22506
- n = expand2(n[0], false).map(embrace);
22507
- if (n.length === 1) {
22508
- return post.map(function(p) {
22509
- return m.pre + n[0] + p;
22510
- });
22511
- }
23222
+ } else {
23223
+ const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
23224
+ const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
23225
+ const isSequence = isNumericSequence || isAlphaSequence;
23226
+ const isOptions = m.body.indexOf(",") >= 0;
23227
+ if (!isSequence && !isOptions) {
23228
+ if (m.post.match(/,(?!,).*\}/)) {
23229
+ str = m.pre + "{" + m.body + escClose + m.post;
23230
+ return expand_(str, max, true);
23231
+ }
23232
+ return [str];
23233
+ }
23234
+ let n;
23235
+ if (isSequence) {
23236
+ n = m.body.split(/\.\./);
23237
+ } else {
23238
+ n = parseCommaParts(m.body);
23239
+ if (n.length === 1 && n[0] !== void 0) {
23240
+ n = expand_(n[0], max, false).map(embrace);
23241
+ if (n.length === 1) {
23242
+ return post.map((p) => m.pre + n[0] + p);
23243
+ }
23244
+ }
23245
+ }
23246
+ let N;
23247
+ if (isSequence && n[0] !== void 0 && n[1] !== void 0) {
23248
+ const x = numeric(n[0]);
23249
+ const y = numeric(n[1]);
23250
+ const width = Math.max(n[0].length, n[1].length);
23251
+ let incr = n.length === 3 && n[2] !== void 0 ? Math.abs(numeric(n[2])) : 1;
23252
+ let test = lte;
23253
+ const reverse = y < x;
23254
+ if (reverse) {
23255
+ incr *= -1;
23256
+ test = gte;
23257
+ }
23258
+ const pad = n.some(isPadded);
23259
+ N = [];
23260
+ for (let i = x; test(i, y); i += incr) {
23261
+ let c;
23262
+ if (isAlphaSequence) {
23263
+ c = String.fromCharCode(i);
23264
+ if (c === "\\") {
23265
+ c = "";
22512
23266
  }
22513
- }
22514
- var N;
22515
- if (isSequence) {
22516
- var x = numeric(n[0]);
22517
- var y = numeric(n[1]);
22518
- var width = Math.max(n[0].length, n[1].length);
22519
- var incr = n.length == 3 ? Math.abs(numeric(n[2])) : 1;
22520
- var test = lte;
22521
- var reverse = y < x;
22522
- if (reverse) {
22523
- incr *= -1;
22524
- test = gte;
22525
- }
22526
- var pad = n.some(isPadded);
22527
- N = [];
22528
- for (var i = x; test(i, y); i += incr) {
22529
- var c;
22530
- if (isAlphaSequence) {
22531
- c = String.fromCharCode(i);
22532
- if (c === "\\")
22533
- c = "";
22534
- } else {
22535
- c = String(i);
22536
- if (pad) {
22537
- var need = width - c.length;
22538
- if (need > 0) {
22539
- var z = new Array(need + 1).join("0");
22540
- if (i < 0)
22541
- c = "-" + z + c.slice(1);
22542
- else
22543
- c = z + c;
22544
- }
23267
+ } else {
23268
+ c = String(i);
23269
+ if (pad) {
23270
+ const need = width - c.length;
23271
+ if (need > 0) {
23272
+ const z = new Array(need + 1).join("0");
23273
+ if (i < 0) {
23274
+ c = "-" + z + c.slice(1);
23275
+ } else {
23276
+ c = z + c;
22545
23277
  }
22546
23278
  }
22547
- N.push(c);
22548
- }
22549
- } else {
22550
- N = [];
22551
- for (var j = 0; j < n.length; j++) {
22552
- N.push.apply(N, expand2(n[j], false));
22553
23279
  }
22554
23280
  }
22555
- for (var j = 0; j < N.length; j++) {
22556
- for (var k = 0; k < post.length; k++) {
22557
- var expansion = pre + N[j] + post[k];
22558
- if (!isTop || isSequence || expansion)
22559
- expansions.push(expansion);
22560
- }
23281
+ N.push(c);
23282
+ }
23283
+ } else {
23284
+ N = [];
23285
+ for (let j = 0; j < n.length; j++) {
23286
+ N.push.apply(N, expand_(n[j], max, false));
23287
+ }
23288
+ }
23289
+ for (let j = 0; j < N.length; j++) {
23290
+ for (let k = 0; k < post.length && expansions.length < max; k++) {
23291
+ const expansion = pre + N[j] + post[k];
23292
+ if (!isTop || isSequence || expansion) {
23293
+ expansions.push(expansion);
22561
23294
  }
22562
23295
  }
22563
- return expansions;
22564
23296
  }
22565
23297
  }
23298
+ return expansions;
23299
+ }
23300
+ var escSlash, escOpen, escClose, escComma, escPeriod, escSlashPattern, escOpenPattern, escClosePattern, escCommaPattern, escPeriodPattern, slashPattern, openPattern, closePattern, commaPattern, periodPattern, EXPANSION_MAX;
23301
+ var init_esm2 = __esm({
23302
+ "node_modules/brace-expansion/dist/esm/index.js"() {
23303
+ init_esm();
23304
+ escSlash = "\0SLASH" + Math.random() + "\0";
23305
+ escOpen = "\0OPEN" + Math.random() + "\0";
23306
+ escClose = "\0CLOSE" + Math.random() + "\0";
23307
+ escComma = "\0COMMA" + Math.random() + "\0";
23308
+ escPeriod = "\0PERIOD" + Math.random() + "\0";
23309
+ escSlashPattern = new RegExp(escSlash, "g");
23310
+ escOpenPattern = new RegExp(escOpen, "g");
23311
+ escClosePattern = new RegExp(escClose, "g");
23312
+ escCommaPattern = new RegExp(escComma, "g");
23313
+ escPeriodPattern = new RegExp(escPeriod, "g");
23314
+ slashPattern = /\\\\/g;
23315
+ openPattern = /\\{/g;
23316
+ closePattern = /\\}/g;
23317
+ commaPattern = /\\,/g;
23318
+ periodPattern = /\\./g;
23319
+ EXPANSION_MAX = 1e5;
23320
+ }
22566
23321
  });
22567
23322
 
22568
23323
  // node_modules/minimatch/dist/esm/assert-valid-pattern.js
@@ -23145,11 +23900,13 @@ var init_ast = __esm({
23145
23900
  let escaping = false;
23146
23901
  let re = "";
23147
23902
  let uflag = false;
23903
+ let inStar = false;
23148
23904
  for (let i = 0; i < glob2.length; i++) {
23149
23905
  const c = glob2.charAt(i);
23150
23906
  if (escaping) {
23151
23907
  escaping = false;
23152
23908
  re += (reSpecials.has(c) ? "\\" : "") + c;
23909
+ inStar = false;
23153
23910
  continue;
23154
23911
  }
23155
23912
  if (c === "\\") {
@@ -23167,16 +23924,19 @@ var init_ast = __esm({
23167
23924
  uflag = uflag || needUflag;
23168
23925
  i += consumed - 1;
23169
23926
  hasMagic2 = hasMagic2 || magic;
23927
+ inStar = false;
23170
23928
  continue;
23171
23929
  }
23172
23930
  }
23173
23931
  if (c === "*") {
23174
- if (noEmpty && glob2 === "*")
23175
- re += starNoEmpty;
23176
- else
23177
- re += star;
23932
+ if (inStar)
23933
+ continue;
23934
+ inStar = true;
23935
+ re += noEmpty && /^[*]+$/.test(glob2) ? starNoEmpty : star;
23178
23936
  hasMagic2 = true;
23179
23937
  continue;
23938
+ } else {
23939
+ inStar = false;
23180
23940
  }
23181
23941
  if (c === "?") {
23182
23942
  re += qmark;
@@ -23202,10 +23962,10 @@ var init_escape = __esm({
23202
23962
  });
23203
23963
 
23204
23964
  // node_modules/minimatch/dist/esm/index.js
23205
- var import_brace_expansion, minimatch, starDotExtRE, starDotExtTest, starDotExtTestDot, starDotExtTestNocase, starDotExtTestNocaseDot, starDotStarRE, starDotStarTest, starDotStarTestDot, dotStarRE, dotStarTest, starRE, starTest, starTestDot, qmarksRE, qmarksTestNocase, qmarksTestNocaseDot, qmarksTestDot, qmarksTest, qmarksTestNoExt, qmarksTestNoExtDot, defaultPlatform, path5, sep3, GLOBSTAR, qmark2, star2, twoStarDot, twoStarNoDot, filter, ext, defaults, braceExpand, makeRe, match, globMagic, regExpEscape2, Minimatch;
23206
- var init_esm = __esm({
23965
+ var minimatch, starDotExtRE, starDotExtTest, starDotExtTestDot, starDotExtTestNocase, starDotExtTestNocaseDot, starDotStarRE, starDotStarTest, starDotStarTestDot, dotStarRE, dotStarTest, starRE, starTest, starTestDot, qmarksRE, qmarksTestNocase, qmarksTestNocaseDot, qmarksTestDot, qmarksTest, qmarksTestNoExt, qmarksTestNoExtDot, defaultPlatform, path5, sep3, GLOBSTAR, qmark2, star2, twoStarDot, twoStarNoDot, filter, ext, defaults, braceExpand, makeRe, match, globMagic, regExpEscape2, Minimatch;
23966
+ var init_esm3 = __esm({
23207
23967
  "node_modules/minimatch/dist/esm/index.js"() {
23208
- import_brace_expansion = __toESM(require_brace_expansion(), 1);
23968
+ init_esm2();
23209
23969
  init_assert_valid_pattern();
23210
23970
  init_ast();
23211
23971
  init_escape();
@@ -23328,7 +24088,7 @@ var init_esm = __esm({
23328
24088
  if (options.nobrace || !/\{(?:(?!\{).)*\}/.test(pattern)) {
23329
24089
  return [pattern];
23330
24090
  }
23331
- return (0, import_brace_expansion.default)(pattern);
24091
+ return expand(pattern);
23332
24092
  };
23333
24093
  minimatch.braceExpand = braceExpand;
23334
24094
  makeRe = (pattern, options = {}) => new Minimatch(pattern, options).makeRe();
@@ -23934,7 +24694,7 @@ var init_esm = __esm({
23934
24694
 
23935
24695
  // node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js
23936
24696
  var perf, warned, PROCESS, emitWarning, AC, AS, shouldWarn, TYPE, isPosInt, getUintArray, ZeroArray, Stack, LRUCache;
23937
- var init_esm2 = __esm({
24697
+ var init_esm4 = __esm({
23938
24698
  "node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js"() {
23939
24699
  perf = typeof performance === "object" && performance && typeof performance.now === "function" ? performance : Date;
23940
24700
  warned = /* @__PURE__ */ new Set();
@@ -25311,7 +26071,7 @@ import { EventEmitter } from "node:events";
25311
26071
  import Stream from "node:stream";
25312
26072
  import { StringDecoder } from "node:string_decoder";
25313
26073
  var proc, isStream, isReadable, isWritable, EOF, MAYBE_EMIT_END, EMITTED_END, EMITTING_END, EMITTED_ERROR, CLOSED, READ, FLUSH, FLUSHCHUNK, ENCODING, DECODER, FLOWING, PAUSED, RESUME, BUFFER, PIPES, BUFFERLENGTH, BUFFERPUSH, BUFFERSHIFT, OBJECTMODE, DESTROYED, ERROR, EMITDATA, EMITEND, EMITEND2, ASYNC, ABORT, ABORTED, SIGNAL, DATALISTENERS, DISCARDED, defer, nodefer, isEndish, isArrayBufferLike, isArrayBufferView, Pipe, PipeProxyErrors, isObjectModeOptions, isEncodingOptions, Minipass;
25314
- var init_esm3 = __esm({
26074
+ var init_esm5 = __esm({
25315
26075
  "node_modules/minipass/dist/esm/index.js"() {
25316
26076
  proc = typeof process === "object" && process ? process : {
25317
26077
  stdout: null,
@@ -26038,10 +26798,10 @@ var init_esm3 = __esm({
26038
26798
  * Return a void Promise that resolves once the stream ends.
26039
26799
  */
26040
26800
  async promise() {
26041
- return new Promise((resolve8, reject2) => {
26801
+ return new Promise((resolve9, reject2) => {
26042
26802
  this.on(DESTROYED, () => reject2(new Error("stream destroyed")));
26043
26803
  this.on("error", (er) => reject2(er));
26044
- this.on("end", () => resolve8());
26804
+ this.on("end", () => resolve9());
26045
26805
  });
26046
26806
  }
26047
26807
  /**
@@ -26065,7 +26825,7 @@ var init_esm3 = __esm({
26065
26825
  return Promise.resolve({ done: false, value: res });
26066
26826
  if (this[EOF])
26067
26827
  return stop();
26068
- let resolve8;
26828
+ let resolve9;
26069
26829
  let reject2;
26070
26830
  const onerr = (er) => {
26071
26831
  this.off("data", ondata);
@@ -26079,19 +26839,19 @@ var init_esm3 = __esm({
26079
26839
  this.off("end", onend);
26080
26840
  this.off(DESTROYED, ondestroy);
26081
26841
  this.pause();
26082
- resolve8({ value, done: !!this[EOF] });
26842
+ resolve9({ value, done: !!this[EOF] });
26083
26843
  };
26084
26844
  const onend = () => {
26085
26845
  this.off("error", onerr);
26086
26846
  this.off("data", ondata);
26087
26847
  this.off(DESTROYED, ondestroy);
26088
26848
  stop();
26089
- resolve8({ done: true, value: void 0 });
26849
+ resolve9({ done: true, value: void 0 });
26090
26850
  };
26091
26851
  const ondestroy = () => onerr(new Error("stream destroyed"));
26092
26852
  return new Promise((res2, rej) => {
26093
26853
  reject2 = rej;
26094
- resolve8 = res2;
26854
+ resolve9 = res2;
26095
26855
  this.once(DESTROYED, ondestroy);
26096
26856
  this.once("error", onerr);
26097
26857
  this.once("end", onend);
@@ -26200,10 +26960,10 @@ import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSyn
26200
26960
  import * as actualFS from "node:fs";
26201
26961
  import { lstat, readdir, readlink, realpath } from "node:fs/promises";
26202
26962
  var realpathSync2, defaultFS, fsFromOption, uncDriveRegexp, uncToDrive, eitherSep, UNKNOWN, IFIFO, IFCHR, IFDIR, IFBLK, IFREG, IFLNK, IFSOCK, IFMT, IFMT_UNKNOWN, READDIR_CALLED, LSTAT_CALLED, ENOTDIR, ENOENT, ENOREADLINK, ENOREALPATH, ENOCHILD, TYPEMASK, entToType, normalizeCache, normalize, normalizeNocaseCache, normalizeNocase, ResolveCache, ChildrenCache, setAsCwd, PathBase, PathWin32, PathPosix, PathScurryBase, PathScurryWin32, PathScurryPosix, PathScurryDarwin, Path, PathScurry;
26203
- var init_esm4 = __esm({
26963
+ var init_esm6 = __esm({
26204
26964
  "node_modules/path-scurry/dist/esm/index.js"() {
26205
- init_esm2();
26206
- init_esm3();
26965
+ init_esm4();
26966
+ init_esm5();
26207
26967
  realpathSync2 = rps.native;
26208
26968
  defaultFS = {
26209
26969
  lstatSync,
@@ -27075,9 +27835,9 @@ var init_esm4 = __esm({
27075
27835
  if (this.#asyncReaddirInFlight) {
27076
27836
  await this.#asyncReaddirInFlight;
27077
27837
  } else {
27078
- let resolve8 = () => {
27838
+ let resolve9 = () => {
27079
27839
  };
27080
- this.#asyncReaddirInFlight = new Promise((res) => resolve8 = res);
27840
+ this.#asyncReaddirInFlight = new Promise((res) => resolve9 = res);
27081
27841
  try {
27082
27842
  for (const e of await this.#fs.promises.readdir(fullpath, {
27083
27843
  withFileTypes: true
@@ -27090,7 +27850,7 @@ var init_esm4 = __esm({
27090
27850
  children.provisional = 0;
27091
27851
  }
27092
27852
  this.#asyncReaddirInFlight = void 0;
27093
- resolve8();
27853
+ resolve9();
27094
27854
  }
27095
27855
  return children.slice(0, children.provisional);
27096
27856
  }
@@ -27933,7 +28693,7 @@ var init_esm4 = __esm({
27933
28693
  var isPatternList, isGlobList, Pattern;
27934
28694
  var init_pattern = __esm({
27935
28695
  "node_modules/glob/dist/esm/pattern.js"() {
27936
- init_esm();
28696
+ init_esm3();
27937
28697
  isPatternList = (pl) => pl.length >= 1;
27938
28698
  isGlobList = (gl) => gl.length >= 1;
27939
28699
  Pattern = class _Pattern {
@@ -28104,7 +28864,7 @@ var init_pattern = __esm({
28104
28864
  var defaultPlatform2, Ignore;
28105
28865
  var init_ignore = __esm({
28106
28866
  "node_modules/glob/dist/esm/ignore.js"() {
28107
- init_esm();
28867
+ init_esm3();
28108
28868
  init_pattern();
28109
28869
  defaultPlatform2 = typeof process === "object" && process && typeof process.platform === "string" ? process.platform : "linux";
28110
28870
  Ignore = class {
@@ -28198,7 +28958,7 @@ var init_ignore = __esm({
28198
28958
  var HasWalkedCache, MatchRecord, SubWalks, Processor;
28199
28959
  var init_processor = __esm({
28200
28960
  "node_modules/glob/dist/esm/processor.js"() {
28201
- init_esm();
28961
+ init_esm3();
28202
28962
  HasWalkedCache = class _HasWalkedCache {
28203
28963
  store;
28204
28964
  constructor(store = /* @__PURE__ */ new Map()) {
@@ -28425,7 +29185,7 @@ var init_processor = __esm({
28425
29185
  var makeIgnore, GlobUtil, GlobWalker, GlobStream;
28426
29186
  var init_walker = __esm({
28427
29187
  "node_modules/glob/dist/esm/walker.js"() {
28428
- init_esm3();
29188
+ init_esm5();
28429
29189
  init_ignore();
28430
29190
  init_processor();
28431
29191
  makeIgnore = (ignore2, opts) => typeof ignore2 === "string" ? new Ignore([ignore2], opts) : Array.isArray(ignore2) ? new Ignore(ignore2, opts) : ignore2;
@@ -28761,8 +29521,8 @@ import { fileURLToPath as fileURLToPath5 } from "node:url";
28761
29521
  var defaultPlatform3, Glob;
28762
29522
  var init_glob = __esm({
28763
29523
  "node_modules/glob/dist/esm/glob.js"() {
28764
- init_esm();
28765
- init_esm4();
29524
+ init_esm3();
29525
+ init_esm6();
28766
29526
  init_pattern();
28767
29527
  init_walker();
28768
29528
  defaultPlatform3 = typeof process === "object" && process && typeof process.platform === "string" ? process.platform : "linux";
@@ -28970,7 +29730,7 @@ var init_glob = __esm({
28970
29730
  var hasMagic;
28971
29731
  var init_has_magic = __esm({
28972
29732
  "node_modules/glob/dist/esm/has-magic.js"() {
28973
- init_esm();
29733
+ init_esm3();
28974
29734
  hasMagic = (pattern, options = {}) => {
28975
29735
  if (!Array.isArray(pattern)) {
28976
29736
  pattern = [pattern];
@@ -29004,12 +29764,12 @@ function globIterate(pattern, options = {}) {
29004
29764
  return new Glob(pattern, options).iterate();
29005
29765
  }
29006
29766
  var streamSync, stream, iterateSync, iterate, sync, glob;
29007
- var init_esm5 = __esm({
29767
+ var init_esm7 = __esm({
29008
29768
  "node_modules/glob/dist/esm/index.js"() {
29009
- init_esm();
29769
+ init_esm3();
29010
29770
  init_glob();
29011
29771
  init_has_magic();
29012
- init_esm();
29772
+ init_esm3();
29013
29773
  init_glob();
29014
29774
  init_has_magic();
29015
29775
  init_ignore();
@@ -29969,7 +30729,7 @@ var init_executePlan = __esm({
29969
30729
  init_query();
29970
30730
  init_extract();
29971
30731
  init_delegate();
29972
- init_esm5();
30732
+ init_esm7();
29973
30733
  init_bash();
29974
30734
  RAW_OUTPUT_START = "<<<RAW_OUTPUT>>>";
29975
30735
  RAW_OUTPUT_END = "<<<END_RAW_OUTPUT>>>";
@@ -30241,6 +31001,264 @@ var init_file_lister = __esm({
30241
31001
  }
30242
31002
  });
30243
31003
 
31004
+ // src/tools/fileTracker.js
31005
+ import { createHash as createHash2 } from "crypto";
31006
+ import { resolve as resolve5, isAbsolute as isAbsolute4 } from "path";
31007
+ function computeContentHash(content) {
31008
+ const normalized = (content || "").split("\n").map((l) => l.trimEnd()).join("\n");
31009
+ return createHash2("sha256").update(normalized).digest("hex").slice(0, 16);
31010
+ }
31011
+ function extractFilePath(target) {
31012
+ const hashIdx = target.indexOf("#");
31013
+ if (hashIdx !== -1) {
31014
+ return target.slice(0, hashIdx);
31015
+ }
31016
+ const colonIdx = target.lastIndexOf(":");
31017
+ if (colonIdx !== -1) {
31018
+ const after = target.slice(colonIdx + 1);
31019
+ if (/^\d+(-\d+)?$/.test(after)) {
31020
+ return target.slice(0, colonIdx);
31021
+ }
31022
+ }
31023
+ return target;
31024
+ }
31025
+ function extractSymbolName(target) {
31026
+ const hashIdx = target.indexOf("#");
31027
+ if (hashIdx !== -1) {
31028
+ const symbol = target.slice(hashIdx + 1);
31029
+ return symbol || null;
31030
+ }
31031
+ return null;
31032
+ }
31033
+ function parseFilePathsFromOutput(output) {
31034
+ const paths = [];
31035
+ const regex = /^(?:File:\s+|---\s+)([^\s].*?)(?:\s+---)?$/gm;
31036
+ let match2;
31037
+ while ((match2 = regex.exec(output)) !== null) {
31038
+ const path9 = match2[1].trim();
31039
+ if (path9 && !path9.startsWith("Results") && !path9.startsWith("Page") && (path9.includes("/") || path9.includes(".") || path9.includes("\\"))) {
31040
+ paths.push(path9);
31041
+ }
31042
+ }
31043
+ return paths;
31044
+ }
31045
+ var FileTracker;
31046
+ var init_fileTracker = __esm({
31047
+ "src/tools/fileTracker.js"() {
31048
+ "use strict";
31049
+ init_symbolEdit();
31050
+ FileTracker = class {
31051
+ /**
31052
+ * @param {Object} [options]
31053
+ * @param {boolean} [options.debug=false] - Enable debug logging
31054
+ */
31055
+ constructor(options = {}) {
31056
+ this.debug = options.debug || false;
31057
+ this._seenFiles = /* @__PURE__ */ new Set();
31058
+ this._contentRecords = /* @__PURE__ */ new Map();
31059
+ }
31060
+ /**
31061
+ * Mark a file as "seen" — the LLM has read its content.
31062
+ * @param {string} resolvedPath - Absolute path to the file
31063
+ */
31064
+ markFileSeen(resolvedPath) {
31065
+ this._seenFiles.add(resolvedPath);
31066
+ if (this.debug) {
31067
+ console.error(`[FileTracker] Marked as seen: ${resolvedPath}`);
31068
+ }
31069
+ }
31070
+ /**
31071
+ * Check if a file has been seen in this session.
31072
+ * @param {string} resolvedPath - Absolute path to the file
31073
+ * @returns {boolean}
31074
+ */
31075
+ isFileSeen(resolvedPath) {
31076
+ return this._seenFiles.has(resolvedPath);
31077
+ }
31078
+ /**
31079
+ * Store a content hash for a symbol in a file.
31080
+ * @param {string} resolvedPath - Absolute path to the file
31081
+ * @param {string} symbolName - Symbol name (e.g. "calculateTotal")
31082
+ * @param {string} code - The symbol's source code
31083
+ * @param {number} startLine - 1-indexed start line
31084
+ * @param {number} endLine - 1-indexed end line
31085
+ * @param {string} [source='extract'] - How the content was obtained
31086
+ */
31087
+ trackSymbolContent(resolvedPath, symbolName, code, startLine, endLine, source = "extract") {
31088
+ const key = `${resolvedPath}#${symbolName}`;
31089
+ const contentHash = computeContentHash(code);
31090
+ this._contentRecords.set(key, {
31091
+ contentHash,
31092
+ startLine,
31093
+ endLine,
31094
+ symbolName,
31095
+ source,
31096
+ timestamp: Date.now()
31097
+ });
31098
+ if (this.debug) {
31099
+ console.error(`[FileTracker] Tracked symbol ${key} (hash: ${contentHash}, lines ${startLine}-${endLine})`);
31100
+ }
31101
+ }
31102
+ /**
31103
+ * Look up a stored content record for a symbol.
31104
+ * @param {string} resolvedPath - Absolute path to the file
31105
+ * @param {string} symbolName - Symbol name
31106
+ * @returns {Object|null} The stored record or null
31107
+ */
31108
+ getSymbolRecord(resolvedPath, symbolName) {
31109
+ return this._contentRecords.get(`${resolvedPath}#${symbolName}`) || null;
31110
+ }
31111
+ /**
31112
+ * Check if a symbol's current content matches what was stored.
31113
+ * @param {string} resolvedPath - Absolute path to the file
31114
+ * @param {string} symbolName - Symbol name
31115
+ * @param {string} currentCode - The symbol's current source code (from findSymbol)
31116
+ * @returns {{ok: boolean, reason?: string, message?: string}}
31117
+ */
31118
+ checkSymbolContent(resolvedPath, symbolName, currentCode) {
31119
+ const key = `${resolvedPath}#${symbolName}`;
31120
+ const record = this._contentRecords.get(key);
31121
+ if (!record) {
31122
+ return { ok: true };
31123
+ }
31124
+ const currentHash = computeContentHash(currentCode);
31125
+ if (currentHash === record.contentHash) {
31126
+ return { ok: true };
31127
+ }
31128
+ return {
31129
+ ok: false,
31130
+ reason: "stale",
31131
+ message: `Symbol "${symbolName}" has changed since you last read it (hash: ${record.contentHash} \u2192 ${currentHash}).`
31132
+ };
31133
+ }
31134
+ /**
31135
+ * Track files from extract target strings.
31136
+ * Marks each file as seen. For #symbol targets, calls findSymbol to get and hash the code.
31137
+ * @param {string[]} targets - Array of extract targets (e.g. ["file.js#fn", "file.js:10-20"])
31138
+ * @param {string} cwd - Working directory for resolving relative paths
31139
+ */
31140
+ async trackFilesFromExtract(targets, cwd) {
31141
+ const seenPaths = /* @__PURE__ */ new Set();
31142
+ const symbolPromises = [];
31143
+ for (const target of targets) {
31144
+ const filePath = extractFilePath(target);
31145
+ const resolved = isAbsolute4(filePath) ? filePath : resolve5(cwd, filePath);
31146
+ if (!seenPaths.has(resolved)) {
31147
+ seenPaths.add(resolved);
31148
+ this.markFileSeen(resolved);
31149
+ }
31150
+ const symbolName = extractSymbolName(target);
31151
+ if (symbolName) {
31152
+ symbolPromises.push(
31153
+ findSymbol(resolved, symbolName, cwd).then((symbolInfo) => {
31154
+ if (symbolInfo) {
31155
+ this.trackSymbolContent(
31156
+ resolved,
31157
+ symbolName,
31158
+ symbolInfo.code,
31159
+ symbolInfo.startLine,
31160
+ symbolInfo.endLine,
31161
+ "extract"
31162
+ );
31163
+ }
31164
+ }).catch((err) => {
31165
+ if (this.debug) {
31166
+ console.error(`[FileTracker] Failed to track symbol "${symbolName}" in ${resolved}: ${err.message}`);
31167
+ }
31168
+ })
31169
+ );
31170
+ }
31171
+ }
31172
+ if (symbolPromises.length > 0) {
31173
+ await Promise.all(symbolPromises);
31174
+ }
31175
+ }
31176
+ /**
31177
+ * Track files discovered in probe search/extract output.
31178
+ * Parses "File: path" headers and "--- path ---" separators, marks each as "seen".
31179
+ * @param {string} output - Probe output text
31180
+ * @param {string} cwd - Working directory for resolving relative paths
31181
+ */
31182
+ async trackFilesFromOutput(output, cwd) {
31183
+ const paths = parseFilePathsFromOutput(output);
31184
+ for (const filePath of paths) {
31185
+ const resolved = isAbsolute4(filePath) ? filePath : resolve5(cwd, filePath);
31186
+ this.markFileSeen(resolved);
31187
+ }
31188
+ }
31189
+ /**
31190
+ * Check if a file is safe to edit (seen-check only).
31191
+ * Mode-specific content verification happens in edit handlers.
31192
+ * @param {string} resolvedPath - Absolute path to the file
31193
+ * @returns {{ok: boolean, reason?: string, message?: string}}
31194
+ */
31195
+ checkBeforeEdit(resolvedPath) {
31196
+ if (!this._seenFiles.has(resolvedPath)) {
31197
+ return {
31198
+ ok: false,
31199
+ reason: "untracked",
31200
+ message: "This file has not been read yet in this session. Use extract or search to read the file first."
31201
+ };
31202
+ }
31203
+ return { ok: true };
31204
+ }
31205
+ /**
31206
+ * Mark a file as seen after a successful write (backward compat).
31207
+ * Also invalidates content records for the file since its content changed.
31208
+ * @param {string} resolvedPath - Absolute path to the file
31209
+ */
31210
+ async trackFileAfterWrite(resolvedPath) {
31211
+ this.markFileSeen(resolvedPath);
31212
+ this.invalidateFileRecords(resolvedPath);
31213
+ }
31214
+ /**
31215
+ * Update the stored hash for a symbol after a successful write.
31216
+ * Enables chained edits to the same symbol.
31217
+ * @param {string} resolvedPath - Absolute path to the file
31218
+ * @param {string} symbolName - Symbol name
31219
+ * @param {string} code - The symbol's new source code
31220
+ * @param {number} startLine - 1-indexed start line (new position)
31221
+ * @param {number} endLine - 1-indexed end line (new position)
31222
+ */
31223
+ trackSymbolAfterWrite(resolvedPath, symbolName, code, startLine, endLine) {
31224
+ this.trackSymbolContent(resolvedPath, symbolName, code, startLine, endLine, "edit");
31225
+ }
31226
+ /**
31227
+ * Remove all content records for a file.
31228
+ * Called after non-symbol edits (text/line mode) since those change content
31229
+ * without providing a symbol-level update.
31230
+ * @param {string} resolvedPath - Absolute path to the file
31231
+ */
31232
+ invalidateFileRecords(resolvedPath) {
31233
+ const prefix = resolvedPath + "#";
31234
+ for (const key of this._contentRecords.keys()) {
31235
+ if (key.startsWith(prefix)) {
31236
+ this._contentRecords.delete(key);
31237
+ }
31238
+ }
31239
+ if (this.debug) {
31240
+ console.error(`[FileTracker] Invalidated content records for ${resolvedPath}`);
31241
+ }
31242
+ }
31243
+ /**
31244
+ * Quick sync check if a file is being tracked (alias for isFileSeen).
31245
+ * @param {string} resolvedPath - Absolute path to the file
31246
+ * @returns {boolean}
31247
+ */
31248
+ isTracked(resolvedPath) {
31249
+ return this.isFileSeen(resolvedPath);
31250
+ }
31251
+ /**
31252
+ * Clear all tracking state.
31253
+ */
31254
+ clear() {
31255
+ this._seenFiles.clear();
31256
+ this._contentRecords.clear();
31257
+ }
31258
+ };
31259
+ }
31260
+ });
31261
+
30244
31262
  // src/agent/simpleTelemetry.js
30245
31263
  import { existsSync as existsSync3, mkdirSync, createWriteStream } from "fs";
30246
31264
  import { dirname as dirname2 } from "path";
@@ -30335,20 +31353,20 @@ var init_simpleTelemetry = __esm({
30335
31353
  }
30336
31354
  async flush() {
30337
31355
  if (this.stream) {
30338
- return new Promise((resolve8) => {
30339
- this.stream.once("drain", resolve8);
31356
+ return new Promise((resolve9) => {
31357
+ this.stream.once("drain", resolve9);
30340
31358
  if (!this.stream.writableNeedDrain) {
30341
- resolve8();
31359
+ resolve9();
30342
31360
  }
30343
31361
  });
30344
31362
  }
30345
31363
  }
30346
31364
  async shutdown() {
30347
31365
  if (this.stream) {
30348
- return new Promise((resolve8) => {
31366
+ return new Promise((resolve9) => {
30349
31367
  this.stream.end(() => {
30350
31368
  console.log(`[SimpleTelemetry] File stream closed: ${this.filePath}`);
30351
- resolve8();
31369
+ resolve9();
30352
31370
  });
30353
31371
  });
30354
31372
  }
@@ -30793,7 +31811,7 @@ var init_probeTool = __esm({
30793
31811
  "src/agent/probeTool.js"() {
30794
31812
  "use strict";
30795
31813
  init_index();
30796
- init_esm5();
31814
+ init_esm7();
30797
31815
  init_symlink_utils();
30798
31816
  toolCallEmitter = new EventEmitter2();
30799
31817
  activeToolExecutions = /* @__PURE__ */ new Map();
@@ -31531,6 +32549,7 @@ var init_index = __esm({
31531
32549
  init_executePlan();
31532
32550
  init_bash();
31533
32551
  init_edit();
32552
+ init_fileTracker();
31534
32553
  init_ProbeAgent();
31535
32554
  init_simpleTelemetry();
31536
32555
  init_probeTool();
@@ -31713,38 +32732,13 @@ function parseXmlToolCallWithThinking(xmlString, validTools) {
31713
32732
  const toolCall = parseXmlToolCall(cleanedXmlString, validTools);
31714
32733
  return toolCall ? { ...toolCall, thinkingContent } : null;
31715
32734
  }
31716
- var implementToolDefinition, listFilesToolDefinition, searchFilesToolDefinition, listSkillsToolDefinition, useSkillToolDefinition, readImageToolDefinition;
32735
+ var listFilesToolDefinition, searchFilesToolDefinition, listSkillsToolDefinition, useSkillToolDefinition, readImageToolDefinition;
31717
32736
  var init_tools2 = __esm({
31718
32737
  "src/agent/tools.js"() {
31719
32738
  "use strict";
31720
32739
  init_index();
31721
32740
  init_xmlParsingUtils();
31722
32741
  init_tasks();
31723
- implementToolDefinition = `
31724
- ## implement
31725
- Description: Implement a given task. Can modify files. Can be used ONLY if task explicitly stated that something requires modification or implementation.
31726
-
31727
- Parameters:
31728
- - task: (required) The task description. Should be as detailed as possible, ideally pointing to exact files which needs be modified or created.
31729
- - autoCommits: (optional) Whether to enable auto-commits in aider. Default is false.
31730
-
31731
- Usage Example:
31732
-
31733
- <examples>
31734
-
31735
- User: Can you implement a function to calculate Fibonacci numbers in main.js?
31736
- <implement>
31737
- <task>Implement a recursive function to calculate the nth Fibonacci number in main.js</task>
31738
- </implement>
31739
-
31740
- User: Can you implement a function to calculate Fibonacci numbers in main.js with auto-commits?
31741
- <implement>
31742
- <task>Implement a recursive function to calculate the nth Fibonacci number in main.js</task>
31743
- <autoCommits>true</autoCommits>
31744
- </implement>
31745
-
31746
- </examples>
31747
- `;
31748
32742
  listFilesToolDefinition = `
31749
32743
  ## listFiles
31750
32744
  Description: List files and directories in a specified location.
@@ -31868,7 +32862,7 @@ function createMockProvider() {
31868
32862
  provider: "mock",
31869
32863
  // Mock the doGenerate method used by Vercel AI SDK
31870
32864
  doGenerate: async ({ messages, tools: tools2 }) => {
31871
- await new Promise((resolve8) => setTimeout(resolve8, 10));
32865
+ await new Promise((resolve9) => setTimeout(resolve9, 10));
31872
32866
  return {
31873
32867
  text: "This is a mock response for testing",
31874
32868
  toolCalls: [],
@@ -37071,23 +38065,23 @@ var init_regexp_parser = __esm({
37071
38065
  return ASSERT_NEVER_REACH_HERE();
37072
38066
  }
37073
38067
  quantifier(isBacktracking = false) {
37074
- let range = void 0;
38068
+ let range2 = void 0;
37075
38069
  const begin = this.idx;
37076
38070
  switch (this.popChar()) {
37077
38071
  case "*":
37078
- range = {
38072
+ range2 = {
37079
38073
  atLeast: 0,
37080
38074
  atMost: Infinity
37081
38075
  };
37082
38076
  break;
37083
38077
  case "+":
37084
- range = {
38078
+ range2 = {
37085
38079
  atLeast: 1,
37086
38080
  atMost: Infinity
37087
38081
  };
37088
38082
  break;
37089
38083
  case "?":
37090
- range = {
38084
+ range2 = {
37091
38085
  atLeast: 0,
37092
38086
  atMost: 1
37093
38087
  };
@@ -37096,7 +38090,7 @@ var init_regexp_parser = __esm({
37096
38090
  const atLeast = this.integerIncludingZero();
37097
38091
  switch (this.popChar()) {
37098
38092
  case "}":
37099
- range = {
38093
+ range2 = {
37100
38094
  atLeast,
37101
38095
  atMost: atLeast
37102
38096
  };
@@ -37105,12 +38099,12 @@ var init_regexp_parser = __esm({
37105
38099
  let atMost;
37106
38100
  if (this.isDigit()) {
37107
38101
  atMost = this.integerIncludingZero();
37108
- range = {
38102
+ range2 = {
37109
38103
  atLeast,
37110
38104
  atMost
37111
38105
  };
37112
38106
  } else {
37113
- range = {
38107
+ range2 = {
37114
38108
  atLeast,
37115
38109
  atMost: Infinity
37116
38110
  };
@@ -37118,25 +38112,25 @@ var init_regexp_parser = __esm({
37118
38112
  this.consumeChar("}");
37119
38113
  break;
37120
38114
  }
37121
- if (isBacktracking === true && range === void 0) {
38115
+ if (isBacktracking === true && range2 === void 0) {
37122
38116
  return void 0;
37123
38117
  }
37124
- ASSERT_EXISTS(range);
38118
+ ASSERT_EXISTS(range2);
37125
38119
  break;
37126
38120
  }
37127
- if (isBacktracking === true && range === void 0) {
38121
+ if (isBacktracking === true && range2 === void 0) {
37128
38122
  return void 0;
37129
38123
  }
37130
- if (ASSERT_EXISTS(range)) {
38124
+ if (ASSERT_EXISTS(range2)) {
37131
38125
  if (this.peekChar(0) === "?") {
37132
38126
  this.consumeChar("?");
37133
- range.greedy = false;
38127
+ range2.greedy = false;
37134
38128
  } else {
37135
- range.greedy = true;
38129
+ range2.greedy = true;
37136
38130
  }
37137
- range.type = "Quantifier";
37138
- range.loc = this.loc(begin);
37139
- return range;
38131
+ range2.type = "Quantifier";
38132
+ range2.loc = this.loc(begin);
38133
+ return range2;
37140
38134
  }
37141
38135
  }
37142
38136
  atom() {
@@ -37838,18 +38832,18 @@ function firstCharOptimizedIndices(ast, result, ignoreCase) {
37838
38832
  if (typeof code === "number") {
37839
38833
  addOptimizedIdxToResult(code, result, ignoreCase);
37840
38834
  } else {
37841
- const range = code;
38835
+ const range2 = code;
37842
38836
  if (ignoreCase === true) {
37843
- for (let rangeCode = range.from; rangeCode <= range.to; rangeCode++) {
38837
+ for (let rangeCode = range2.from; rangeCode <= range2.to; rangeCode++) {
37844
38838
  addOptimizedIdxToResult(rangeCode, result, ignoreCase);
37845
38839
  }
37846
38840
  } else {
37847
- for (let rangeCode = range.from; rangeCode <= range.to && rangeCode < minOptimizationVal; rangeCode++) {
38841
+ for (let rangeCode = range2.from; rangeCode <= range2.to && rangeCode < minOptimizationVal; rangeCode++) {
37848
38842
  addOptimizedIdxToResult(rangeCode, result, ignoreCase);
37849
38843
  }
37850
- if (range.to >= minOptimizationVal) {
37851
- const minUnOptVal = range.from >= minOptimizationVal ? range.from : minOptimizationVal;
37852
- const maxUnOptVal = range.to;
38844
+ if (range2.to >= minOptimizationVal) {
38845
+ const minUnOptVal = range2.from >= minOptimizationVal ? range2.from : minOptimizationVal;
38846
+ const maxUnOptVal = range2.to;
37853
38847
  const minOptIdx = charCodeToOptimizedIndex(minUnOptVal);
37854
38848
  const maxOptIdx = charCodeToOptimizedIndex(maxUnOptVal);
37855
38849
  for (let currOptIdx = minOptIdx; currOptIdx <= maxOptIdx; currOptIdx++) {
@@ -37910,8 +38904,8 @@ function findCode(setNode, targetCharCodes) {
37910
38904
  if (typeof codeOrRange === "number") {
37911
38905
  return includes_default(targetCharCodes, codeOrRange);
37912
38906
  } else {
37913
- const range = codeOrRange;
37914
- return find_default(targetCharCodes, (targetCode) => range.from <= targetCode && targetCode <= range.to) !== void 0;
38907
+ const range2 = codeOrRange;
38908
+ return find_default(targetCharCodes, (targetCode) => range2.from <= targetCode && targetCode <= range2.to) !== void 0;
37915
38909
  }
37916
38910
  });
37917
38911
  }
@@ -55816,8 +56810,8 @@ var require_createRange = __commonJS({
55816
56810
  var require_range = __commonJS({
55817
56811
  "node_modules/lodash/range.js"(exports2, module2) {
55818
56812
  var createRange = require_createRange();
55819
- var range = createRange();
55820
- module2.exports = range;
56813
+ var range2 = createRange();
56814
+ module2.exports = range2;
55821
56815
  }
55822
56816
  });
55823
56817
 
@@ -65058,7 +66052,7 @@ var require_compile = __commonJS({
65058
66052
  const schOrFunc = root2.refs[ref2];
65059
66053
  if (schOrFunc)
65060
66054
  return schOrFunc;
65061
- let _sch = resolve8.call(this, root2, ref2);
66055
+ let _sch = resolve9.call(this, root2, ref2);
65062
66056
  if (_sch === void 0) {
65063
66057
  const schema = (_a = root2.localRefs) === null || _a === void 0 ? void 0 : _a[ref2];
65064
66058
  const { schemaId } = this.opts;
@@ -65085,7 +66079,7 @@ var require_compile = __commonJS({
65085
66079
  function sameSchemaEnv(s1, s2) {
65086
66080
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
65087
66081
  }
65088
- function resolve8(root2, ref2) {
66082
+ function resolve9(root2, ref2) {
65089
66083
  let sch;
65090
66084
  while (typeof (sch = this.refs[ref2]) == "string")
65091
66085
  ref2 = sch;
@@ -65660,7 +66654,7 @@ var require_fast_uri = __commonJS({
65660
66654
  }
65661
66655
  return uri;
65662
66656
  }
65663
- function resolve8(baseURI, relativeURI, options) {
66657
+ function resolve9(baseURI, relativeURI, options) {
65664
66658
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
65665
66659
  const resolved = resolveComponent(parse9(baseURI, schemelessOptions), parse9(relativeURI, schemelessOptions), schemelessOptions, true);
65666
66660
  schemelessOptions.skipEscape = true;
@@ -65887,7 +66881,7 @@ var require_fast_uri = __commonJS({
65887
66881
  var fastUri = {
65888
66882
  SCHEMES,
65889
66883
  normalize: normalize3,
65890
- resolve: resolve8,
66884
+ resolve: resolve9,
65891
66885
  resolveComponent,
65892
66886
  equal,
65893
66887
  serialize,
@@ -76149,7 +77143,7 @@ var require_compose_scalar = __commonJS({
76149
77143
  var resolveBlockScalar = require_resolve_block_scalar();
76150
77144
  var resolveFlowScalar = require_resolve_flow_scalar();
76151
77145
  function composeScalar(ctx, token, tagToken, onError) {
76152
- const { value, type, comment, range } = token.type === "block-scalar" ? resolveBlockScalar.resolveBlockScalar(ctx, token, onError) : resolveFlowScalar.resolveFlowScalar(token, ctx.options.strict, onError);
77146
+ const { value, type, comment, range: range2 } = token.type === "block-scalar" ? resolveBlockScalar.resolveBlockScalar(ctx, token, onError) : resolveFlowScalar.resolveFlowScalar(token, ctx.options.strict, onError);
76153
77147
  const tagName = tagToken ? ctx.directives.tagName(tagToken.source, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg)) : null;
76154
77148
  let tag;
76155
77149
  if (ctx.options.stringKeys && ctx.atKey) {
@@ -76169,7 +77163,7 @@ var require_compose_scalar = __commonJS({
76169
77163
  onError(tagToken ?? token, "TAG_RESOLVE_FAILED", msg);
76170
77164
  scalar = new Scalar.Scalar(value);
76171
77165
  }
76172
- scalar.range = range;
77166
+ scalar.range = range2;
76173
77167
  scalar.source = value;
76174
77168
  if (type)
76175
77169
  scalar.type = type;
@@ -78781,14 +79775,14 @@ var init_parser7 = __esm({
78781
79775
  // src/agent/skills/registry.js
78782
79776
  import { existsSync as existsSync5 } from "fs";
78783
79777
  import { readdir as readdir2, readFile as readFile2, realpath as realpath2, lstat as lstat2 } from "fs/promises";
78784
- import { resolve as resolve5, join as join3, isAbsolute as isAbsolute4, sep as sep4, relative } from "path";
79778
+ import { resolve as resolve6, join as join3, isAbsolute as isAbsolute5, sep as sep4, relative } from "path";
78785
79779
  function isPathInside(basePath, targetPath) {
78786
- const base2 = resolve5(basePath);
78787
- const target = resolve5(targetPath);
79780
+ const base2 = resolve6(basePath);
79781
+ const target = resolve6(targetPath);
78788
79782
  const rel = relative(base2, target);
78789
79783
  if (rel === "") return true;
78790
79784
  if (rel === ".." || rel.startsWith(`..${sep4}`)) return false;
78791
- if (isAbsolute4(rel)) return false;
79785
+ if (isAbsolute5(rel)) return false;
78792
79786
  return true;
78793
79787
  }
78794
79788
  function isSafeEntryName(name) {
@@ -78805,7 +79799,7 @@ var init_registry = __esm({
78805
79799
  SKILL_FILE_NAME = "SKILL.md";
78806
79800
  SkillRegistry = class {
78807
79801
  constructor({ repoRoot, skillDirs = DEFAULT_SKILL_DIRS, debug = false } = {}) {
78808
- this.repoRoot = repoRoot ? resolve5(repoRoot) : process.cwd();
79802
+ this.repoRoot = repoRoot ? resolve6(repoRoot) : process.cwd();
78809
79803
  this.repoRootReal = null;
78810
79804
  this.skillDirs = Array.isArray(skillDirs) && skillDirs.length > 0 ? skillDirs : DEFAULT_SKILL_DIRS;
78811
79805
  this.debug = debug;
@@ -78859,8 +79853,8 @@ var init_registry = __esm({
78859
79853
  }
78860
79854
  }
78861
79855
  async _resolveSkillDir(skillDir) {
78862
- const resolved = isAbsolute4(skillDir) ? resolve5(skillDir) : resolve5(this.repoRoot, skillDir);
78863
- const repoRoot = this.repoRootReal || resolve5(this.repoRoot);
79856
+ const resolved = isAbsolute5(skillDir) ? resolve6(skillDir) : resolve6(this.repoRoot, skillDir);
79857
+ const repoRoot = this.repoRootReal || resolve6(this.repoRoot);
78864
79858
  const resolvedReal = await this._resolveRealPath(resolved);
78865
79859
  if (!resolvedReal) return null;
78866
79860
  if (!isPathInside(repoRoot, resolvedReal)) {
@@ -79050,7 +80044,7 @@ function extractErrorInfo(error) {
79050
80044
  };
79051
80045
  }
79052
80046
  function sleep(ms) {
79053
- return new Promise((resolve8) => setTimeout(resolve8, ms));
80047
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
79054
80048
  }
79055
80049
  var DEFAULT_RETRYABLE_ERRORS, RetryManager;
79056
80050
  var init_RetryManager = __esm({
@@ -79982,7 +80976,7 @@ var init_built_in_server = __esm({
79982
80976
  }
79983
80977
  });
79984
80978
  this.registerHandlers();
79985
- return new Promise((resolve8, reject2) => {
80979
+ return new Promise((resolve9, reject2) => {
79986
80980
  this.httpServer.listen(this.port, this.host, async () => {
79987
80981
  const address = this.httpServer.address();
79988
80982
  this.port = address.port;
@@ -79992,7 +80986,7 @@ var init_built_in_server = __esm({
79992
80986
  console.log(`[MCP] Messages endpoint: http://${this.host}:${this.port}/messages`);
79993
80987
  }
79994
80988
  this.emit("ready", { host: this.host, port: this.port });
79995
- resolve8({ host: this.host, port: this.port });
80989
+ resolve9({ host: this.host, port: this.port });
79996
80990
  });
79997
80991
  this.httpServer.on("error", reject2);
79998
80992
  });
@@ -80211,7 +81205,7 @@ var init_built_in_server = __esm({
80211
81205
  * Parse request body as JSON
80212
81206
  */
80213
81207
  async parseRequestBody(req) {
80214
- return new Promise((resolve8, reject2) => {
81208
+ return new Promise((resolve9, reject2) => {
80215
81209
  let body = "";
80216
81210
  req.on("data", (chunk) => {
80217
81211
  body += chunk.toString();
@@ -80219,7 +81213,7 @@ var init_built_in_server = __esm({
80219
81213
  req.on("end", () => {
80220
81214
  try {
80221
81215
  const parsed = body ? JSON.parse(body) : null;
80222
- resolve8(parsed);
81216
+ resolve9(parsed);
80223
81217
  } catch (error) {
80224
81218
  reject2(error);
80225
81219
  }
@@ -80526,12 +81520,12 @@ data: ${JSON.stringify(data2)}
80526
81520
  }
80527
81521
  this.connections.clear();
80528
81522
  if (this.httpServer) {
80529
- return new Promise((resolve8) => {
81523
+ return new Promise((resolve9) => {
80530
81524
  this.httpServer.close(() => {
80531
81525
  if (this.debug) {
80532
81526
  console.log("[MCP] Built-in server stopped");
80533
81527
  }
80534
- resolve8();
81528
+ resolve9();
80535
81529
  });
80536
81530
  });
80537
81531
  }
@@ -80866,8 +81860,8 @@ ${opts.schema}`;
80866
81860
  break;
80867
81861
  }
80868
81862
  } else if (!processEnded) {
80869
- await new Promise((resolve8) => {
80870
- resolver = resolve8;
81863
+ await new Promise((resolve9) => {
81864
+ resolver = resolve9;
80871
81865
  });
80872
81866
  }
80873
81867
  }
@@ -81127,12 +82121,12 @@ async function createCodexEngine(options = {}) {
81127
82121
  }
81128
82122
  }
81129
82123
  if (message.id !== void 0 && pendingRequests.has(message.id)) {
81130
- const { resolve: resolve8, reject: reject2 } = pendingRequests.get(message.id);
82124
+ const { resolve: resolve9, reject: reject2 } = pendingRequests.get(message.id);
81131
82125
  pendingRequests.delete(message.id);
81132
82126
  if (message.error) {
81133
82127
  reject2(new Error(message.error.message || JSON.stringify(message.error)));
81134
82128
  } else {
81135
- resolve8(message.result);
82129
+ resolve9(message.result);
81136
82130
  }
81137
82131
  }
81138
82132
  if (message.method === "codex/event" && message.params) {
@@ -81153,7 +82147,7 @@ async function createCodexEngine(options = {}) {
81153
82147
  });
81154
82148
  }
81155
82149
  function sendRequest(method, params = {}) {
81156
- return new Promise((resolve8, reject2) => {
82150
+ return new Promise((resolve9, reject2) => {
81157
82151
  const id = ++requestId;
81158
82152
  const request = {
81159
82153
  jsonrpc: "2.0",
@@ -81161,7 +82155,7 @@ async function createCodexEngine(options = {}) {
81161
82155
  method,
81162
82156
  params
81163
82157
  };
81164
- pendingRequests.set(id, { resolve: resolve8, reject: reject2 });
82158
+ pendingRequests.set(id, { resolve: resolve9, reject: reject2 });
81165
82159
  setTimeout(() => {
81166
82160
  if (pendingRequests.has(id)) {
81167
82161
  pendingRequests.delete(id);
@@ -81224,7 +82218,7 @@ ${prompt}`;
81224
82218
  const reqId = requestId + 1;
81225
82219
  let fullResponse = "";
81226
82220
  let gotSessionId = false;
81227
- const eventPromise = new Promise((resolve8) => {
82221
+ const eventPromise = new Promise((resolve9) => {
81228
82222
  eventHandlers.set(reqId, (eventParams) => {
81229
82223
  const msg = eventParams.msg;
81230
82224
  if (msg.type === "session_configured" && msg.session_id && !gotSessionId) {
@@ -81244,7 +82238,7 @@ ${prompt}`;
81244
82238
  });
81245
82239
  setTimeout(() => {
81246
82240
  eventHandlers.delete(reqId);
81247
- resolve8();
82241
+ resolve9();
81248
82242
  }, 6e5);
81249
82243
  });
81250
82244
  const resultPromise = sendRequest("tools/call", {
@@ -81434,7 +82428,7 @@ import { randomUUID as randomUUID6 } from "crypto";
81434
82428
  import { EventEmitter as EventEmitter5 } from "events";
81435
82429
  import { existsSync as existsSync6 } from "fs";
81436
82430
  import { readFile as readFile3, stat, readdir as readdir3 } from "fs/promises";
81437
- import { resolve as resolve6, isAbsolute as isAbsolute5, dirname as dirname5, basename, normalize as normalize2, sep as sep5 } from "path";
82431
+ import { resolve as resolve7, isAbsolute as isAbsolute6, dirname as dirname5, basename, normalize as normalize2, sep as sep5 } from "path";
81438
82432
  function extractWrappedToolName(wrappedToolError) {
81439
82433
  if (!wrappedToolError || typeof wrappedToolError !== "string") {
81440
82434
  return "unknown";
@@ -81466,6 +82460,7 @@ var init_ProbeAgent = __esm({
81466
82460
  init_imageConfig();
81467
82461
  init_tools2();
81468
82462
  init_common();
82463
+ init_fileTracker();
81469
82464
  init_probeTool();
81470
82465
  init_mockProvider();
81471
82466
  init_index();
@@ -81507,7 +82502,7 @@ var init_ProbeAgent = __esm({
81507
82502
  * @param {string} [options.customPrompt] - Custom prompt to replace the default system message
81508
82503
  * @param {string} [options.systemPrompt] - Alias for customPrompt; takes precedence when both are provided
81509
82504
  * @param {string} [options.promptType] - Predefined prompt type (code-explorer, code-searcher, architect, code-review, support)
81510
- * @param {boolean} [options.allowEdit=false] - Allow the use of the 'implement' tool
82505
+ * @param {boolean} [options.allowEdit=false] - Allow the use of the 'edit' and 'create' tools
81511
82506
  * @param {boolean} [options.enableDelegate=false] - Enable the delegate tool for task distribution to subagents
81512
82507
  * @param {boolean} [options.enableExecutePlan=false] - Enable the execute_plan DSL orchestration tool
81513
82508
  * @param {string} [options.architectureFileName] - Architecture context filename to embed from repo root (defaults to AGENTS.md with CLAUDE.md fallback; ARCHITECTURE.md is always included when present)
@@ -81556,6 +82551,7 @@ var init_ProbeAgent = __esm({
81556
82551
  this.customPrompt = options.systemPrompt || options.customPrompt || null;
81557
82552
  this.promptType = options.promptType || "code-explorer";
81558
82553
  this.allowEdit = !!options.allowEdit;
82554
+ this.hashLines = options.hashLines !== void 0 ? !!options.hashLines : this.allowEdit;
81559
82555
  this.enableDelegate = !!options.enableDelegate;
81560
82556
  this.enableExecutePlan = !!options.enableExecutePlan;
81561
82557
  this.debug = options.debug || process.env.DEBUG === "1";
@@ -81617,7 +82613,8 @@ var init_ProbeAgent = __esm({
81617
82613
  if (this.debug) {
81618
82614
  console.log(`[DEBUG] Generated session ID for agent: ${this.sessionId}`);
81619
82615
  console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
81620
- console.log(`[DEBUG] Allow Edit (implement tool): ${this.allowEdit}`);
82616
+ console.log(`[DEBUG] Allow Edit: ${this.allowEdit}`);
82617
+ console.log(`[DEBUG] Hash Lines: ${this.hashLines}`);
81621
82618
  console.log(`[DEBUG] Search delegation enabled: ${this.searchDelegate}`);
81622
82619
  console.log(`[DEBUG] Workspace root: ${this.workspaceRoot}`);
81623
82620
  console.log(`[DEBUG] Working directory (cwd): ${this.cwd}`);
@@ -82002,9 +82999,12 @@ var init_ProbeAgent = __esm({
82002
82999
  cwd: this.cwd,
82003
83000
  workspaceRoot: this.workspaceRoot,
82004
83001
  allowedFolders: this.allowedFolders,
83002
+ // File state tracking for safe multi-edit workflows (only when editing is enabled)
83003
+ fileTracker: this.allowEdit ? new FileTracker({ debug: this.debug }) : null,
82005
83004
  outline: this.outline,
82006
83005
  searchDelegate: this.searchDelegate,
82007
83006
  allowEdit: this.allowEdit,
83007
+ hashLines: this.hashLines,
82008
83008
  enableDelegate: this.enableDelegate,
82009
83009
  enableExecutePlan: this.enableExecutePlan,
82010
83010
  enableBash: this.enableBash,
@@ -82789,7 +83789,7 @@ var init_ProbeAgent = __esm({
82789
83789
  let resolvedPath = imagePath;
82790
83790
  if (!imagePath.includes("/") && !imagePath.includes("\\")) {
82791
83791
  for (const dir of listFilesDirectories) {
82792
- const potentialPath = resolve6(dir, imagePath);
83792
+ const potentialPath = resolve7(dir, imagePath);
82793
83793
  const loaded = await this.loadImageIfValid(potentialPath);
82794
83794
  if (loaded) {
82795
83795
  if (this.debug) {
@@ -82859,8 +83859,8 @@ var init_ProbeAgent = __esm({
82859
83859
  const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
82860
83860
  let absolutePath;
82861
83861
  let isPathAllowed2 = false;
82862
- if (isAbsolute5(imagePath)) {
82863
- absolutePath = safeRealpath(resolve6(imagePath));
83862
+ if (isAbsolute6(imagePath)) {
83863
+ absolutePath = safeRealpath(resolve7(imagePath));
82864
83864
  isPathAllowed2 = allowedDirs.some((dir) => {
82865
83865
  const resolvedDir = safeRealpath(dir);
82866
83866
  return absolutePath === resolvedDir || absolutePath.startsWith(resolvedDir + sep5);
@@ -82868,7 +83868,7 @@ var init_ProbeAgent = __esm({
82868
83868
  } else {
82869
83869
  for (const dir of allowedDirs) {
82870
83870
  const resolvedDir = safeRealpath(dir);
82871
- const resolvedPath = safeRealpath(resolve6(dir, imagePath));
83871
+ const resolvedPath = safeRealpath(resolve7(dir, imagePath));
82872
83872
  if (resolvedPath === resolvedDir || resolvedPath.startsWith(resolvedDir + sep5)) {
82873
83873
  absolutePath = resolvedPath;
82874
83874
  isPathAllowed2 = true;
@@ -83058,7 +84058,7 @@ var init_ProbeAgent = __esm({
83058
84058
  let guidanceCandidates = [];
83059
84059
  if (hasConfiguredName) {
83060
84060
  const targetName = basename(configuredName);
83061
- if (configuredName !== targetName || configuredName.includes("/") || configuredName.includes("\\") || configuredName.includes("..") || isAbsolute5(configuredName)) {
84061
+ if (configuredName !== targetName || configuredName.includes("/") || configuredName.includes("\\") || configuredName.includes("..") || isAbsolute6(configuredName)) {
83062
84062
  console.warn(`[WARN] Invalid architectureFileName (must be a simple filename): ${configuredName}`);
83063
84063
  } else if (targetName) {
83064
84064
  const targetLower = targetName.toLowerCase();
@@ -83125,7 +84125,7 @@ var init_ProbeAgent = __esm({
83125
84125
  pushEntry(architectureMatch);
83126
84126
  const contexts = [];
83127
84127
  for (const entry of uniqueEntries) {
83128
- const filePath = resolve6(rootDirectory, entry.name);
84128
+ const filePath = resolve7(rootDirectory, entry.name);
83129
84129
  try {
83130
84130
  const content = await readFile3(filePath, "utf8");
83131
84131
  let kind = "other";
@@ -83190,10 +84190,10 @@ ${this.architectureContext.content}
83190
84190
  }
83191
84191
  _getSkillsRepoRoot() {
83192
84192
  if (this.workspaceRoot) {
83193
- return resolve6(this.workspaceRoot);
84193
+ return resolve7(this.workspaceRoot);
83194
84194
  }
83195
84195
  if (this.allowedFolders && this.allowedFolders.length > 0) {
83196
- return resolve6(this.allowedFolders[0]);
84196
+ return resolve7(this.allowedFolders[0]);
83197
84197
  }
83198
84198
  return process.cwd();
83199
84199
  }
@@ -83382,10 +84382,6 @@ Workspace: ${this.allowedFolders.join(", ")}`;
83382
84382
  }
83383
84383
  if (isToolAllowed("readImage")) {
83384
84384
  toolDefinitions += `${readImageToolDefinition}
83385
- `;
83386
- }
83387
- if (this.allowEdit && isToolAllowed("implement")) {
83388
- toolDefinitions += `${implementToolDefinition}
83389
84385
  `;
83390
84386
  }
83391
84387
  if (this.allowEdit && isToolAllowed("edit")) {
@@ -83467,7 +84463,7 @@ The configuration is loaded from src/config.js lines 15-25 which contains the da
83467
84463
  availableToolsList += "- query: Search code using structural AST patterns.\n";
83468
84464
  }
83469
84465
  if (isToolAllowed("extract")) {
83470
- availableToolsList += "- extract: Extract specific code blocks or lines from files.\n";
84466
+ availableToolsList += '- extract: Extract specific code blocks or lines from files. Use with symbol targets (e.g. "file.js#funcName") to get line numbers for line-targeted editing.\n';
83471
84467
  }
83472
84468
  if (isToolAllowed("listFiles")) {
83473
84469
  availableToolsList += "- listFiles: List files and directories in a specified location.\n";
@@ -83484,11 +84480,8 @@ The configuration is loaded from src/config.js lines 15-25 which contains the da
83484
84480
  if (isToolAllowed("readImage")) {
83485
84481
  availableToolsList += "- readImage: Read and load an image file for AI analysis.\n";
83486
84482
  }
83487
- if (this.allowEdit && isToolAllowed("implement")) {
83488
- availableToolsList += "- implement: Implement a feature or fix a bug using aider.\n";
83489
- }
83490
84483
  if (this.allowEdit && isToolAllowed("edit")) {
83491
- availableToolsList += "- edit: Edit files using exact string replacement.\n";
84484
+ availableToolsList += "- edit: Edit files using text replacement, AST-aware symbol operations, or line-targeted editing.\n";
83492
84485
  }
83493
84486
  if (this.allowEdit && isToolAllowed("create")) {
83494
84487
  availableToolsList += "- create: Create new files with specified content.\n";
@@ -83576,8 +84569,14 @@ Follow these instructions carefully:
83576
84569
  8. Once the task is fully completed, use the '<attempt_completion>' tool to provide the final result. This is the ONLY way to signal completion.
83577
84570
  9. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
83578
84571
  10. When modifying files, choose the appropriate tool:
83579
- - Use 'edit' for precise changes to existing files (requires exact string match)
83580
- - Use 'create' for new files or complete file rewrites` : ""}
84572
+ - Use 'edit' for all code modifications:
84573
+ * For small changes (a line or a few lines), use old_string + new_string \u2014 copy old_string verbatim from the file.
84574
+ * For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
84575
+ * For editing specific lines from search/extract output, use start_line (and optionally end_line) with the line numbers shown in the output.${this.hashLines ? ' Line references include content hashes (e.g. "42:ab") for integrity verification.' : ""}
84576
+ * For editing inside large functions: first use extract with the symbol target (e.g. "file.js#myFunction") to see the function with line numbers${this.hashLines ? " and hashes" : ""}, then use start_line/end_line to surgically edit specific lines within it.
84577
+ - Use 'create' for new files or complete file rewrites.
84578
+ - If an edit fails, read the error message \u2014 it tells you exactly how to fix the call and retry.
84579
+ - The system tracks which files you've seen via search/extract. If you try to edit a file you haven't read, or one that changed since you last read it, the edit will fail with instructions to re-read first. Always use extract before editing to ensure you have current file content.` : ""}
83581
84580
  </instructions>
83582
84581
  `;
83583
84582
  let systemMessage = "";
@@ -84077,8 +85076,11 @@ You are working with a workspace. Available paths: ${workspaceDesc}
84077
85076
  if (this.enableSkills && this.allowedTools.isEnabled("useSkill")) validTools.push("useSkill");
84078
85077
  if (this.allowedTools.isEnabled("readImage")) validTools.push("readImage");
84079
85078
  validTools.push("attempt_completion");
84080
- if (this.allowEdit && this.allowedTools.isEnabled("implement")) {
84081
- validTools.push("implement", "edit", "create");
85079
+ if (this.allowEdit && this.allowedTools.isEnabled("edit")) {
85080
+ validTools.push("edit");
85081
+ }
85082
+ if (this.allowEdit && this.allowedTools.isEnabled("create")) {
85083
+ validTools.push("create");
84082
85084
  }
84083
85085
  if (this.enableBash && this.allowedTools.isEnabled("bash")) {
84084
85086
  validTools.push("bash");
@@ -84300,7 +85302,7 @@ ${errorXml}
84300
85302
  try {
84301
85303
  let resolvedWorkingDirectory = this.workspaceRoot || this.cwd || this.allowedFolders && this.allowedFolders[0] || process.cwd();
84302
85304
  if (params.workingDirectory) {
84303
- const requestedDir = safeRealpath(isAbsolute5(params.workingDirectory) ? resolve6(params.workingDirectory) : resolve6(resolvedWorkingDirectory, params.workingDirectory));
85305
+ const requestedDir = safeRealpath(isAbsolute6(params.workingDirectory) ? resolve7(params.workingDirectory) : resolve7(resolvedWorkingDirectory, params.workingDirectory));
84304
85306
  const isWithinAllowed = !this.allowedFolders || this.allowedFolders.length === 0 || this.allowedFolders.some((folder) => {
84305
85307
  const resolvedFolder = safeRealpath(folder);
84306
85308
  return requestedDir === resolvedFolder || requestedDir.startsWith(resolvedFolder + sep5);
@@ -84371,6 +85373,8 @@ ${errorXml}
84371
85373
  // Inherit bash enablement
84372
85374
  bashConfig: this.bashConfig,
84373
85375
  // Inherit bash configuration
85376
+ allowEdit: this.allowEdit,
85377
+ // Inherit edit/create permission
84374
85378
  allowedTools: allowedToolsForDelegate,
84375
85379
  // Inherit allowed tools from parent
84376
85380
  debug: this.debug,
@@ -85527,7 +86531,7 @@ import {
85527
86531
  McpError
85528
86532
  } from "@modelcontextprotocol/sdk/types.js";
85529
86533
  import { readFileSync as readFileSync2, existsSync as existsSync7 } from "fs";
85530
- import { resolve as resolve7 } from "path";
86534
+ import { resolve as resolve8 } from "path";
85531
86535
 
85532
86536
  // src/agent/acp/server.js
85533
86537
  import { randomUUID as randomUUID7 } from "crypto";
@@ -85786,8 +86790,8 @@ var ACPConnection = class extends EventEmitter6 {
85786
86790
  if (params !== null) {
85787
86791
  message.params = params;
85788
86792
  }
85789
- return new Promise((resolve8, reject2) => {
85790
- this.pendingRequests.set(id, { resolve: resolve8, reject: reject2 });
86793
+ return new Promise((resolve9, reject2) => {
86794
+ this.pendingRequests.set(id, { resolve: resolve9, reject: reject2 });
85791
86795
  this.sendMessage(message);
85792
86796
  setTimeout(() => {
85793
86797
  if (this.pendingRequests.has(id)) {
@@ -86201,7 +87205,7 @@ dotenv3.config();
86201
87205
  function readInputContent(input) {
86202
87206
  if (!input) return null;
86203
87207
  try {
86204
- const resolvedPath = resolve7(input);
87208
+ const resolvedPath = resolve8(input);
86205
87209
  if (existsSync7(resolvedPath)) {
86206
87210
  return readFileSync2(resolvedPath, "utf-8").trim();
86207
87211
  }
@@ -86210,7 +87214,7 @@ function readInputContent(input) {
86210
87214
  return input;
86211
87215
  }
86212
87216
  function readFromStdin() {
86213
- return new Promise((resolve8, reject2) => {
87217
+ return new Promise((resolve9, reject2) => {
86214
87218
  let data2 = "";
86215
87219
  let hasReceivedData = false;
86216
87220
  let dataChunks = [];
@@ -86235,7 +87239,7 @@ function readFromStdin() {
86235
87239
  if (!trimmed && dataChunks.length === 0) {
86236
87240
  reject2(new Error("No input received from stdin"));
86237
87241
  } else {
86238
- resolve8(trimmed);
87242
+ resolve9(trimmed);
86239
87243
  }
86240
87244
  });
86241
87245
  process.stdin.on("error", (error) => {
@@ -86267,7 +87271,8 @@ function parseArgs() {
86267
87271
  schema: null,
86268
87272
  provider: null,
86269
87273
  model: null,
86270
- allowEdit: false,
87274
+ allowEdit: process.env.ALLOW_EDIT === "1" || false,
87275
+ hashLines: process.env.HASH_LINES !== void 0 ? process.env.HASH_LINES === "1" : void 0,
86271
87276
  enableDelegate: false,
86272
87277
  verbose: false,
86273
87278
  help: false,
@@ -86316,6 +87321,10 @@ function parseArgs() {
86316
87321
  config.verbose = true;
86317
87322
  } else if (arg === "--allow-edit") {
86318
87323
  config.allowEdit = true;
87324
+ } else if (arg === "--hash-lines") {
87325
+ config.hashLines = true;
87326
+ } else if (arg === "--no-hash-lines") {
87327
+ config.hashLines = false;
86319
87328
  } else if (arg === "--enable-delegate") {
86320
87329
  config.enableDelegate = true;
86321
87330
  } else if (arg === "--no-delegate") {
@@ -86412,12 +87421,14 @@ Options:
86412
87421
  --schema <schema|file> Output schema (JSON, XML, any format - text or file path)
86413
87422
  --provider <name> Force AI provider: anthropic, openai, google
86414
87423
  --model <name> Override model name
86415
- --allow-edit Enable code modification capabilities
87424
+ --allow-edit Enable code modification capabilities (edit + create tools)
87425
+ --hash-lines Annotate search/extract output with line hashes (default: on when --allow-edit)
87426
+ --no-hash-lines Disable line hash annotations even with --allow-edit
86416
87427
  --enable-delegate Enable delegate tool for task distribution to subagents
86417
87428
  --allowed-tools <tools> Filter available tools (comma-separated list)
86418
87429
  Use '*' or 'all' for all tools (default)
86419
87430
  Use 'none' or '' for no tools (raw AI mode)
86420
- Specific tools: search,query,extract,listFiles,searchFiles,listSkills,useSkill
87431
+ Specific tools: search,query,extract,edit,create,listFiles,searchFiles,listSkills,useSkill
86421
87432
  Supports exclusion: '*,!bash' (all except bash)
86422
87433
  --disable-tools Disable all tools (raw AI mode, no code analysis)
86423
87434
  Convenience flag equivalent to --allowed-tools none
@@ -86455,6 +87466,8 @@ Environment Variables:
86455
87466
  FORCE_PROVIDER Force specific provider (anthropic, openai, google)
86456
87467
  MODEL_NAME Override model name
86457
87468
  MAX_RESPONSE_TOKENS Maximum tokens for AI response
87469
+ ALLOW_EDIT Enable code modification (set to '1')
87470
+ HASH_LINES Annotate output with line hashes (set to '1'; default: on with ALLOW_EDIT)
86458
87471
  DEBUG Enable verbose mode (set to '1')
86459
87472
 
86460
87473
  Examples:
@@ -86471,6 +87484,8 @@ Examples:
86471
87484
  probe agent "Explain this code" --allowed-tools search,extract # Only search and extract
86472
87485
  probe agent "What is this project about?" --allowed-tools none # Raw AI mode (no tools)
86473
87486
  probe agent "Tell me about this project" --disable-tools # Raw AI mode (convenience flag)
87487
+ probe agent "Fix the off-by-one error" --allow-edit --path ./src # Enable code editing
87488
+ ALLOW_EDIT=1 probe agent "Refactor the login flow" # Edit via env var
86474
87489
  probe agent --mcp # Start MCP server mode
86475
87490
  probe agent --acp # Start ACP server mode
86476
87491