ek9_edit_file

Answers first: status (applied | ready | would-fail | conflicts), written, edits[{file, startLine, endLine, before, after}], wouldBeDiagnostics from the verifying compile and contentHash on an applied edit - an atomic batch of text edits to one file, or to several as one apply.

Category: Editor · Compare: Claude Code Edit tool / IntelliJ Find-and-Replace in File / sed -i

When to use it

You want a few exact text changes in an EXISTING file - one call with path + edits:[{mode:'find-replace', find:'old', replace:'new'}]. The line-replace and regex-replace modes mix freely in the same atomic batch, and files:[{path, edits}] lands several files as one apply. Cheaper than ek9_write_file for small surgical changes; untouched bytes are preserved exactly.

When not to use it

Details

find-replace is literal + case-sensitive (whitespace/indentation must match exactly - it is NOT re-indented), guarded by expectedOccurrences (integer, default 1, or 'all' — regex-replace shares the guard); line-replace takes 1-based inclusive startLine/endLine + replace (empty deletes) and a replacement written flush-left is re-indented to the replaced range's indentation (relative indentation kept; a later line shallower than the range is kept where written when only that compiles, said in corrected[]); a 'default operator X' line for a construct that already declares X is left out, and a find-replace whose find is gone because what it adds is already in the file is not applied - both said in corrected[] (nothing else to write is status:'unchanged'); regex-replace takes a Java pattern + replace with $1 backreferences. Every mode reads find/replace (replacement, replaceWith, newText, search, oldText and kind are accepted spellings). Edits apply sequentially (edits[1] sees edits[0]'s output) - a batch whose line-replace ranges were all numbered against the file as read, and fails in order but compiles applied bottom-up, is applied that way and says so in corrected[]; validation failures return status:'conflicts' (NO edits applied) with per-edit, mechanically repairable kinds — edit-position-out-of-range / find-not-found (carries a whitespace near-miss hint) / expected-occurrences-mismatch / cascading-edit-conflict / regex-invalid. Non-.ek9 targets are written without compile checking (non-ek9-file-not-compile-checked advisory); expectedContentHash gives whole-file lost-update protection.

Arguments

ArgumentTypeRequiredWhat it is
editsarray of objectOrdered edit operations, applied sequentially (edits[1] sees edits[0]'s output); one object per edit with mode + find/replace, or startLine/endLine + replace, or pattern + replace.
expectedContentHashstringThe contentHash ek9_read_file returned for the file's CURRENT content; the call is refused when the on-disk content differs (lost-update protection, same semantics as ek9_write_file).
filesarray of objectMULTI-FILE form: [{path, edits}] instead of path + edits - every file's edits are applied and the workspace compiled ONCE, so a change that only compiles when several files move together (a signature changed here, its callers there) lands as one atomic apply; any file failing rolls back all of them. Use it whenever a single-file edit would be rolled back for diagnostics in OTHER files.
pathstringFile path: workspace-relative (preferred) or absolute inside the workspace. Must exist.

It also takes the arguments every tool takes.

Recorded calls

Real calls, recorded against small projects and checked on every build - a changed answer fails it. 3 of the 46 recorded are shown.

Find equals replace unchanged

Request

{
  "edits" : [ {
    "find" : "stdout.println(report(100.0, 0.25))",
    "mode" : "find-replace",
    "replace" : "stdout.println(report(100.0, 0.25))"
  } ],
  "path" : "task.ek9"
}

Response

{
  "conflicts" : [ ],
  "corrected" : [ "edits[0]: its find and replace are the same text, so it changes nothing - nothing is written for it" ],
  "edits" : [ ],
  "meta" : {
    "serverMs" : "<timing>"
  },
  "risk" : {
    "crossesModuleBoundary" : false,
    "estimatedSeverity" : "trivial",
    "fileCount" : 0,
    "lineCount" : 0,
    "touchesPublicApi" : false,
    "touchesTests" : false
  },
  "status" : "unchanged",
  "summary" : "Nothing to apply: task.ek9 already has what the edits would write",
  "workspaceHash" : "<workspace-hash>",
  "wouldBeDiagnostics" : [ ],
  "written" : false
}

Edit file dry run ready

Request

{
  "dryRun" : true,
  "edits" : [ {
    "find" : "\"Goodbye\"",
    "mode" : "find-replace",
    "replace" : "\"Farewell\""
  } ],
  "path" : "main.ek9"
}

Response

{
  "conflicts" : [ ],
  "edits" : [ {
    "after" : "      <- result as String: \"Farewell\"",
    "before" : "      <- result as String: \"Goodbye\"",
    "endLine" : 10,
    "file" : "main.ek9",
    "startLine" : 10,
    "type" : "replace"
  } ],
  "meta" : {
    "serverMs" : "<timing>"
  },
  "risk" : {
    "crossesModuleBoundary" : false,
    "estimatedSeverity" : "trivial",
    "fileCount" : 1,
    "lineCount" : 1,
    "touchesPublicApi" : false,
    "touchesTests" : false
  },
  "status" : "ready",
  "summary" : "Would apply 1 edit (1 line affected)",
  "workspaceHash" : "<workspace-hash>",
...

Cut short here: 3 more lines. The whole recorded call.

Default operator already declared left out

Request

{
  "edits" : [ {
    "find" : "      Basket()\n        this.items :=? List() of String\n\n      add()",
    "mode" : "find-replace",
    "replace" : "      Basket()\n        this.items :=? List() of String\n\n      default operator ?\n\n      add()"
  } ],
  "path" : "task.ek9"
}

Response

{
  "conflicts" : [ ],
  "corrected" : [ "edits[0]: 'default operator ?' left out - Basket already has 'default operator ?' (line 18); it was all the edit added, so nothing is written for it" ],
  "edits" : [ ],
  "meta" : {
    "serverMs" : "<timing>"
  },
  "risk" : {
    "crossesModuleBoundary" : false,
    "estimatedSeverity" : "trivial",
    "fileCount" : 0,
    "lineCount" : 0,
    "touchesPublicApi" : false,
    "touchesTests" : false
  },
  "status" : "unchanged",
  "summary" : "Nothing to apply: task.ek9 already has what the edits would write",
  "workspaceHash" : "<workspace-hash>",
  "wouldBeDiagnostics" : [ ],
  "written" : false
}