/*
* std/edit - pure helpers for applying and validating agent-authored text patches.
*/
fn __edit_sha256(text) {
return "sha256:" + sha256(text ?? "")
}
fn __edit_collapse_ws(text) {
return trim(regex_replace("\\s+", " ", text ?? ""))
}
fn __edit_normalize_line(line) {
return __edit_collapse_ws(line)
}
fn __edit_structural_line(line) {
let compact = __edit_collapse_ws(line)
return regex_replace("\\s+", "", compact)
}
fn __edit_signature(text, structural) {
var lines = []
for line in split(text ?? "", "\n") {
let normalized = if structural {
__edit_structural_line(line)
} else {
__edit_normalize_line(line)
}
if structural && normalized == "" {
continue
}
lines = lines + [normalized]
}
return join(lines, "\n")
}
fn __edit_nonblank_count(lines) {
var count = 0
for line in lines {
if __edit_structural_line(line) != "" {
count = count + 1
}
}
return count
}
fn __edit_start_line(prefix) {
if prefix == "" {
return 0
}
return len(split(prefix, "\n")) - 1
}
fn __edit_region(start_line, end_line_exclusive, old_text, new_text, match_kind) {
let end_line = if end_line_exclusive > start_line {
end_line_exclusive - 1
} else {
start_line
}
return {
start_line: start_line,
end_line: end_line,
end_line_exclusive: end_line_exclusive,
old_line_count: max(end_line_exclusive - start_line, 0),
new_line_count: len(split(new_text ?? "", "\n")),
old_sha256: __edit_sha256(old_text),
new_sha256: __edit_sha256(new_text),
match_kind: match_kind,
}
}
fn __edit_context(prefix, needle, suffix) {
let before_lines = split(prefix, "\n")
let after_lines = split(suffix, "\n")
let before = if len(before_lines) > 2 {
join(before_lines[-2:], "\n")
} else {
prefix
}
let after = if len(after_lines) > 2 {
join(after_lines[:2], "\n")
} else {
suffix
}
return trim(before + needle + after)
}
fn __edit_candidate_contexts(parts, old_text, max_contexts) {
var contexts = []
var prefix = ""
var idx = 0
let limit = max_contexts ?? 3
let needle_lines = max(len(split(old_text ?? "", "\n")), 1)
while idx < len(parts) - 1 && len(contexts) < limit {
prefix = prefix + parts[idx]
let start_line = __edit_start_line(prefix)
contexts = contexts
+ [
{
start_line: start_line,
end_line: start_line + needle_lines - 1,
snippet: __edit_context(prefix, old_text, parts[idx + 1]),
},
]
prefix = prefix + old_text
idx = idx + 1
}
return contexts
}
fn __edit_line_candidate_contexts(text, candidates, max_contexts) {
let lines = split(text ?? "", "\n")
var contexts = []
let limit = max_contexts ?? 3
var idx = 0
while idx < len(candidates) && len(contexts) < limit {
let candidate = candidates[idx]
let start_line = candidate.start_line
let end_line_exclusive = candidate.end_line_exclusive
let prefix_start = max(start_line - 2, 0)
let suffix_end = min(end_line_exclusive + 2, len(lines))
let snippet = trim(join(lines[prefix_start:suffix_end], "\n"))
contexts = contexts
+ [{start_line: start_line, end_line: end_line_exclusive - 1, snippet: snippet}]
idx = idx + 1
}
return contexts
}
fn __edit_error(code, message, text, old_text, new_text, fields = nil) {
let base = {
ok: false,
changed: false,
error_code: code,
message: message,
patched: text,
before_sha256: __edit_sha256(text),
after_sha256: __edit_sha256(text),
old_sha256: __edit_sha256(old_text),
new_sha256: __edit_sha256(new_text),
errors: [{code: code, message: message}],
warnings: [],
changed_regions: [],
provenance: {module: "std/edit"},
}
return base.merge(fields ?? {})
}
fn __edit_lazy_placeholder_patterns() {
return [
"(?im)^\\s*//\\s*\\.\\.\\.?\\s*(rest|remaining|existing|implementation|code|omit)",
"(?im)^\\s*//\\s*TODO:?\\s*(implement|fill|add|complete)",
"(?im)^\\s*#\\s*\\.\\.\\.?\\s*(rest|remaining|existing|implementation|code)",
"(?im)^\\s*/\\*\\s*\\.\\.\\.?\\s*\\*/\\s*$",
"(?im)^\\s*//\\s*\\.\\.\\.\\s*$",
"(?im)^\\s*#\\s*\\.\\.\\.\\s*$",
"(?im)^\\s*pass\\s*#\\s*\\.\\.\\.",
]
}
fn __edit_lazy_placeholder_phrases() {
return ["unchanged", "omitted for brevity", "same as before", "rest of the file", "remaining code"]
}
fn __edit_lazy_match_count(text) {
let body = text ?? ""
if body == "" {
return 0
}
var total = 0
for line in split(body, "\n") {
let t = lowercase(trim(line))
if t == "..." || t == "…" {
total = total + 1
}
}
for pattern in __edit_lazy_placeholder_patterns() {
let matches = regex_match(pattern, body) ?? []
total = total + len(matches)
}
let lowered = lowercase(body)
for phrase in __edit_lazy_placeholder_phrases() {
if contains(lowered, phrase) {
total = total + 1
}
}
return total
}
fn __edit_has_lazy_placeholder(new_text) {
return __edit_lazy_match_count(new_text) > 0
}
fn __edit_guardrails(old_text, new_text, options) {
let opts = options ?? {}
if old_text == "" && !(opts?.allow_empty_old_text ?? false) {
return {code: "empty_old_text", message: "old_text must not be empty"}
}
if old_text == new_text && !(opts?.allow_noop ?? false) {
return {code: "no_op", message: "patch does not change the selected text"}
}
if __edit_collapse_ws(old_text) == __edit_collapse_ws(new_text)
&& old_text != new_text
&& !(opts?.allow_whitespace_only ?? false) {
return {code: "whitespace_only", message: "patch changes only whitespace"}
}
if __edit_has_lazy_placeholder(new_text) && !(opts?.allow_lazy_placeholders ?? false) {
return {code: "lazy_placeholder", message: "patch contains an omission or unchanged-content placeholder"}
}
let old_len = len(old_text)
let new_len = len(new_text)
let growth = new_len - old_len
let max_growth_bytes = opts?.max_growth_bytes ?? 20000
if max_growth_bytes != nil && growth > max_growth_bytes {
return {code: "excessive_growth", message: "patch grows selected text by more than max_growth_bytes"}
}
let max_growth_ratio = opts?.max_growth_ratio ?? 8
if max_growth_ratio != nil && growth > 1024 && new_len > old_len * max_growth_ratio {
return {code: "excessive_growth", message: "patch grows selected text by more than max_growth_ratio"}
}
return nil
}
fn __edit_success(
text,
patched,
old_text,
new_text,
match_kind,
start_line,
end_line_exclusive,
options,
) {
let before_hash = __edit_sha256(text)
let after_hash = __edit_sha256(patched)
let expected_region = __edit_region(start_line, end_line_exclusive, old_text, new_text, match_kind)
return {
ok: true,
changed: text != patched,
patched: patched,
match_kind: match_kind,
start_line: expected_region.start_line,
end_line: expected_region.end_line,
end_line_exclusive: expected_region.end_line_exclusive,
expected_region: expected_region,
changed_regions: edit_changed_regions(text, patched),
before_sha256: before_hash,
after_sha256: after_hash,
old_sha256: __edit_sha256(old_text),
new_sha256: __edit_sha256(new_text),
errors: [],
warnings: [],
provenance: {
module: "std/edit",
helper: "edit_apply_old_new_patch",
match_kind: match_kind,
before_sha256: before_hash,
after_sha256: after_hash,
caller: options?.provenance,
},
}
}
fn __edit_splice_raw(text, start_line, end_line_exclusive, new_text) {
let lines = split(text ?? "", "\n")
var out = []
if start_line > 0 {
out = out + lines[:start_line]
}
if new_text ?? "" != "" {
out = out + split(new_text ?? "", "\n")
}
if end_line_exclusive < len(lines) {
out = out + lines[end_line_exclusive:]
}
return join(out, "\n")
}
fn __edit_has_distinctive_token(line, min_chars) {
let chars = min_chars ?? 4
if chars <= 0 {
return true
}
let tokens = regex_match("[A-Za-z0-9_]+", line ?? "") ?? []
for token in tokens {
if len(token) >= chars {
return true
}
}
return false
}
fn __edit_structural_anchors_ok(old_text, options) {
let opts = options ?? {}
let min_lines = opts?.structural_min_nonblank_lines ?? 3
let anchor_chars = opts?.structural_anchor_chars ?? 4
let anchor_mode = lowercase(opts?.structural_require_anchored_lines ?? "both")
let needle_lines = split(old_text ?? "", "\n")
var nonblank_lines = []
for line in needle_lines {
if __edit_structural_line(line) != "" {
nonblank_lines = nonblank_lines + [line]
}
}
if len(nonblank_lines) < min_lines {
return false
}
if anchor_mode == "none" || anchor_chars <= 0 {
return true
}
let first = nonblank_lines[0] ?? ""
let last = nonblank_lines[len(nonblank_lines) - 1] ?? ""
let first_ok = __edit_has_distinctive_token(first, anchor_chars)
let last_ok = __edit_has_distinctive_token(last, anchor_chars)
if anchor_mode == "either" {
return first_ok || last_ok
}
return first_ok && last_ok
}
fn __edit_line_candidates(text, old_text, structural, options) {
let lines = split(text ?? "", "\n")
var candidates = []
let target = __edit_signature(old_text, structural)
if target == "" {
return candidates
}
if !structural {
let width = len(split(old_text ?? "", "\n"))
var start = 0
while start + width <= len(lines) {
let segment = join(lines[start:start + width], "\n")
if __edit_signature(segment, false) == target {
candidates = candidates + [{start_line: start, end_line_exclusive: start + width, text: segment}]
}
start = start + 1
}
return candidates
}
if !__edit_structural_anchors_ok(old_text, options) {
return candidates
}
let target_count = __edit_nonblank_count(split(old_text ?? "", "\n"))
var start = 0
while start < len(lines) {
if __edit_structural_line(lines[start]) == "" {
start = start + 1
continue
}
var end = start
var nonblank = 0
while end < len(lines) && nonblank < target_count {
if __edit_structural_line(lines[end]) != "" {
nonblank = nonblank + 1
}
end = end + 1
}
if nonblank == target_count {
while end < len(lines) && __edit_structural_line(lines[end]) == "" {
end = end + 1
}
let segment = join(lines[start:end], "\n")
if __edit_signature(segment, true) == target {
candidates = candidates + [{start_line: start, end_line_exclusive: end, text: segment}]
}
}
start = start + 1
}
return candidates
}
fn __edit_apply_line_candidate(text, old_text, new_text, match_kind, candidates, options) {
if len(candidates) == 0 {
return nil
}
let opts = options ?? {}
if len(candidates) > 1 {
return __edit_error(
"ambiguous_match",
"old_text matched multiple normalized regions",
text,
old_text,
new_text,
{
candidate_count: len(candidates),
match_kind: match_kind,
candidate_contexts: __edit_line_candidate_contexts(text, candidates, opts?.max_candidate_contexts),
},
)
}
let candidate = candidates[0]
let patched = __edit_splice_raw(text, candidate.start_line, candidate.end_line_exclusive, new_text)
let result = __edit_success(
text,
patched,
candidate.text,
new_text,
match_kind,
candidate.start_line,
candidate.end_line_exclusive,
opts,
)
if match_kind == "structural" || match_kind == "line" {
return result.merge({whitespace_explanation: edit_explain_whitespace_difference(old_text, candidate.text)})
}
return result
}
/**
* Apply one old/new anchored patch to text.
*
* Returns `{ok, changed, patched, match_kind, start_line, end_line,
* before_sha256, after_sha256, changed_regions, errors, warnings, provenance}`.
* Matching is exact first by default, then whitespace-normalized line matching,
* then structural matching that ignores blank lines and whitespace.
*
* Structural matches are conservative by default: the needle must contain at
* least `structural_min_nonblank_lines` (3) non-blank lines, and both the
* first and last non-blank anchor lines must carry a distinctive token of at
* least `structural_anchor_chars` (4) alphanumeric characters. Relax with
* `structural_require_anchored_lines: "either"` or `"none"` only when callers
* have a host-side validator that can re-check the result. Pass
* `strip_line_numbers: true` to pre-strip ` N | ` line-number prefixes from
* `old_text` when the model pasted them straight from a numbered file read.
*/
pub fn edit_apply_old_new_patch(text, old_text, new_text, options = nil) {
let source = text ?? ""
let raw_old = old_text ?? ""
let new = new_text ?? ""
let opts = options ?? {}
let old = if opts?.strip_line_numbers ?? false {
edit_strip_line_number_prefixes(raw_old)
} else {
raw_old
}
let guardrail = __edit_guardrails(old, new, opts)
if guardrail != nil {
return __edit_error(guardrail.code, guardrail.message, source, old, new)
}
let mode = opts?.match ?? "auto"
if mode != "line" && mode != "structural" {
let parts = split(source, old)
if len(parts) == 2 {
let start_line = __edit_start_line(parts[0])
let end_line_exclusive = start_line + len(split(old, "\n"))
return __edit_success(
source,
parts[0] + new + parts[1],
old,
new,
"exact",
start_line,
end_line_exclusive,
opts,
)
}
if len(parts) > 2 {
return __edit_error(
"ambiguous_match",
"old_text matched multiple exact regions",
source,
old,
new,
{
candidate_count: len(parts) - 1,
candidate_contexts: __edit_candidate_contexts(parts, old, opts?.max_candidate_contexts ?? 3),
},
)
}
if mode == "exact" {
return __edit_error("no_match", "old_text did not match exactly", source, old, new)
}
}
if mode == "line" || mode == "auto" {
let line_result = __edit_apply_line_candidate(
source,
old,
new,
"line",
__edit_line_candidates(source, old, false, opts),
opts,
)
if line_result != nil {
return line_result
}
if mode == "line" {
return __edit_error("no_match", "old_text did not match any normalized line region", source, old, new)
}
}
if mode == "structural" || mode == "auto" {
let structural_result = __edit_apply_line_candidate(
source,
old,
new,
"structural",
__edit_line_candidates(source, old, true, opts),
opts,
)
if structural_result != nil {
return structural_result
}
}
return __edit_error("no_match", "old_text did not match any safe patch region", source, old, new)
}
/** Splice a half-open 0-based line range and return patch metadata. */
pub fn edit_splice_lines(text, start_line, end_line_exclusive, new_text, options = nil) {
let source = text ?? ""
let lines = split(source, "\n")
let start = start_line ?? 0
let end = end_line_exclusive ?? start
if start < 0 || end < start || end > len(lines) {
return __edit_error(
"invalid_line_range",
"line range must be 0-based, half-open, and inside the text",
source,
"",
new_text ?? "",
)
}
let old = join(lines[start:end], "\n")
let guardrail = __edit_guardrails(old, new_text ?? "", (options ?? {}).merge({allow_empty_old_text: true}))
if guardrail != nil {
return __edit_error(guardrail.code, guardrail.message, source, old, new_text ?? "")
}
let patched = __edit_splice_raw(source, start, end, new_text ?? "")
return __edit_success(source, patched, old, new_text ?? "", "line_splice", start, end, options ?? {})
}
fn __edit_find_resync(before_lines, after_lines, before_index, after_index) {
let window = 80
var distance = 1
while distance <= window {
var before_delta = 0
while before_delta <= distance {
let after_delta = distance - before_delta
let next_before = before_index + before_delta
let next_after = after_index + after_delta
if next_before < len(before_lines)
&& next_after < len(after_lines)
&& before_lines[next_before] == after_lines[next_after] {
return {before_index: next_before, after_index: next_after}
}
before_delta = before_delta + 1
}
distance = distance + 1
}
return {before_index: len(before_lines), after_index: len(after_lines)}
}
fn __edit_hunk(before_start, after_start, before_end, after_end, next_before, next_after) {
return {
start_line: min(before_start, after_start),
end_line: max(before_end, after_end),
end_line_exclusive: max(next_before, next_after),
before_start_line: before_start,
after_start_line: after_start,
before_end_line: before_end,
after_end_line: after_end,
before_line_count: max(before_end - before_start + 1, 0),
after_line_count: max(after_end - after_start + 1, 0),
}
}
/** Return deterministic line-level changed-region metadata. */
pub fn edit_changed_regions(before, after) {
let before_lines = split(before ?? "", "\n")
let after_lines = split(after ?? "", "\n")
var regions = []
var before_index = 0
var after_index = 0
while before_index < len(before_lines) || after_index < len(after_lines) {
if before_index < len(before_lines)
&& after_index < len(after_lines)
&& before_lines[before_index] == after_lines[after_index] {
before_index = before_index + 1
after_index = after_index + 1
continue
}
let before_start = before_index
let after_start = after_index
let resync = __edit_find_resync(before_lines, after_lines, before_index, after_index)
let next_before = resync.before_index
let next_after = resync.after_index
regions = regions
+ [__edit_hunk(before_start, after_start, next_before - 1, next_after - 1, next_before, next_after)]
before_index = next_before
after_index = next_after
}
return regions
}
fn __edit_expected_end(region) {
if region?.end_line != nil {
return region.end_line
}
if region?.end_line_exclusive != nil {
return region.end_line_exclusive - 1
}
return region?.start_line ?? 0
}
fn __edit_region_within(actual, expected) {
let expected_start = expected?.start_line ?? 0
let expected_end = __edit_expected_end(expected)
return actual.start_line >= expected_start && actual.end_line <= expected_end
}
/**
* Strip ` N | ` line-number prefixes from raw text when at least 60% of
* non-empty lines carry that shape. Useful preprocessing for old_text strings
* that a model pasted verbatim from a numbered file read; otherwise the text
* never matches because each anchor line has an extra `42 | ` prefix.
*
* The 60% threshold is intentional: a file that legitimately contains one or
* two `N | …` lines (e.g. a docstring example) is left alone.
*/
pub fn edit_strip_line_number_prefixes(text) {
let raw = text ?? ""
if raw == "" {
return raw
}
let lines = split(raw, "\n")
var non_empty_total = 0
var matching = 0
for line in lines {
if trim(line) == "" {
continue
}
non_empty_total = non_empty_total + 1
if regex_match("^\\s*\\d+\\s*\\| ", line) {
matching = matching + 1
}
}
if non_empty_total == 0 {
return raw
}
if matching * 100 / non_empty_total <= 60 {
return raw
}
var stripped = []
for line in lines {
stripped = stripped + [regex_replace("^\\s*\\d+\\s*\\| ", "", line)]
}
return join(stripped, "\n")
}
fn __edit_count_tab_indent(lines) {
var total = 0
for line in lines {
if (line ?? "").starts_with("\t") {
total = total + 1
}
}
return total
}
fn __edit_count_space_indent(lines) {
var total = 0
for line in lines {
let raw = line ?? ""
if raw == "" || raw.starts_with("\t") {
continue
}
if raw.starts_with(" ") {
total = total + 1
}
}
return total
}
fn __edit_min_indent(lines) {
var smallest = -1
for line in lines {
let raw = line ?? ""
if trim(raw) == "" {
continue
}
let leading = len(raw) - len(regex_replace("^[ \\t]+", "", raw))
if smallest == -1 || leading < smallest {
smallest = leading
}
}
return smallest
}
fn __edit_count_blank_lines(lines) {
var total = 0
for line in lines {
if trim(line ?? "") == "" {
total = total + 1
}
}
return total
}
/**
* Diagnose the dominant whitespace discrepancy between an old_text needle and
* the actual matched span in the source. Hosts log this on fuzzy/structural
* matches so the model can learn from each near-miss.
*
* Returns a short human-readable sentence such as
* `"your old_string used tabs but the file uses spaces"` or
* `"your old_string had 1 extra blank line(s) that the file does not"`.
* Returns `""` when needle and matched are identical or no dominant cause is
* detectable.
*/
pub fn edit_explain_whitespace_difference(needle, matched) {
let needle_text = needle ?? ""
let matched_text = matched ?? ""
if needle_text == matched_text {
return ""
}
let needle_lines = split(needle_text, "\n")
let matched_lines = split(matched_text, "\n")
let needle_tabs = __edit_count_tab_indent(needle_lines)
let needle_spaces = __edit_count_space_indent(needle_lines)
let matched_tabs = __edit_count_tab_indent(matched_lines)
let matched_spaces = __edit_count_space_indent(matched_lines)
if needle_tabs > 0 && matched_tabs == 0 && matched_spaces > 0 {
return "your old_string used tabs but the file uses spaces"
}
if needle_spaces > 0 && matched_spaces == 0 && matched_tabs > 0 {
return "your old_string used spaces but the file uses tabs"
}
let needle_min = __edit_min_indent(needle_lines)
let matched_min = __edit_min_indent(matched_lines)
if needle_min != matched_min && needle_min >= 0 && matched_min >= 0 {
return "your old_string's base indent was ${needle_min} but the file's matched block starts at indent ${matched_min}"
}
let needle_blanks = __edit_count_blank_lines(needle_lines)
let matched_blanks = __edit_count_blank_lines(matched_lines)
if needle_blanks != matched_blanks {
let delta = matched_blanks - needle_blanks
if delta > 0 {
return "your old_string was missing ${delta} blank line(s) that the file has"
}
return "your old_string had ${0 - delta} extra blank line(s) that the file does not"
}
return "minor whitespace formatting (exact cause not pinpointed)"
}
/**
* Detect whether a whole-file edit looks like a lazy truncation: a file with
* at least `min_old_lines` (10) lines shrunk below `min_keep_pct` (35%) of
* its original line count *and* the new content still contains at least one
* lazy placeholder.
*
* Returns `{ok, lazy, error_code, message, old_lines, new_lines, lazy_hits,
* provenance}`. `ok` is true when the edit is sound; false when the
* shrinkage + placeholder shape is present. Distinct from
* `edit_apply_old_new_patch`'s `lazy_placeholder` guardrail, which fires on
* any placeholder in the patch region — this one is for whole-file rewrites
* where placeholders by themselves are not a signal.
*/
pub fn edit_check_lazy_truncation(old_content, new_content, options = nil) {
let opts = options ?? {}
let min_old_lines = opts?.min_old_lines ?? 10
let min_keep_pct = opts?.min_keep_pct ?? 35
let old_text = old_content ?? ""
let new_text = new_content ?? ""
let old_lines = len(split(old_text, "\n"))
let new_lines = len(split(new_text, "\n"))
let lazy_hits = __edit_lazy_match_count(new_text)
let provenance = {
module: "std/edit",
helper: "edit_check_lazy_truncation",
before_sha256: __edit_sha256(old_text),
after_sha256: __edit_sha256(new_text),
caller: opts?.provenance,
}
let neutral = {
ok: true,
lazy: false,
error_code: nil,
message: "",
old_lines: old_lines,
new_lines: new_lines,
lazy_hits: lazy_hits,
provenance: provenance,
}
if old_lines < min_old_lines {
return neutral
}
let threshold = old_lines * min_keep_pct / 100
if new_lines > threshold {
return neutral
}
if lazy_hits == 0 {
return neutral
}
return {
ok: false,
lazy: true,
error_code: "lazy_truncation",
message: "lazy edit: file was truncated from ${old_lines} to ${new_lines} lines with placeholder comments — provide the complete implementation",
old_lines: old_lines,
new_lines: new_lines,
lazy_hits: lazy_hits,
provenance: provenance,
}
}
/**
* Validate that before/after text differs only inside expected line regions.
* Expected regions use inclusive `end_line` or half-open `end_line_exclusive`.
*/
pub fn edit_validate_changed_regions(before, after, expected_regions, options = nil) {
let actual = edit_changed_regions(before ?? "", after ?? "")
let expected = expected_regions ?? []
var errors = []
for region in actual {
var matched = false
for expected_region in expected {
if __edit_region_within(region, expected_region) {
matched = true
break
}
}
if !matched {
errors = errors
+ [
{
code: "unexpected_changed_region",
message: "changed region falls outside expected patch range",
region: region,
},
]
}
}
return {
ok: len(errors) == 0,
actual_regions: actual,
expected_regions: expected,
errors: errors,
error_codes: errors.map({ error -> error.code }),
warnings: [],
provenance: {
module: "std/edit",
helper: "edit_validate_changed_regions",
before_sha256: __edit_sha256(before ?? ""),
after_sha256: __edit_sha256(after ?? ""),
caller: options?.provenance,
},
}
}