/** * Unique search/replace (Grok-shaped). No Bare imports. */ function countOccurrences(hay, needle) { if (!needle) return 0; const h = String(hay || ''); const n = String(needle); let count = 0; let i = 0; while (i < h.length) { const at = h.indexOf(n, i); if (at < 0) break; count += 1; i = at + Math.max(1, n.length); } return count; } function applySearchReplace(cur, oldString, newString, replaceAll) { const old = oldString == null ? '' : String(oldString); const neu = newString == null ? '' : String(newString); const text = cur == null ? '' : String(cur); if (!old) { if (text.trim()) { throw new Error( 'old_string is empty but the file is not empty; refuse overwrite. Use write_file to replace the whole file, or pass a unique old_string.' ); } return { text: neu, created: true, replacements: 1 }; } const n = countOccurrences(text, old); if (n === 0) throw new Error('old_string not found'); if (n > 1 && !replaceAll) { throw new Error( 'old_string matched ' + n + ' times; add surrounding lines to make it unique, or set replace_all to true.' ); } const next = replaceAll ? text.split(old).join(neu) : text.replace(old, neu); return { text: next, created: false, replacements: replaceAll ? n : 1 }; } function contextSnippet(text, needle, radius) { const lines = String(text || '').split('\n'); const r = radius == null ? 3 : radius; const want = String(needle || ''); let idx = -1; if (want) { for (let i = 0; i < lines.length; i++) { if (lines[i].indexOf(want) >= 0) { idx = i; break; } } } if (idx < 0) idx = 0; const start = Math.max(0, idx - r); const end = Math.min(lines.length, idx + r + 1); return lines .slice(start, end) .map((l, i) => String(start + i + 1).padStart(6) + '| ' + l) .join('\n'); } module.exports = { countOccurrences, applySearchReplace, contextSnippet };