refactor: 蓝图系统三项优化(P1/P3/P4)

P1 - CONVENTIONS 版本号自动同步
- 删除手动维护的 MASTER_CONVENTIONS_VERSION 常量
- 新增 master_conventions_version() 从 frontmatter 自动解析,基于 OnceLock 缓存
- CLAUDE_MANAGED_TEMPLATE 改为 claude_managed_template() 函数,版本号随之自动同步

P3 - 梦核提示词精简
- 新增 conventions_rules_summary(),按白名单抽取规则段落
- 保留:何时更新蓝图、模块状态定义、可派发标准、迭代管理、飞轮外环、注意事项
- 跳过:任务卡格式、manifest 字段规范、流程说明等实现细节
- 提示词 CONVENTIONS 部分从 ~500 行压缩至关键规则段落

P4 - usage.json 补执行 delta
- 快照新增 task_ids.done / task_ids.blocked(当前任务状态,供下次对比)
- 快照新增 delta.newly_done / newly_blocked / unblocked(与上次快照的差量)
- 梦核可据此看到执行轨迹,而非仅状态截面
This commit is contained in:
lanrtop 2026-04-08 02:59:40 +09:00
parent 69c05380cc
commit 4d9c003470

View File

@ -4,10 +4,24 @@ use std::path::Path;
// ── 主节点master CONVENTIONS.md 内容和版本(编译时嵌入)────────────────────
const MASTER_CONVENTIONS: &str = include_str!("../../../.blueprint/CONVENTIONS.md");
const MASTER_CONVENTIONS_VERSION: &str = "1.4.2";
// CLAUDE.md 蓝图管理区模板(版本与 CONVENTIONS 保持一致)
const CLAUDE_MANAGED_TEMPLATE: &str = r#"<!-- BLUEPRINT_MANAGED_START version="1.4.2" -->
/// 从 MASTER_CONVENTIONS frontmatter 自动解析版本号,无需手动维护常量。
fn master_conventions_version() -> &'static str {
static VERSION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
VERSION.get_or_init(|| {
for line in MASTER_CONVENTIONS.lines() {
if let Some(v) = line.strip_prefix("version: ") {
return v.trim().to_string();
}
}
"unknown".to_string()
})
}
/// CLAUDE.md 蓝图管理区模板,版本号从 CONVENTIONS frontmatter 自动同步。
fn claude_managed_template() -> String {
let ver = master_conventions_version();
format!(r#"<!-- BLUEPRINT_MANAGED_START version="{ver}" -->
##
使 `.blueprint/`
@ -30,7 +44,8 @@ const CLAUDE_MANAGED_TEMPLATE: &str = r#"<!-- BLUEPRINT_MANAGED_START version="1
`.blueprint/CONVENTIONS.md`
- `files` + `acceptance` + complexity `S/M` 📋
- Claude Agent
<!-- BLUEPRINT_MANAGED_END -->"#;
<!-- BLUEPRINT_MANAGED_END -->"#)
}
// ── manifest.yaml 对应结构 ───────────────────────────────────────────────────
@ -896,7 +911,7 @@ pub fn get_blueprint_status(project_path: String) -> Result<BlueprintStatus, Str
claude_md_managed: false,
claude_md_version: None,
manifest_updated: None,
master_version: MASTER_CONVENTIONS_VERSION.to_string(),
master_version: master_conventions_version().to_string(),
});
}
@ -920,9 +935,9 @@ pub fn get_blueprint_status(project_path: String) -> Result<BlueprintStatus, Str
let manifest_updated = read_manifest_updated(&bp_dir.join("manifest.yaml"));
// 判断状态
let rules_current = conventions_version.as_deref() == Some(MASTER_CONVENTIONS_VERSION)
let rules_current = conventions_version.as_deref() == Some(master_conventions_version())
&& claude_md_managed
&& claude_md_version.as_deref() == Some(MASTER_CONVENTIONS_VERSION);
&& claude_md_version.as_deref() == Some(master_conventions_version());
let status = if !rules_current {
"rules_outdated".to_string()
@ -938,7 +953,7 @@ pub fn get_blueprint_status(project_path: String) -> Result<BlueprintStatus, Str
claude_md_managed,
claude_md_version,
manifest_updated,
master_version: MASTER_CONVENTIONS_VERSION.to_string(),
master_version: master_conventions_version().to_string(),
})
}
@ -965,9 +980,10 @@ pub fn sync_blueprint_rules(project_path: String) -> Result<SyncResult, String>
// 2. 处理 CLAUDE.md
let claude_path = root.join("CLAUDE.md");
let managed_template = claude_managed_template();
let claude_action = if !claude_path.exists() {
// 创建新文件
let content = format!("# {}\n\n{CLAUDE_MANAGED_TEMPLATE}\n",
let content = format!("# {}\n\n{managed_template}\n",
root.file_name().and_then(|n| n.to_str()).unwrap_or("Project"));
std::fs::write(&claude_path, content)
.map_err(|e| format!("创建 CLAUDE.md 失败: {e}"))?;
@ -978,13 +994,13 @@ pub fn sync_blueprint_rules(project_path: String) -> Result<SyncResult, String>
if content.contains("<!-- BLUEPRINT_MANAGED_START") {
// 替换现有 MANAGED 区
let new_content = replace_managed_section(&content, CLAUDE_MANAGED_TEMPLATE);
let new_content = replace_managed_section(&content, &managed_template);
std::fs::write(&claude_path, new_content)
.map_err(|e| format!("更新 CLAUDE.md 失败: {e}"))?;
"updated".to_string()
} else {
// 追加 MANAGED 区
let new_content = format!("{}\n\n{CLAUDE_MANAGED_TEMPLATE}\n", content.trim_end());
let new_content = format!("{}\n\n{managed_template}\n", content.trim_end());
std::fs::write(&claude_path, new_content)
.map_err(|e| format!("追加 CLAUDE.md 失败: {e}"))?;
"appended".to_string()
@ -995,7 +1011,7 @@ pub fn sync_blueprint_rules(project_path: String) -> Result<SyncResult, String>
success: true,
conventions_written: true,
claude_md_action: claude_action.clone(),
message: format!("CONVENTIONS.md 已同步至 v{MASTER_CONVENTIONS_VERSION}CLAUDE.md {claude_action}"),
message: format!("CONVENTIONS.md 已同步至 v{}CLAUDE.md {claude_action}", master_conventions_version()),
})
}
@ -1348,6 +1364,45 @@ pub fn get_flywheel_stats(project_paths: Vec<String>) -> Result<FlywheelStats, S
// ── 梦核提示词生成 ────────────────────────────────────────────────────────────
/// 从完整 CONVENTIONS 中抽取对梦核有判断价值的规则段落。
/// 跳过任务卡格式定义、manifest 字段规范、安装说明、/architect /splitter 流程等纯实现细节。
fn conventions_rules_summary() -> String {
// 保留这些二级/三级标题对应的章节(前缀匹配)
const KEEP: &[&str] = &[
"## 何时更新蓝图",
"## 模块状态定义",
"### 可派发标准",
"## 迭代管理",
"## 飞轮外环",
"## 注意事项",
];
let mut result = Vec::<&str>::new();
let mut in_keep = false;
let mut current_section_start = 0usize;
let lines: Vec<&str> = MASTER_CONVENTIONS.lines().collect();
for (i, &line) in lines.iter().enumerate() {
let is_heading = line.starts_with("## ") || line.starts_with("### ");
if is_heading {
if in_keep {
// 结束上一个保留段
let chunk = &lines[current_section_start..i];
result.extend_from_slice(chunk);
result.push("");
}
in_keep = KEEP.iter().any(|&k| line.starts_with(k));
if in_keep { current_section_start = i; }
}
}
// 收尾
if in_keep {
result.extend_from_slice(&lines[current_section_start..]);
}
result.join("\n")
}
#[tauri::command]
pub fn generate_muhe_prompt(stats: FlywheelStats) -> String {
// ── 队列对比表 ────────────────────────────────────────────────────────────
@ -1456,7 +1511,9 @@ pub fn generate_muhe_prompt(stats: FlywheelStats) -> String {
---
## CONVENTIONS.mdv{conventions_version}
## CONVENTIONS.mdv{conventions_version}
> /
{conventions}
@ -1500,8 +1557,8 @@ pub fn generate_muhe_prompt(stats: FlywheelStats) -> String {
ai_stale_summary = ai_stale_summary,
iteration_summary = iteration_summary,
stalled_modules_summary = stalled_modules_summary,
conventions_version = MASTER_CONVENTIONS_VERSION,
conventions = MASTER_CONVENTIONS,
conventions_version = master_conventions_version(),
conventions = conventions_rules_summary(),
)
}
@ -1585,6 +1642,47 @@ fn append_usage_snapshot(bp_dir: &Path, manifest: &BlueprintManifest, stats: &Bl
.map(|m| m.id.clone())
.collect();
// 当前所有任务的状态快照(用于 delta 计算和下次快照对比)
let mut current_done: std::collections::HashSet<String> = Default::default();
let mut current_blocked: std::collections::HashSet<String> = Default::default();
for module in &manifest.modules {
for task in &module.tasks {
let key = format!("{}/{}", module.id, task.title);
match task.status.as_str() {
"done" => { current_done.insert(key); }
"blocked" => { current_blocked.insert(key); }
_ => {}
}
}
}
// 与上一条快照对比,生成 delta
let (newly_done, newly_blocked, unblocked): (Vec<String>, Vec<String>, Vec<String>) = {
let prev = root["snapshots"].as_array()
.and_then(|arr| arr.iter().max_by_key(|s| s["at"].as_str().unwrap_or("")));
if let Some(prev_snap) = prev {
let prev_done: std::collections::HashSet<String> = prev_snap["task_ids"]["done"]
.as_array().map(|a| a.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
.unwrap_or_default();
let prev_blocked: std::collections::HashSet<String> = prev_snap["task_ids"]["blocked"]
.as_array().map(|a| a.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
.unwrap_or_default();
let mut nd: Vec<String> = current_done.difference(&prev_done).cloned().collect();
let mut nb: Vec<String> = current_blocked.difference(&prev_blocked).cloned().collect();
let mut ub: Vec<String> = prev_blocked.difference(&current_blocked).cloned().collect();
nd.sort(); nb.sort(); ub.sort();
(nd, nb, ub)
} else {
(Vec::new(), Vec::new(), Vec::new())
}
};
let mut done_ids: Vec<String> = current_done.into_iter().collect();
let mut blocked_ids: Vec<String> = current_blocked.into_iter().collect();
done_ids.sort(); blocked_ids.sort();
let snapshot = serde_json::json!({
"at": at,
"conventions_version": conventions_version,
@ -1596,7 +1694,7 @@ fn append_usage_snapshot(bp_dir: &Path, manifest: &BlueprintManifest, stats: &Bl
"planned": stats.planned,
"concept": stats.concept,
"blocked": stats.blocked,
"stalled": stalled_modules // in_progress 但无可推进任务的模块 id 列表
"stalled": stalled_modules
},
"tasks": {
"total": stats.total_tasks,
@ -1606,7 +1704,18 @@ fn append_usage_snapshot(bp_dir: &Path, manifest: &BlueprintManifest, stats: &Bl
"todo": tasks_todo,
"dispatched": stats.dispatchable
},
"blocked_reasons": blocked_reasons
"blocked_reasons": blocked_reasons,
// 当前任务状态快照,供下次 delta 计算使用
"task_ids": {
"done": done_ids,
"blocked": blocked_ids
},
// 与上一条快照的 delta首次快照为空
"delta": {
"newly_done": newly_done,
"newly_blocked": newly_blocked,
"unblocked": unblocked
}
});
if let Some(arr) = root["snapshots"].as_array_mut() {