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:
parent
56536effff
commit
7a81222686
@ -85,6 +85,7 @@ modules:
|
||||
area: frontend
|
||||
status: done
|
||||
progress: 100
|
||||
|
||||
position: [1000, 0]
|
||||
|
||||
- id: blueprint-onboarding
|
||||
|
||||
@ -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_handler,TS 封装完成
|
||||
|
||||
### ✅ 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
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
---
|
||||
id: blueprint-view
|
||||
name: 蓝图可视化
|
||||
status: in_progress
|
||||
progress: 86
|
||||
status: done
|
||||
progress: 100
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
@ -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_errors);MCP 新增 `get_project_snapshots` 工具,返回指定天数的历史趋势 Markdown 表格
|
||||
|
||||
## 决策记录
|
||||
|
||||
- 项目导师是洞察与引导层,health-center 是数据基础层,两者是上下游关系,不合并为一个模块
|
||||
- Claude 是智能层的唯一执行者,代码只负责数据采集与结构化,不在代码层做质量推断
|
||||
- 炼境自指是设计意图,不是边缘 case——从一开始就将其纳入系统,而非事后兼容
|
||||
- 任务卡保持 concept 状态直到 health-center 完成,避免在数据基础未就绪时过早实现上层
|
||||
- 历史快照是 Claude 做趋势分析的数据基础,代码只负责采集落库,分析由 Claude 通过 MCP 完成
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user