fix: P2 conventions 分发覆盖 WSL 项目

save_user_conventions / reset_conventions_to_builtin 移除前端传路径的设计,
改为后端自行从 DB 查询所有项目路径(Windows 路径 + WSL UNC 路径),
确保 WSL 项目不再被漏同步;同步锁定蓝图数据:T4 关锁、blueprint-view frontmatter 修正
This commit is contained in:
lanrtop 2026-04-08 03:55:36 +09:00
parent 56536effff
commit 7a81222686
7 changed files with 78 additions and 26 deletions

View File

@ -85,6 +85,7 @@ modules:
area: frontend
status: done
progress: 100
position: [1000, 0]
- id: blueprint-onboarding

View File

@ -49,27 +49,27 @@ progress: 100
- acceptance: 设置页提供「同步所有项目蓝图规则」按钮,调用 sync_blueprint_rules 遍历所有已注册项目,返回每个项目的同步结果摘要
### ✅ 用户版 CONVENTIONS 存储层初始化
- status: todo
- status: done
- complexity: S
- files: src-tauri/src/commands/blueprint.rs, src-tauri/src/lib.rs
- acceptance: 新增 `USER_CONVENTIONS_DIR: OnceLock<PathBuf>``init_user_conventions_dir(app_data_dir)`,在 lib.rs setup() 中紧跟 db::init 调用;`effective_conventions_content() -> String` 优先读 app_data_dir/conventions/CONVENTIONS.md文件不存在时 fallback 到编译时内置;`effective_conventions_version() -> String` 从 effective 内容解析 frontmatter version
### ✅ 用户版 CONVENTIONS 读写 command
- status: todo
- status: done
- complexity: M
- files: src-tauri/src/commands/blueprint.rs, src-tauri/src/lib.rs, src/lib/commands.ts
- depends: blueprint-governance
- acceptance: 新增三个 command(1) `get_user_conventions_status` 返回 `{ source: "builtin"|"user", version, content, builtin_version, outdated_from_builtin }`(2) `save_user_conventions(content)` 从 frontmatter 解析版本(失败时返回错误),写入用户版文件,清除 effective 内容缓存,自动触发遍历所有项目的 sync_blueprint_rules返回 `{ version, synced_count }`(3) `reset_conventions_to_builtin` 删除用户版文件并清除缓存;三个 command 注册到 invoke_handlerTS 封装完成
### ✅ sync/梦核/summary 切换为 effective 内容
- status: todo
- status: done
- complexity: S
- files: src-tauri/src/commands/blueprint.rs
- depends: blueprint-governance
- acceptance: `sync_blueprint_rules`、`generate_muhe_prompt`、`conventions_rules_summary`、`claude_managed_template` 全部改用 `effective_conventions_content()``effective_conventions_version()`,不再直接引用 MASTER_CONVENTIONS 常量;有用户版时分发用户版,无用户版时 fallback 内置版,行为与改前一致
### ✅ GovernancePanel 规则编辑器 UI
- status: todo
- status: done
- complexity: M
- files: src/components/dashboard/BlueprintModal.tsx, src/lib/commands.ts
- depends: blueprint-governance

View File

@ -1,8 +1,8 @@
---
id: blueprint-view
name: 蓝图可视化
status: in_progress
progress: 86
status: done
progress: 100
---
## 概述

View File

@ -67,8 +67,8 @@
- depends: T1
- acceptance: 项目卡片底部出现"🎓"导师按钮,点击展示健康摘要和近期错误,"生成导师报告"按钮点击后将 mentor_markdown 复制到剪贴板
### 🔵 T4 · 炼境自指接入
- status: in_progress
### T4 · 炼境自指接入
- status: done
- complexity: S
- files: src-tauri/src/commands/mentor.rs
- depends: T1
@ -116,9 +116,16 @@
- depends: T1
- acceptance: 应用启动时后台扫描所有项目,超 14 天无 git 提交或蓝图存在 🔴 blocked 任务的项目写入告警category=startup_alert前端 App.tsx 加载完成后调用 `get_startup_alerts`,有结果则在页面顶部显示可关闭的黄色 Banner列出需关注的项目名
### ✅ T11 · 项目历史快照
- status: done
- complexity: S
- files: src-tauri/src/db.rs, src-tauri/src/commands/mentor.rs, src-tauri/src/mcp_server.rs
- acceptance: 新增 `project_snapshots` 表(每天每项目唯一 UNIQUE 约束启动健康扫描时写入快照days_since_commit、blueprint_done_rate、blueprint_in_progress、blueprint_blocked、has_errorsMCP 新增 `get_project_snapshots` 工具,返回指定天数的历史趋势 Markdown 表格
## 决策记录
- 项目导师是洞察与引导层health-center 是数据基础层,两者是上下游关系,不合并为一个模块
- Claude 是智能层的唯一执行者,代码只负责数据采集与结构化,不在代码层做质量推断
- 炼境自指是设计意图,不是边缘 case——从一开始就将其纳入系统而非事后兼容
- 任务卡保持 concept 状态直到 health-center 完成,避免在数据基础未就绪时过早实现上层
- 历史快照是 Claude 做趋势分析的数据基础,代码只负责采集落库,分析由 Claude 通过 MCP 完成

View File

@ -1123,8 +1123,56 @@ pub struct SaveConventionsResult {
pub synced_count: u32,
}
/// 从 DB 收集所有项目的可访问路径Windows 路径或 WSL UNC 路径)
fn collect_all_sync_paths() -> Vec<String> {
let conn = match crate::db::pool().get() {
Ok(c) => c,
Err(_) => return vec![],
};
let mut stmt = match conn.prepare(
"SELECT win_path, wsl_path, platform FROM project_workspaces",
) {
Ok(s) => s,
Err(_) => return vec![],
};
let configured_distro: String = conn
.query_row("SELECT value FROM settings WHERE key='wsl_distro'", [], |r| r.get(0))
.unwrap_or_else(|_| "Ubuntu".to_string());
stmt.query_map([], |row| {
Ok((
row.get::<_, Option<String>>(0)?,
row.get::<_, Option<String>>(1)?,
row.get::<_, Option<String>>(2)?,
))
})
.map(|iter| {
iter.filter_map(|r| r.ok())
.filter_map(|(win_path, wsl_path, platform)| {
match platform.as_deref() {
Some("wsl") => {
let lp = wsl_path?;
if lp.starts_with("\\\\") {
// 已是 UNC直接用
Some(lp)
} else if !lp.is_empty() {
// Linux 路径,转为 UNC
let inner = lp.trim_start_matches('/').replace('/', "\\");
Some(format!("\\\\wsl.localhost\\{}\\{}", configured_distro, inner))
} else {
None
}
}
_ => win_path.filter(|p| !p.is_empty()),
}
})
.collect()
})
.unwrap_or_default()
}
#[tauri::command]
pub fn save_user_conventions(content: String, project_paths: Vec<String>) -> Result<SaveConventionsResult, String> {
pub fn save_user_conventions(content: String) -> Result<SaveConventionsResult, String> {
// 1. 解析版本号,非法内容拒绝写入
let version = parse_frontmatter_version_str(&content)
.ok_or_else(|| "内容缺少有效的 frontmatter version 字段,写入已拒绝".to_string())?;
@ -1141,10 +1189,10 @@ pub fn save_user_conventions(content: String, project_paths: Vec<String>) -> Res
// 3. 清除缓存,使新版本立即生效
invalidate_effective_cache();
// 4. 自动同步所有传入的项目
// 4. 自动同步所有项目(后端自行查询路径,含 WSL
let mut synced = 0u32;
for path_str in &project_paths {
if sync_blueprint_rules(path_str.clone()).is_ok() {
for path_str in collect_all_sync_paths() {
if sync_blueprint_rules(path_str).is_ok() {
synced += 1;
}
}
@ -1153,7 +1201,7 @@ pub fn save_user_conventions(content: String, project_paths: Vec<String>) -> Res
}
#[tauri::command]
pub fn reset_conventions_to_builtin(project_paths: Vec<String>) -> Result<SaveConventionsResult, String> {
pub fn reset_conventions_to_builtin() -> Result<SaveConventionsResult, String> {
// 删除用户版文件
if let Some(path) = user_conventions_path() {
if path.exists() {
@ -1163,10 +1211,10 @@ pub fn reset_conventions_to_builtin(project_paths: Vec<String>) -> Result<SaveCo
}
invalidate_effective_cache();
// 同步所有项目回内置版本
// 同步所有项目回内置版本(后端自行查询路径,含 WSL
let mut synced = 0u32;
for path_str in &project_paths {
if sync_blueprint_rules(path_str.clone()).is_ok() {
for path_str in collect_all_sync_paths() {
if sync_blueprint_rules(path_str).is_ok() {
synced += 1;
}
}

View File

@ -1175,9 +1175,7 @@ function GovernancePanel({
setConventionsSaveError(null);
setConventionsSaveResult(null);
try {
const projects = await getProjects();
const paths = projects.map((p: Project) => p.win_path).filter((p): p is string => !!p);
const res = await saveUserConventions(conventionsInput, paths);
const res = await saveUserConventions(conventionsInput);
setConventionsSaveResult(`已应用 v${res.version},同步了 ${res.synced_count} 个项目`);
setConventionsInput("");
setConventionsEditorOpen(false);
@ -1195,9 +1193,7 @@ function GovernancePanel({
setConventionsSaveError(null);
setConventionsSaveResult(null);
try {
const projects = await getProjects();
const paths = projects.map((p: Project) => p.win_path).filter((p): p is string => !!p);
const res = await resetConventionsToBuiltin(paths);
const res = await resetConventionsToBuiltin();
setConventionsSaveResult(`已重置为内置基线 v${res.version},同步了 ${res.synced_count} 个项目`);
refetchUserConventions();
refetchStatus();

View File

@ -1017,11 +1017,11 @@ export interface SaveConventionsResult {
export const getUserConventionsStatus = () =>
invoke<UserConventionsStatus>("get_user_conventions_status");
export const saveUserConventions = (content: string, projectPaths: string[]) =>
invoke<SaveConventionsResult>("save_user_conventions", { content, projectPaths });
export const saveUserConventions = (content: string) =>
invoke<SaveConventionsResult>("save_user_conventions", { content });
export const resetConventionsToBuiltin = (projectPaths: string[]) =>
invoke<SaveConventionsResult>("reset_conventions_to_builtin", { projectPaths });
export const resetConventionsToBuiltin = () =>
invoke<SaveConventionsResult>("reset_conventions_to_builtin");
export interface FlywheelCohort {
conventions_version: string;