diff --git a/.blueprint/manifest.yaml b/.blueprint/manifest.yaml index caa2c7e..0a4d16c 100644 --- a/.blueprint/manifest.yaml +++ b/.blueprint/manifest.yaml @@ -85,6 +85,7 @@ modules: area: frontend status: done progress: 100 + position: [1000, 0] - id: blueprint-onboarding diff --git a/.blueprint/modules/blueprint-governance.md b/.blueprint/modules/blueprint-governance.md index f8fcb5b..914cc41 100644 --- a/.blueprint/modules/blueprint-governance.md +++ b/.blueprint/modules/blueprint-governance.md @@ -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` 和 `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 diff --git a/.blueprint/modules/blueprint-view.md b/.blueprint/modules/blueprint-view.md index c6b67c3..6deca0e 100644 --- a/.blueprint/modules/blueprint-view.md +++ b/.blueprint/modules/blueprint-view.md @@ -1,8 +1,8 @@ --- id: blueprint-view name: 蓝图可视化 -status: in_progress -progress: 86 +status: done +progress: 100 --- ## 概述 diff --git a/.blueprint/modules/project-mentor.md b/.blueprint/modules/project-mentor.md index 5258b49..16e0eb6 100644 --- a/.blueprint/modules/project-mentor.md +++ b/.blueprint/modules/project-mentor.md @@ -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 完成 diff --git a/src-tauri/src/commands/blueprint.rs b/src-tauri/src/commands/blueprint.rs index 2aa89c5..c8b858c 100644 --- a/src-tauri/src/commands/blueprint.rs +++ b/src-tauri/src/commands/blueprint.rs @@ -1123,8 +1123,56 @@ pub struct SaveConventionsResult { pub synced_count: u32, } +/// 从 DB 收集所有项目的可访问路径(Windows 路径或 WSL UNC 路径) +fn collect_all_sync_paths() -> Vec { + 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>(0)?, + row.get::<_, Option>(1)?, + row.get::<_, Option>(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) -> Result { +pub fn save_user_conventions(content: String) -> Result { // 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) -> 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) -> Res } #[tauri::command] -pub fn reset_conventions_to_builtin(project_paths: Vec) -> Result { +pub fn reset_conventions_to_builtin() -> Result { // 删除用户版文件 if let Some(path) = user_conventions_path() { if path.exists() { @@ -1163,10 +1211,10 @@ pub fn reset_conventions_to_builtin(project_paths: Vec) -> Result 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(); diff --git a/src/lib/commands.ts b/src/lib/commands.ts index cf95b01..7aba8db 100644 --- a/src/lib/commands.ts +++ b/src/lib/commands.ts @@ -1017,11 +1017,11 @@ export interface SaveConventionsResult { export const getUserConventionsStatus = () => invoke("get_user_conventions_status"); -export const saveUserConventions = (content: string, projectPaths: string[]) => - invoke("save_user_conventions", { content, projectPaths }); +export const saveUserConventions = (content: string) => + invoke("save_user_conventions", { content }); -export const resetConventionsToBuiltin = (projectPaths: string[]) => - invoke("reset_conventions_to_builtin", { projectPaths }); +export const resetConventionsToBuiltin = () => + invoke("reset_conventions_to_builtin"); export interface FlywheelCohort { conventions_version: string;