From f4ff83d94e6726c071bc9413a510f8bafd3aa4f8 Mon Sep 17 00:00:00 2001 From: lanrtop Date: Thu, 2 Jul 2026 19:49:22 +0900 Subject: [PATCH] =?UTF-8?q?feat(governance):=20=E8=A7=84=E5=88=99=E5=88=86?= =?UTF-8?q?=E5=8F=91=E8=B7=A8=E5=B7=A5=E5=85=B7=E5=8C=96=E2=80=94=E2=80=94?= =?UTF-8?q?AGENTS.md=20=E6=88=90=E4=B8=BA=E8=93=9D=E5=9B=BE=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E5=8D=95=E4=B8=80=E6=9D=A5=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sync_blueprint_rules 改为双文件写入: - AGENTS.md:完整蓝图工作流 MANAGED 块(开放标准,Qwen/Cursor/Copilot 均读), 措辞去 Claude 专属化("Claude 应做的"→"AI 应做的",技能引用改为通用流程描述) - CLAUDE.md:MANAGED 块瘦身为 @AGENTS.md 薄引用(保留 version marker, 版本检测不受影响),内容永不分叉 - 抽 upsert_managed_block 公共 helper(created/updated/appended 三态), 附 tempdir 单元测试;SyncResult 新增 agents_md_action 背景:批量同步后发现所有项目只落 CLAUDE.md,跨工具内容被锁进单一工具 私有文件,违反宪章「跨工具单一来源 = AGENTS.md」。 下次批量同步时全部项目自动迁移到新结构(marker 机制天然幂等)。 Rules-Applied: R01, R07 --- .blueprint/modules/blueprint-governance.md | 9 ++ CLAUDE.md | 2 +- src-tauri/src/commands/blueprint.rs | 142 ++++++++++++++++----- src/lib/commands.ts | 1 + 4 files changed, 120 insertions(+), 34 deletions(-) diff --git a/.blueprint/modules/blueprint-governance.md b/.blueprint/modules/blueprint-governance.md index 914cc41..1550d90 100644 --- a/.blueprint/modules/blueprint-governance.md +++ b/.blueprint/modules/blueprint-governance.md @@ -74,3 +74,12 @@ progress: 100 - files: src/components/dashboard/BlueprintModal.tsx, src/lib/commands.ts - depends: blueprint-governance - acceptance: GovernancePanel 新增规则来源 badge(「内置基线 vX.X.X」或「用户自定义 vX.X.X」);新增「应用梦核建议」区域:文本框用于粘贴新 CONVENTIONS 内容 + 确认按钮,确认后调用 save_user_conventions,成功显示「已同步 N 个项目」;有用户版时显示「重置为基线」按钮,调用 reset_conventions_to_builtin 后来源恢复为内置 + +### ✅ 跨工具规则分发:AGENTS.md 单一来源 +- status: done +- complexity: M +- files: src-tauri/src/commands/blueprint.rs, src/lib/commands.ts +- acceptance: sync_blueprint_rules 将完整蓝图工作流 MANAGED 块写入 AGENTS.md(开放标准,Qwen/Cursor/Copilot 均读),措辞去 Claude 专属化;CLAUDE.md 的 MANAGED 块瘦身为 @AGENTS.md 引用(保留 version marker 使版本检测不受影响);两文件共用 upsert helper(created/updated/appended 三态),helper 有 tempdir 单元测试;SyncResult 新增 agents_md_action 字段;cargo test + typecheck 通过 + +> 背景:批量同步后用户发现所有项目只落了 CLAUDE.md——跨工具内容被锁进单一工具私有文件,违反宪章「跨工具单一来源 = AGENTS.md」。 + diff --git a/CLAUDE.md b/CLAUDE.md index da988c9..89a129b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,7 +47,7 @@ docs/templates/ 炼境接入包模板草案(agents-meta-rules - 代码重构/简化:/simplify → /code-review - 发现深层问题:/Dev-deep-think - + ## 项目蓝图 本项目使用 `.blueprint/` 目录维护项目全景架构图,驱动需求讨论和任务管理。 diff --git a/src-tauri/src/commands/blueprint.rs b/src-tauri/src/commands/blueprint.rs index 7be8f9c..41c6cc6 100644 --- a/src-tauri/src/commands/blueprint.rs +++ b/src-tauri/src/commands/blueprint.rs @@ -84,13 +84,14 @@ fn parse_frontmatter_version_str(content: &str) -> Option { None } -/// CLAUDE.md 蓝图管理区模板,版本号使用当前生效的 CONVENTIONS 版本。 -fn claude_managed_template() -> String { +/// AGENTS.md 蓝图管理区模板(跨工具单一来源,措辞工具无关),版本号使用当前生效的 CONVENTIONS 版本。 +fn agents_managed_template() -> String { let ver = effective_conventions_version(); format!(r#" ## 项目蓝图 本项目使用 `.blueprint/` 目录维护项目全景架构图,驱动需求讨论和任务管理。 +本节适用于任何 AI 编程工具(Claude / Qwen / Cursor / Copilot 等)。 ### 对话开始时必须执行 @@ -99,7 +100,7 @@ fn claude_managed_template() -> String { ### 需求讨论 → 蓝图同步规则 -| 用户说的 | Claude 应做的 | +| 用户说的 | AI 应做的 | |----------|--------------| | "我想加个 XX 功能" | 确认需求后:manifest 新增模块(status: concept),创建 `.blueprint/modules/.md` | | "XX 功能需要 A、B、C" | 在模块文件中拆分为任务卡 | @@ -112,9 +113,9 @@ fn claude_managed_template() -> String { | complexity | 执行流程 | |-----------|---------| | S / M | 直接拆任务卡,确保每张卡有 `files` + `acceptance` + `complexity` → 标记 📋 | -| L(复杂系统设计) | 先 `/architect` 输出设计文档,用户确认后 `/splitter` 拆成 S/M 任务卡 | +| L(复杂系统设计) | 先做架构设计(Claude 用 `/architect` 技能,其他工具按 CONVENTIONS「复杂功能分析流程」章节执行)输出设计文档,用户确认后拆成 S/M 任务卡 | -> L 级任务禁止直接写代码,必须先经过 `/architect` → `/splitter` 流程。 +> L 级任务禁止直接写代码,必须先经过 架构设计 → 任务拆解 流程。 ### 任务执行两阶段提交 @@ -125,6 +126,22 @@ fn claude_managed_template() -> String { "#) } +/// CLAUDE.md 蓝图管理区模板:薄引用,指向 AGENTS.md 单一来源。 +/// 保留 version marker 使 get_blueprint_status 的版本检测继续工作。 +fn claude_managed_template() -> String { + let ver = effective_conventions_version(); + format!(r#" +## 项目蓝图 + +蓝图工作流规则统一维护在 @AGENTS.md 的 BLUEPRINT_MANAGED 区(跨工具单一来源,勿在此复制)。 + +对话开始时必须执行: +1. 读取 `.blueprint/manifest.yaml` 了解项目全景和当前状态 +2. 读取 `.blueprint/CONVENTIONS.md` 了解完整工作流规则 +3. 遵循 AGENTS.md 中的蓝图工作流(需求→蓝图同步、复杂度决策、两阶段提交) +"#) +} + // ── manifest.yaml 对应结构 ─────────────────────────────────────────────────── #[derive(Debug, Serialize, Deserialize, Clone)] @@ -1049,13 +1066,41 @@ pub struct SyncResult { pub success: bool, pub conventions_written: bool, pub claude_md_action: String, // created | appended | updated | skipped + pub agents_md_action: String, // created | appended | updated pub message: String, } +/// 在指定文件中写入/替换 BLUEPRINT_MANAGED 块: +/// 文件不存在 → 用 header + 模板创建;已有 MANAGED 区 → 原位替换;否则追加。 +/// 返回动作:created | updated | appended +fn upsert_managed_block( + path: &Path, + template: &str, + header_when_created: &str, +) -> Result { + let label = path.file_name().and_then(|n| n.to_str()).unwrap_or("file"); + if !path.exists() { + let content = format!("{header_when_created}\n\n{template}\n"); + std::fs::write(path, content).map_err(|e| format!("创建 {label} 失败: {e}"))?; + return Ok("created".to_string()); + } + let content = std::fs::read_to_string(path).map_err(|e| format!("读取 {label} 失败: {e}"))?; + if content.contains(" +内容A +"; + let tpl_v2 = " +内容B +"; + + // 1. 不存在 → created,含 header + let a = upsert_managed_block(&f, tpl_v1, "# 头部").unwrap(); + assert_eq!(a, "created"); + let c = std::fs::read_to_string(&f).unwrap(); + assert!(c.starts_with("# 头部")); + assert!(c.contains("内容A")); + + // 2. 已有 MANAGED 区 → updated(原位替换,header 保留,旧内容消失) + let a = upsert_managed_block(&f, tpl_v2, "# 头部").unwrap(); + assert_eq!(a, "updated"); + let c = std::fs::read_to_string(&f).unwrap(); + assert!(c.contains("内容B") && !c.contains("内容A")); + assert_eq!(c.matches("BLUEPRINT_MANAGED_START").count(), 1); + + // 3. 存在但无 MANAGED 区 → appended(既有内容保留) + let g = dir.join("CLAUDE.md"); + std::fs::write(&g, "# 手写内容 + +自定义段落").unwrap(); + let a = upsert_managed_block(&g, tpl_v1, "# 不该出现").unwrap(); + assert_eq!(a, "appended"); + let c = std::fs::read_to_string(&g).unwrap(); + assert!(c.contains("自定义段落") && c.contains("内容A") && !c.contains("不该出现")); + + let _ = std::fs::remove_dir_all(&dir); + } } + diff --git a/src/lib/commands.ts b/src/lib/commands.ts index d7083c2..90e8c50 100644 --- a/src/lib/commands.ts +++ b/src/lib/commands.ts @@ -869,6 +869,7 @@ export interface SyncResult { success: boolean; conventions_written: boolean; claude_md_action: string; + agents_md_action: string; message: string; }