diff --git a/.blueprint/manifest.yaml b/.blueprint/manifest.yaml index bd38a52..ea048cb 100644 --- a/.blueprint/manifest.yaml +++ b/.blueprint/manifest.yaml @@ -6,7 +6,7 @@ description: > 项目自身保留业务规则、测试、部署声明与交付决策。炼境本身亦被纳入其中, 形成可自我进化的开发智能闭环。 iteration: 1 -updated: 2026-10-02 +updated: 2026-10-03 agent_button_carrier: tauri @@ -376,7 +376,7 @@ modules: progress: 100 note: "体系分发主线全卡完成:…/V1/U3/B4/U1 ✅(2026-07-15 择升环三断点修复收官:U3 规则校验 / B4 沉默项目信号 / U1 全托管升级+批量重分发)。U1 批量模式 MCP 实调待重建安装后终验,通过后模块收官 done" position: [3500, 0] - updated: 2026-07-15 + updated: 2026-10-03 - id: gitea-integration name: Gitea 集成 diff --git a/.blueprint/modules/onboarding-pack.md b/.blueprint/modules/onboarding-pack.md index 95d469e..0f9d4da 100644 --- a/.blueprint/modules/onboarding-pack.md +++ b/.blueprint/modules/onboarding-pack.md @@ -168,6 +168,14 @@ - acceptance: agent_verify: [文件] AGENTS.md.tmpl「agent 按钮与验证路径」节含「同源同边界」「操作序列 agent 皆可复刻」「三分定位」表述,MANAGED 版本 1.6.1→1.7.0;炼境自身 AGENTS.md 块内同步;cargo test onboarding 通过 - notes: 2026-08-23 多角度辩证收敛(v3→v3.1 外科手术版):真实增量仅三个半(先本体后投影时序/复刻+复现路径/三分定位启发式/飞轮转译轻写一句),其余为已有六条重述不重复注入;三分定位从"定律"降级"启发式"(crypto.randomUUID 环境层实锤:本体投影都没病,病在环境);原型探索豁免时序约束;飞轮转译不承诺机制(采集通道挂 governance-feedback-loop 缺口①) +### ✅ F1. 托管模板指纹测试:改内容必须升版本(文档约束 → 机器校验) +- status: done +- done_note: 2026-10-03 完成。tests 新增 managed_template_fingerprints_track_versions + 「版本号 → 摘要」登记表 TEMPLATE_FINGERPRINTS 三项:resources/onboarding/** 全部资源(递归遍历,新增模板文件自动纳入)↔ BASE_ONBOARDING_PACK_VERSION;AGENTS / git-workflow 管理块 ↔ 块首 version。摘要前 CRLF→LF,段间 \0 分隔;Python 直接对 git index LF blob 独立复算三项摘要与 Windows CRLF 工作区结果一致。报错区分「内容变了版本没升」与「版本已升待更新指纹」,并给出可直接粘贴的新登记行。BASE 与模板 capability-packs.json 相等复用既有 check_capability_packs 断言(补可读报错)。自验:cargo test --locked onboarding 22 全过;envoy.md 改一字 → 仅 pack 项失败(其余 21 测仍全过,即旧测试抓不到该漏洞);AGENTS 块内改一字 → pack + AGENTS 两项失败;均已还原(envoy.md sha256 前后一致)后全绿。侦察佐证:dbc1b06(2026-09-23)改了 git-workflow 管理块内容未升 1.7.0,文档规则在 07-06 之后仍被漏过。**合入前实证更新流程**:rebase 到并行合入的 PR #28(envoy/SKILL/AGENTS 模板改动,规范升 BASE 1.7.0、AGENTS 块 1.10.0)后测试按设计报「版本已升,更新指纹常量即可」并给出新登记行(git-workflow 未动不报),照报错更新登记表后 22 全过、全量 cargo test 267 过 +- complexity: S +- files: src-tauri/src/commands/onboarding.rs, .claude/rules/onboarding-sync.md +- acceptance: agent_verify: [cargo test] `cd src-tauri && cargo test --locked onboarding` 全绿;临时改 resources/onboarding/.claude/commands/envoy.md 一个字 → 指纹测试失败且报错含「升 BASE_ONBOARDING_PACK_VERSION 与 capability-packs.json 版本,并更新指纹常量」,还原后通过;摘要前 CRLF→LF 归一化(工作区 autocrlf=CRLF 与 CI LF 同摘要) +- notes: 全托管文件(envoy.md / SKILL.md)按内容覆盖,但 verify 只看 capability-packs 版本(check_skill 仅子串检查)——只改内容忘升 BASE_ONBOARDING_PACK_VERSION 时 verify 永远 ready、无人重跑分发,子项目收不到更新。onboarding-sync.md 的升版规则此前只有文档约束(2026-07-06 块版本漏升实锤)。指纹表同时钉住 AGENTS / git-workflow 的 ONBOARDING_MANAGED 块版本(同类漏洞:块版本不升 → dispatch_versioned 全部 skipped) + ## 复盘笔记 【复盘】U1 全托管文件升级通道 + 批量重分发(2026-07-15) diff --git a/.claude/rules/onboarding-sync.md b/.claude/rules/onboarding-sync.md index 2ab2140..bd626ee 100644 --- a/.claude/rules/onboarding-sync.md +++ b/.claude/rules/onboarding-sync.md @@ -5,4 +5,6 @@ globs: src-tauri/resources/onboarding/** **改模板内容必须同步升级 `ONBOARDING_MANAGED_START version="x.y.z"` 版本号**——版本是分发引擎唯一的升级信号,内容变了版本不动,批量分发会全部 skipped(2026-07-06 实测踩坑)。模板改完后跑 `cargo test onboarding` 确认版本相关测试通过。 +该规则已由指纹测试 `managed_template_fingerprints_track_versions`(`onboarding.rs`)机器校验:`resources/onboarding/**` 任何内容变化须升 `BASE_ONBOARDING_PACK_VERSION` 与模板 `capability-packs.json` 版本,管理块内容变化另须升块首 version,再按测试报错把新摘要填回 `TEMPLATE_FINGERPRINTS`。 + 分发到子项目需要**重建安装炼境**(模板打包在二进制资源里),再调 `apply_onboarding_pack`。 diff --git a/src-tauri/src/commands/onboarding.rs b/src-tauri/src/commands/onboarding.rs index 6bd63eb..d454beb 100644 --- a/src-tauri/src/commands/onboarding.rs +++ b/src-tauri/src/commands/onboarding.rs @@ -1263,7 +1263,12 @@ mod tests { assert_eq!(unavailable.state, VerifyState::Unavailable); let pack_ready = check_capability_packs(Some(TMPL_CAPABILITY_PACKS)); - assert_eq!(pack_ready.state, VerifyState::Ready); + assert_eq!( + pack_ready.state, + VerifyState::Ready, + "模板 capability-packs.json 的版本须等于 BASE_ONBOARDING_PACK_VERSION / TOOLCHAIN_PACK_VERSION:{}", + pack_ready.detail + ); let pack_old = check_capability_packs(Some(r#"{"schema_version":0,"packs":[]}"#)); assert_eq!(pack_old.state, VerifyState::VersionMismatch); @@ -1442,6 +1447,131 @@ mod tests { ); } + // ── 托管模板指纹:改内容必须升版本(onboarding-sync.md 升版规则的机器校验)── + + /// 版本号 → 摘要登记表:(指纹对象, 登记版本, sha256)。 + /// - `resources/onboarding/**`:全部接入包资源,对应 BASE_ONBOARDING_PACK_VERSION—— + /// verify 只凭 capability-packs.json 的版本判落后,全托管文件(envoy/SKILL)的内容不参与比对 + /// - `<模板>#ONBOARDING_MANAGED`:版本化块,对应块首 version——dispatch_versioned 唯一的升级信号 + /// + /// 改了模板:按测试报错升对应版本,再把报错给出的新摘要填回这里。 + const TEMPLATE_FINGERPRINTS: [(&str, &str, &str); 3] = [ + ( + "resources/onboarding/**", + "1.7.0", + "0c39acd7c0513a1063ea0c2de5da06ba2baa69b7eb85b167499859ab6ad0eb45", + ), + ( + "AGENTS.md.tmpl#ONBOARDING_MANAGED", + "1.10.0", + "59b29e99c1b2184732dbf828eb3f2c8d3bb5afdfdbd30fd5d967daced82313e5", + ), + ( + "git-workflow.md.tmpl#ONBOARDING_MANAGED", + "1.7.0", + "9d52231fd725b786ad4d2bf874e21c155150b4df5a115e3b8facf23b6c23c255", + ), + ]; + + /// sha256 hex。先把 CRLF 归一化为 LF:Windows autocrlf 工作区是 CRLF,CI 检出是 LF。 + /// 段间加 \0 分隔,内容在相邻段之间挪动也会改变摘要。 + fn lf_sha256<'a>(parts: impl IntoIterator) -> String { + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + for part in parts { + hasher.update(part.replace("\r\n", "\n").as_bytes()); + hasher.update([0u8]); + } + hasher + .finalize() + .iter() + .map(|byte| format!("{byte:02x}")) + .collect() + } + + /// 递归收集 (相对路径, 内容)——新增模板文件无需登记,自动纳入指纹。 + fn collect_resource_files(root: &Path, dir: &Path, out: &mut Vec<(String, String)>) { + for entry in std::fs::read_dir(dir).unwrap() { + let path = entry.unwrap().path(); + if path.is_dir() { + collect_resource_files(root, &path, out); + continue; + } + let relative = path + .strip_prefix(root) + .unwrap() + .to_string_lossy() + .replace('\\', "/"); + let content = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("读取模板 {relative} 失败: {e}")); + out.push((relative, content)); + } + } + + #[test] + fn managed_template_fingerprints_track_versions() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("resources/onboarding"); + let mut files = Vec::new(); + collect_resource_files(&root, &root, &mut files); + files.sort(); + let pack_digest = lf_sha256( + files + .iter() + .flat_map(|(path, content)| [path.as_str(), content.as_str()]), + ); + let block = |template: &'static str| { + let block = extract_managed_block(template).expect("模板须含 ONBOARDING_MANAGED 块"); + ( + block_version(block).expect("管理块首须有 version"), + lf_sha256([block]), + ) + }; + let (agents_version, agents_digest) = block(TMPL_AGENTS); + let (workflow_version, workflow_digest) = block(TMPL_GIT_WORKFLOW); + let actual = [ + ( + BASE_ONBOARDING_PACK_VERSION, + pack_digest, + "改了托管模板请升 BASE_ONBOARDING_PACK_VERSION 与 capability-packs.json 版本,并更新指纹常量", + ), + ( + agents_version, + agents_digest, + "改了 AGENTS.md.tmpl 管理块请升块首 version(不升则存量项目重跑接入包全部 skipped),并更新指纹常量", + ), + ( + workflow_version, + workflow_digest, + "改了 git-workflow.md.tmpl 管理块请升块首 version(不升则存量项目重跑接入包全部 skipped),并更新指纹常量", + ), + ]; + + let stale: Vec = TEMPLATE_FINGERPRINTS + .iter() + .zip(&actual) + .filter(|((_, version, digest), (now_version, now_digest, _))| { + *version != *now_version || *digest != *now_digest + }) + .map(|((name, version, _), (now_version, now_digest, hint))| { + let (advice, next_version) = if version == now_version { + (*hint, "<新版本>") + } else { + ("版本已升,更新指纹常量即可", *now_version) + }; + format!( + "- {name}:登记 v{version},当前 v{now_version}——{advice}。\n TEMPLATE_FINGERPRINTS 改为 (\"{name}\", \"{next_version}\", \"{now_digest}\")" + ) + }) + .collect(); + let hashed: Vec<&str> = files.iter().map(|(path, _)| path.as_str()).collect(); + assert!( + stale.is_empty(), + "托管模板内容与登记版本不符:\n{}\n(resources/onboarding/** 参与摘要的文件:{})", + stale.join("\n"), + hashed.join(", ") + ); + } + #[test] fn skill_is_projected_to_codex_and_claude_from_one_source() { let base = std::env::temp_dir().join(format!(