// 评审提示词的拼装:把解析好的 Diff 变成给模型看的文本。
//
// 只做数据到文本的转换,不联网,所以可以单独测。分成这两层是有原因的:
// MoonBit native 侧没有现成的 HTTPS 包,发请求要靠外部程序,
// 那就先把「给模型看什么」做实,传输那一层单独接。
//
// 每行前面带两列行号(改动前 / 改动后),模型给意见时才能指到准确的行;
// 这两个值来自解析器,不是这里现算的。
///|
fn lineno_column(n : Int?) -> String {
match n {
None => ""
Some(v) => v.to_string()
}
}
///|
fn hunk_text(h : Hunk) -> String {
let out : Array[String] = []
out.push(
"@@ -\{h.old_start},\{h.old_count} +\{h.new_start},\{h.new_count} @@",
)
for l in h.lines {
let old = lineno_column(l.old_lineno).pad_start(4, ' ')
let new = lineno_column(l.new_lineno).pad_start(4, ' ')
out.push("\{old}\{new} \{l.kind.prefix()}\{l.content}")
}
out.join("\n")
}
///|
fn file_text(f : FileDiff) -> String {
let head = "### \{f.display_path()} (\{f.status.label()}, +\{f.added_count()} -\{f.removed_count()})"
let body = if f.binary {
"(二进制文件,没有可比对的文本)"
} else if f.hunks.is_empty() {
"(没有内容改动)"
} else {
f.hunks.map(hunk_text).join("\n")
}
head + "\n" + body
}
///|
/// 给模型的指令。写在这里而不是散在命令行里,方便换供应商时一起替换。
let prompt_rules : String = [
"你在评审一个 git 改动。下面是 unified diff 解析出来的结果:每段以一个文件开头,",
"行首两列分别是改动前和改动后的行号,之后是 diff 的 `+` / `-` / 空格标记。",
"", "只针对新增和删除的行提意见,重点看正确性、边界条件、并发和资源泄漏、",
"以及和周围代码不一致的地方。格式和命名这类工具能查的问题不用提。",
"", "按这个格式逐条输出,一条一行,不要额外解释:", " 文件路径:行号: 意见",
"行号用改动后的行号;意见针对被删掉的内容时,用改动前的行号。",
"没有意见就只输出 LGTM。",
].join("\n")
///|
pub fn Diff::review_prompt(self : Diff) -> String {
let out : Array[String] = [prompt_rules, ""]
for f in self.files {
out.push(file_text(f))
out.push("")
}
out.join("\n")
}