feat(flywheel): 阶段三执行质量预测层(trailer 死链修复 + 风险画像) #2

Merged
lanrtop merged 14 commits from feat/flywheel-intelligence into master 2026-07-04 14:45:28 +08:00
Showing only changes of commit 2116e54e29 - Show all commits

View File

@ -47,19 +47,120 @@ fn liangjing_path(project_id: &str) -> Result<PathBuf, String> {
Ok(project_root(project_id)?.join(".claude").join("liangjing.json"))
}
/// 写炼境上下文文件,供子项目 Claude 会话直接读取 project_id(不通过遍历 list_group_projects 匹配)
fn write_liangjing_context(project_id: &str, port: u16) -> Result<(), String> {
/// 写炼境上下文文件,供子项目 Claude 会话直接读取 project_id / group_id
fn write_liangjing_context(project_id: &str, group_id: &str, port: u16) -> Result<(), String> {
let path = liangjing_path(project_id)?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| format!("创建 .claude 目录失败: {}", e))?;
}
let ctx = json!({ "project_id": project_id, "mcp_port": port });
let ctx = json!({
"project_id": project_id,
"group_id": group_id,
"mcp_port": port
});
let content = serde_json::to_string_pretty(&ctx).map_err(|e| e.to_string())?;
std::fs::write(&path, content)
.map_err(|e| format!("写入 liangjing.json 失败: {}", e))
}
/// 查找 claude CLI 路径(优先环境变量 CLAUDE_BIN然后常用安装目录最后 where 命令)
fn find_claude_bin() -> Option<String> {
if let Ok(path) = std::env::var("CLAUDE_BIN") {
if std::path::Path::new(&path).exists() {
return Some(path);
}
}
let home = std::env::var("USERPROFILE")
.or_else(|_| std::env::var("HOME"))
.unwrap_or_default();
let candidates = [
format!(r"{}\.local\bin\claude.exe", home),
format!(r"{}\AppData\Local\AnthropicClaude\claude.exe", home),
r"C:\Program Files\AnthropicClaude\claude.exe".to_string(),
];
for c in &candidates {
if std::path::Path::new(c).exists() {
return Some(c.clone());
}
}
// 最后尝试 where 命令GUI 进程 PATH 可能不含用户 binbut worth trying
if let Ok(out) = std::process::Command::new("where").arg("claude.exe").output() {
if out.status.success() {
if let Ok(stdout) = String::from_utf8(out.stdout) {
if let Some(line) = stdout.lines().next() {
return Some(line.trim().to_string());
}
}
}
}
None
}
/// 将产品组 MCP 注册到 Claude 全局配置(~/.claude.jsonbest-effort失败仅记录日志
fn try_register_globally(group_id: &str, port: u16) {
let server_name = format!("lian-jing-{}", group_id);
let server_url = format!("http://127.0.0.1:{}/mcp/group/{}", port, group_id);
let Some(claude_bin) = find_claude_bin() else {
db::log_event(
"mcp_inject",
"warn",
"claude CLI 未找到,跳过全局 Mcp 注册",
None,
);
return;
};
let git_bash = std::env::var("CLAUDE_CODE_GIT_BASH_PATH").unwrap_or_else(|_| {
r"C:\Program Files\Git\bin\bash.exe".to_string()
});
match std::process::Command::new(&claude_bin)
.args([
"mcp",
"add",
"--transport",
"sse",
"-s",
"user",
&server_name,
&server_url,
])
.env("CLAUDE_CODE_GIT_BASH_PATH", &git_bash)
.output()
{
Ok(out) if out.status.success() => {
db::log_event(
"mcp_inject",
"info",
"全局 MCP 注册成功",
Some(&server_name),
);
}
Ok(out) => {
let msg = format!(
"claude mcp add 非零退出: {}",
String::from_utf8_lossy(&out.stderr)
);
db::log_event("mcp_inject", "warn", &msg, Some(&server_name));
}
Err(e) => {
db::log_event(
"mcp_inject",
"warn",
&format!("执行 claude mcp add 失败: {}", e),
Some(&server_name),
);
}
}
}
// ── JSON 读写 ─────────────────────────────────────────────────────────────────
fn read_json(path: &PathBuf) -> Result<Value, String> {
@ -118,8 +219,11 @@ pub fn inject_project_mcp(project_id: &str, group_id: &str) -> Result<(), String
log_event("mcp_inject", "info", "MCP 配置注入成功", Some(&ctx));
})?;
// 写炼境上下文,让子项目 Claude 会话无需遍历即可知道自己的 project_id
let _ = write_liangjing_context(project_id, port);
// 写炼境上下文(含 group_id让子项目 Claude 会话无需遍历即可知道 project_id + group_id
let _ = write_liangjing_context(project_id, group_id, port);
// 同步注册到 ~/.claude.json让 Claude Code 能加载 MCP 工具best-effort
try_register_globally(group_id, port);
Ok(())
}