feat(onboard/parity): 写入汇 WriteSink 与写入前计划 plan_onboarding,处置独立审查(P8c) #36

Merged
lanrtop merged 6 commits from feat/agent-client-parity-p8c into master 2026-10-03 09:59:15 +08:00
Showing only changes of commit 04a0069363 - Show all commits

View File

@ -249,13 +249,20 @@ skill 只是五个接入面之一,兼容层覆盖全部五面。
- mcp_verify: "2026-10-03 PR #33 合并(ddd729c)、运行中的炼境重编译生效后:verify_onboarding(enterprise) 的 clients 三端均 converged;连续两次 inject_mcp 回执三端均「已一致(未改动)」,耗时 26 ms、112 ms,.lian-jing/context.json、.codex/config.toml、.claude/settings.json、.claude/liangjing.json 四个文件 mtime 不变,git status 前后均干净。"
- known_limits: "WSL 项目已有的 Claude local 托管条目在端口变更后不再刷新(重注入对 WSL 记 not_applicable),而 remove_project_mcp 与 on_project_root_changed 对 WSL 根仍会起 CLI 清理——口径不完全一致,WSL 路径项目已按 2026-07-04 裁决放弃,暂不处理。Codex 托管块已存在时 upsert 不检查块外手写同名表(存量问题),只读判定会报 needs_human 提醒人工清理。注册表与适配器的端到端组合(缺 CLI 时其余两端照常)只在单元层分别验证,未做真实客户端的组合实测。"
### 📋 P8c — 写入前计划:WriteSink + plan_onboarding
- status: todo
### 🔵 P8c — 写入前计划:WriteSink + plan_onboarding
- status: in_progress
- complexity: M
- files: src-tauri/src/onboard/plan.rs, src-tauri/src/lib.rs, src-tauri/src/commands/onboarding.rs, src-tauri/src/commands/blueprint.rs, src-tauri/src/mcp_inject.rs
- depends: P8b
- acceptance: "agent_verify: [cargo test onboard::plan] 临时 git 仓库:空仓库做 plan 全部是 create、目录快照前后字节一致;已接入仓库连续两次 plan 全部 noop、digest 相同,任一目标文件变化 digest 就变;块外有未提交改动时该文件带预警且 plan.blocked 非空;内容相同仅行尾为 CRLF 判 noop;超长 diff 截断并标注;context.json 是别的 project_id 时记 blocked(identity_conflict);machine_changes 列出各本机步骤的预计动作,计划阶段假 claude CLI 只收到 mcp get。[cargo test] 原有 onboarding、blueprint、mcp_inject 测试全部通过、行为不变。"
- 说明:WriteSink 以 write_if_content_changed 作为真实写入汇;P10 的混合文件检测前移到写入之前;与 CI 的 plan/apply 同一模式(Q2)。
- 实现(2026-10-03,分支 feat/agent-client-parity-p8c,提交 ef2c7fd):lib.rs 新增 WriteSink trait(read/write,内容忽略行尾未变不写)与 DiskSink(真实写入,父目录按需创建)。接入包模板分发(dispatch_templates_with)、蓝图规则同步(sync_blueprint_rules_with)、项目身份与 MCP 投影(write_project_mcp_files_with:context.json、settings.json 兼容条目、liangjing.json、Codex 托管块)全部改经写入汇,原入口改为 DiskSink 薄包装。新增 onboard/plan.rs:PlanSink 只记录不落盘,后续步骤读到前面步骤的待写内容(AGENTS.md 先后被模板分发与蓝图规则改写,计划与实际写入算出同一结果);write_project_files 为 plan 与 apply(P8d)共用的写入序列;plan_onboarding 给出逐文件 create/update/noop、有上限的 diff(60 行)、混合文件预警(P10 前移到写入前,记 blocked(mixed_file))、身份冲突、手写 Codex 条目与本机 manual_kept/other_project/needs_human 阻断项,并对(项目 ID、逐文件路径/动作/内容哈希、本机步骤/状态、阻断项)算 sha256 digest。mcp_inject::plan_onboarding 为生产入口:本机侧预计动作 token(读受保护存储判断有无)、shim(比对稳定入口内容)、claude/codex/dsh(复用 P8b 的只读 client_static_status)、codex_trust(只读解析 ~/.codex/config.toml 的 [projects.'<路径>'] trust_level,含祖先目录)。2026-10-03 合并 master(PR #34 重复登记修复 U1/U2,同改 mcp_inject.rs、commands/onboarding.rs、mcp_tools.rs)自动合并无冲突。收尾补测试 plan_predicts_exactly_what_apply_writes(计划的文件集合与内容哈希 = 随后 DiskSink 实际写入,git status 文件集合一致),修正 mcp_inject::plan_onboarding 注释「不起子进程」为「不起客户端 CLI,混合文件预警会起只读 git」。
- verification: "2026-10-03:合并 master 后全量 cargo test 310 passed / 0 failed / 5 ignored(ef2c7fd 时 307,PR #34 带入 3 条);补测试后 311 passed / 0 failed / 5 ignored,编译 0 warning。onboard::plan 7 条:空仓库全 create 且目录快照(含 .git)字节不变;apply 后连续两次计划全 noop、digest 相同,本机动作变化与目标文件变化 digest 都变;CRLF 检出同内容判 noop;混合文件带 warning 且 blocked(mixed_file)、context.json 属别的项目记 blocked(identity_conflict);手写 Codex 条目记 manual_entry、本机 other_project 记 shared_key;PlanSink 覆盖读与改回撤销;diff 截断与标注;计划=实际写入。mcp_inject 新增 codex_trust_reads_projects_table_including_ancestors。变异:PlanSink::read 忽略待写内容 → plan_predicts_exactly_what_apply_writes(AGENTS.md 哈希不符)与 plan_sink_overlays_pending_writes_and_reverts_to_noop 失败,原有 5 条 plan 测试仍通过(补测试前此变异只被单元测试捕获)。"
- 自查(对照 acceptance 逐条):空仓库全 create + 快照字节一致 ✅;两次 plan 全 noop、digest 相同 ✅;任一目标文件变化 digest 变——测试以 capability-packs.json 一个文件为代表,并另测本机动作变化 ✅;块外未提交改动带预警且 blocked 非空 ✅;CRLF 判 noop ✅;超长 diff 截断并标注——在 bounded_diff 单元层测试,未经 plan 端到端构造超长差异 ✅;identity_conflict ✅;machine_changes 列出各本机步骤 ✅(plan_machine_steps 依赖真实凭据存储与用户目录,无注入点,未做单元测试,只经 codex_config_trusts 与 P8b 的只读判定测试间接覆盖);原有 onboarding、blueprint、mcp_inject 测试全部通过 ✅。
- 有意偏差:① 计划阶段完全不起 claude CLI(Claude 只读 ~/.claude.json 判定,沿用 P8b 的 static_status),比 acceptance「假 claude CLI 只收到 mcp get」更严;因此没有假 CLI 断言,由结构保证(static_status 路径无 CLI 调用)。② 文件动作只有 create/update/noop,设计文档 §1 的 append/skip 合并为 update/noop(acceptance 只要求 create/noop)。③ DiskSink 与 write_if_content_changed 同语义但未直接复用:另加按需建父目录,且读失败(非「不存在」)时报错而不是覆盖写。
- 自查发现(留待独立审查处置,本次未改代码):① 行为变化——dispatch_one 原以 target.exists() 判「已存在跳过」,现经 sink.read 判定,已存在但非 UTF-8(如 UTF-16/GBK 的 lefthook.yml、mise.toml)由 skipped 变 failed,plan 中成为 needs_human 阻断;全托管文件读失败由覆盖写变 failed(更安全)。② 设计 §1 要求计划单列 bypassPermissions 默认值(P8 决策 Q4、P8g 视图需要),当前只体现在 .claude/settings.json 的 create 概要里,未单列;本机变更清单也还没有产品组与 buff、.git/hooks(lefthook install)两步——宜在 P8d 编排时补进 machine_changes。③ codex_config_trusts 只认 [projects.'…'] 段头,TOML 基本字符串里的转义反斜杠(如 `[projects."D:\\Work"]`)与内联表写法不识别(本机 Codex 实际写的是字面量字符串小写路径,已核对 13 个段头均为此格式)。
- T1:plan_onboarding 目前无 MCP / Tauri 投影(P8e 才加),输出尚不是外部契约,docs/ai-context/project-context.yaml 未改;P8e 的 acceptance 已含「project-context.yaml 补 plan、digest 与状态语义」。
- 待办:独立只读审查与修复、开 PR(M 级,由统筹会话安排);合并后标 ✅。
### 📋 P8d — onboard_project 编排本体
- status: todo