fix: patch checks full target span + marker injection/integrity (algo #8)
This commit is contained in:
+43
-7
@@ -282,9 +282,34 @@ def _protected_ranges(content: str, spans: list[str]) -> list[tuple[int, int]]:
|
||||
return ranges
|
||||
|
||||
|
||||
def _in_ranges(pos: int, ranges: list[tuple[int, int]]) -> bool:
|
||||
"""判断位置 pos 是否落在任意冻结区间内。"""
|
||||
return any(start <= pos < end for start, end in ranges)
|
||||
def _span_overlaps_ranges(pos: int, length: int, ranges: list[tuple[int, int]]) -> bool:
|
||||
"""判断 [pos, pos+length) 是否与任一冻结区间相交(不止起点)。
|
||||
|
||||
起点落在正文、末端伸入冻结区的 target 也须拦截,否则 replace/delete 会连带
|
||||
改动冻结区(如破坏 appendix/momentum marker)。
|
||||
|
||||
参数:
|
||||
pos: target 在正文中的起点。
|
||||
length: target 长度。
|
||||
ranges: 冻结区间 [start, end) 列表。
|
||||
返回:
|
||||
与任一区间相交返回 True。
|
||||
"""
|
||||
end = pos + length
|
||||
return any(start < end and pos < r_end for start, r_end in ranges)
|
||||
|
||||
|
||||
# 冻结区 marker 字面量:LLM 生成的 edit 不得注入这些字面量,否则破坏 marker 配对
|
||||
_MARKER_LITERALS = (APPENDIX_START, APPENDIX_END, MOMENTUM_START, MOMENTUM_END)
|
||||
|
||||
|
||||
def _edit_injects_marker(edit: dict) -> bool:
|
||||
"""判断 edit 的 target/content 是否含冻结区 marker 字面量(注入拦截)。"""
|
||||
for key in ("target", "content"):
|
||||
value = edit.get(key)
|
||||
if isinstance(value, str) and any(m in value for m in _MARKER_LITERALS):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _append_at(content: str, ranges: list[tuple[int, int]]) -> int:
|
||||
@@ -317,8 +342,8 @@ def _do_insert_after(
|
||||
_insert_at(content, _append_at(content, ranges), payload),
|
||||
"applied_insert_after_fallback",
|
||||
)
|
||||
if _in_ranges(pos, ranges):
|
||||
logger.warning("insert_after 目标在冻结区,跳过 target={}", target[:80])
|
||||
if _span_overlaps_ranges(pos, len(target), ranges):
|
||||
logger.warning("insert_after 目标跨入冻结区,跳过 target={}", target[:80])
|
||||
return content, "skipped_protected"
|
||||
at = pos + len(target)
|
||||
nl = content.find("\n", at)
|
||||
@@ -340,8 +365,8 @@ def _do_replace_delete(
|
||||
if pos == -1:
|
||||
logger.warning("{} 锚点缺失,跳过 target={}", op, target[:80])
|
||||
return content, "skipped_target_not_found"
|
||||
if _in_ranges(pos, ranges):
|
||||
logger.warning("{} 目标在冻结区,跳过 target={}", op, target[:80])
|
||||
if _span_overlaps_ranges(pos, len(target), ranges):
|
||||
logger.warning("{} 目标跨入冻结区,跳过 target={}", op, target[:80])
|
||||
return content, "skipped_protected"
|
||||
new_content = content.replace(target, payload if op == "replace" else "", 1)
|
||||
return new_content, "applied_" + op
|
||||
@@ -403,6 +428,17 @@ def apply_patch_with_report(
|
||||
reports: list[dict] = []
|
||||
for i, edit in enumerate(edits, 1):
|
||||
try:
|
||||
if isinstance(edit, dict) and _edit_injects_marker(edit):
|
||||
logger.warning("edit 含冻结区 marker 字面量,拒绝该 edit index={}", i)
|
||||
report = {
|
||||
"op": str(edit.get("op", "")),
|
||||
"target": str(edit.get("target", "") or "")[:200],
|
||||
"content_preview": str(edit.get("content", "") or "")[:200],
|
||||
"status": "skipped_marker_injection",
|
||||
}
|
||||
report["index"] = i
|
||||
reports.append(report)
|
||||
continue
|
||||
ranges = _protected_ranges(content, spans)
|
||||
content, report = _apply_one(content, edit, ranges)
|
||||
except (KeyError, TypeError, ValueError, AttributeError) as exc:
|
||||
|
||||
Reference in New Issue
Block a user