修复策划工具乱序局部修改导致的中断
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled

按原文位置排序并顺序拼接补丁,避免替换偏移失效。
新增乱序、中文与长度变化的局部修改回归测试。
同步策划 Agent 批量局部修改技术说明。
This commit is contained in:
2026-09-17 06:53:44 +00:00
parent d85622069d
commit 6e5bb571fb
2 changed files with 41 additions and 3 deletions
@@ -380,11 +380,16 @@ pub(crate) fn execute_design_file_tool(
}
return Err(details.join("\n"));
}
let mut updated = content.clone();
for (index, start, end) in matches.into_iter().rev() {
matches.sort_unstable_by_key(|(_, start, _)| *start);
let mut updated = String::with_capacity(content.len());
let mut cursor = 0;
for (index, start, end) in matches {
let (_, new) = &normalized[index];
updated.replace_range(start..end, new);
updated.push_str(&content[cursor..start]);
updated.push_str(new);
cursor = end;
}
updated.push_str(&content[cursor..]);
if updated == content {
return Err(format!("没有产生修改:{display}"));
}
@@ -834,6 +839,35 @@ mod tests {
assert!(!root.join("design_artifacts/notes").exists());
}
#[test]
fn patch_file_applies_out_of_order_edits_with_changing_utf8_lengths() {
let temp = test_root();
let root = temp.path();
execute_design_file_tool(
root,
"write_file",
&json!({"path":"notes/design.md","content":"开头\n\n保留一\n乙乙\n保留二\n\n结尾"}),
)
.expect("write");
execute_design_file_tool(
root,
"patch_file",
&json!({
"path":"notes/design.md",
"edits":[
{"old_text":"","new_text":"新的结论"},
{"old_text":"","new_text":"扩展A"},
{"old_text":"乙乙","new_text":""}
]
}),
)
.expect("patch out of order");
assert_eq!(
fs::read_to_string(root.join("design_artifacts/notes/design.md")).expect("read disk"),
"开头\n扩展A\n保留一\n\n保留二\n新的结论\n结尾"
);
}
#[test]
fn phase_context_injects_current_skill_only() {
let resources = DesignResources::new(pack_root()).expect("pack");