KeiSeiKit-1.0/_primitives/_rust/kei-agent-runtime/src/simulated_merge.rs
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

113 lines
3.7 KiB
Rust

//! Simulated-merge executor + glob matcher.
//!
//! Schema §Verify execution — worktree short-circuit → simulated merge:
//! orchestrator creates temp worktree off main, applies agent's diff, runs
//! verifies from that vantage to catch integration regressions invisible
//! in agent's isolated worktree.
use crate::validate::validate_agent_id;
use anyhow::{anyhow, Context, Result};
use std::path::{Path, PathBuf};
use std::process::Command;
/// Create a temp worktree off `main_repo` at HEAD of `main`, apply the agent's
/// diff, return the temp worktree path. Caller cleans up.
///
/// Validates `agent_id` before constructing any tmp path — path-traversal
/// defence per the HIGH-risk agent_id sink audit.
pub fn run_simulated_merge(
agent_id: &str,
agent_worktree: &Path,
main_repo: &Path,
) -> Result<PathBuf> {
validate_agent_id(agent_id)
.map_err(|e| anyhow!("agent_id rejected in run_simulated_merge: {e}"))?;
let tmp = std::env::temp_dir().join(format!("kei-test-merge-{agent_id}"));
let _ = std::fs::remove_dir_all(&tmp);
run_git(main_repo, &["worktree", "add", "-d", tmp.to_str().unwrap(), "main"])
.context("git worktree add failed")?;
let diff = run_git(agent_worktree, &["diff", "main"])
.context("git diff against main failed")?;
if !diff.trim().is_empty() {
apply_diff(&tmp, &diff)?;
}
Ok(tmp)
}
/// Apply a unified diff to `dir` via `git apply --index`. Empty diff is a no-op.
pub fn apply_diff(dir: &Path, diff: &str) -> Result<()> {
use std::io::Write;
let mut child = Command::new("git")
.arg("apply")
.arg("--index")
.current_dir(dir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.context("spawn git apply")?;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(diff.as_bytes()).context("write diff stdin")?;
}
let out = child.wait_with_output().context("git apply wait")?;
if !out.status.success() {
anyhow::bail!("git apply failed: {}", String::from_utf8_lossy(&out.stderr));
}
Ok(())
}
/// Run `git <args>` in `dir`, return stdout as UTF-8 string.
pub fn run_git(dir: &Path, args: &[&str]) -> Result<String> {
let out = Command::new("git")
.args(args)
.current_dir(dir)
.output()
.with_context(|| format!("git {}", args.join(" ")))?;
if !out.status.success() {
anyhow::bail!(
"git {} failed: {}",
args.join(" "),
String::from_utf8_lossy(&out.stderr)
);
}
Ok(String::from_utf8_lossy(&out.stdout).into_owned())
}
/// Shell-style glob match. Supports `**` (any directories) and `*` (any chars
/// except `/`). Bracketed classes and `?` not supported — task specs use
/// simple patterns.
pub fn glob_match(pattern: &str, path: &str) -> bool {
let re = glob_to_regex(pattern);
match regex::Regex::new(&re) {
Ok(r) => r.is_match(path),
Err(_) => false,
}
}
fn glob_to_regex(pattern: &str) -> String {
let mut out = String::from("^");
let bytes = pattern.as_bytes();
let mut i = 0;
while i < bytes.len() {
let c = bytes[i] as char;
if c == '*' && i + 1 < bytes.len() && bytes[i + 1] as char == '*' {
out.push_str(".*");
i += 2;
if i < bytes.len() && bytes[i] as char == '/' {
i += 1;
}
} else if c == '*' {
out.push_str("[^/]*");
i += 1;
} else if "().+?|^$\\[]{}".contains(c) {
out.push('\\');
out.push(c);
i += 1;
} else {
out.push(c);
i += 1;
}
}
out.push('$');
out
}