feat: 拆分评审与提交消息提示词并增加 hook 备份与 JSON 输出

- 新增 custom_reviewer_prompt 和 custom_commit_msg_prompt,兼容旧 custom_prompt
- pre-commit hook 安装时备份已有 hook,卸载时恢复
- 支持 --format json 输出评审结果
- diff 发送前自动脱敏敏感信息
This commit is contained in:
unclesam committed 2026-08-23 22:31:30 +08:00
1 parent 39c58316a5
commit b04d70b99a
18 files changed
+637 -45

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
package security
import "regexp"
type redactRule struct {
pattern *regexp.Regexp
replacement string
}
var redactRules = []redactRule{
{
// PEM 私钥必须优先处理,避免后续规则只替换其中一部分。
pattern: regexp.MustCompile(`(?s)-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----`),
replacement: "[REDACTED_SECRET]",
},
{
// 常见 Token 前缀:OpenAI/GitHub/AWS/Slack 等。
pattern: regexp.MustCompile(`(?i)\b(?:sk-[A-Za-z0-9]{16,}|gh[pousr]_[A-Za-z0-9_]{16,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})\b`),
replacement: "[REDACTED_SECRET]",
},
{
// Authorization: Bearer ...
pattern: regexp.MustCompile(`(?i)(Authorization\s*:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+`),
replacement: "${1}[REDACTED_BEARER_TOKEN]",
},
{
// JWT。
pattern: regexp.MustCompile(`\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b`),
replacement: "[REDACTED_SECRET]",
},
{
// 数据库连接串。
pattern: regexp.MustCompile(`(?i)\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?):\/\/[^\s"'<>]+`),
replacement: "[REDACTED_SECRET]",
},
{
// 常见密钥字段赋值。值至少 8 个字符,减少普通变量误报。
pattern: regexp.MustCompile(`(?i)(\b(?:api[_-]?key|access[_-]?key|secret(?:[_-]?key)?|password|passwd|token)\b\s*[:=]\s*["']?)[^\s"' ,;]{8,}`),
replacement: "${1}[REDACTED_SECRET]",
},
}
// RedactSecrets 在 Diff 发送给模型前替换常见敏感信息。
// 脱敏只作用于发送给模型的字符串,不修改工作区文件或 Git 内容。
func RedactSecrets(input string) string {
output := input
for _, rule := range redactRules {
output = rule.pattern.ReplaceAllString(output, rule.replacement)
}
return output
}
+38
View File
@@ -0,0 +1,38 @@
package security
import (
"strings"
"testing"
)
func TestRedactSecrets(t *testing.T) {
input := `
api_key = "sk-12345678901234567890"
Authorization: Bearer my-secret-bearer-token-123
password: "this-is-a-secret-password"
-----BEGIN RSA PRIVATE KEY-----
secret private key content
-----END RSA PRIVATE KEY-----
`
if strings.Contains(input, "[REDACTED_") {
t.Fatal("测试输入不应预先包含脱敏占位符")
}
output := RedactSecrets(input)
if output == input {
t.Fatal("RedactSecrets 未改变包含敏感信息的输入")
}
for _, secret := range []string{
"sk-12345678901234567890",
"my-secret-bearer-token-123",
"this-is-a-secret-password",
"secret private key content",
} {
if strings.Contains(output, secret) {
t.Fatalf("敏感信息未被脱敏: %q\noutput: %s", secret, output)
}
}
if !strings.Contains(output, "[REDACTED_SECRET]") || !strings.Contains(output, "[REDACTED_BEARER_TOKEN]") {
t.Fatalf("脱敏占位符不完整: %s", output)
}
}