三角度方案(最小编排 / 收敛流水线 / 本体收口)并行设计 + 综合评审,设计文档入库 docs/tasks/2026-10-03-p8-onboarding-oneclick-design.md(16 条已核实代码事实、风险 R1–R18、决策 Q1–Q11)。用户以 /goal 授权按推荐方向代为推进,11 项全部采纳推荐项。 - 主方案:同步、零模型的最小编排本体 + 可选异步探针;嫁接 plan/digest 写前确认、 经真实 shim 的认证链重放、「已接入」单一判定、客户端适配器注册表 - A1 不前置:P8/P4 载体都在炼境进程内,离线写入会绕过进程内锁且有 MSIX 重定向 风险,真正耦合是全局 pool() 而非 crate 边界;A1 改为 A1a(随 P8b 的适应度测试)、 A1b(按需 CLI 薄客户端)、A1c(触发式拆 crate,四个触发条件) - P4 分 L0 静态 / L1 零模型 / L3 无头会话;P8 只依赖 L1(P4a) - 验收口径变更(Q1,报备):P8 由「三端无头 A–F 矩阵」改为「L1 零模型矩阵 + 可选 L3 结果及是否过期」,理由写在 P8 卡 - P6 依赖 P8c(Q3);P4b 排在 A4 之后(Q8);manifest 清除「P4/P8 依赖 A1」 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
42 KiB
P8 一键接入 与 P4 三端探针:设计决策(2026-10-03)
决策状态:2026-10-03 由三角度方案(最小编排 / 收敛流水线 / 本体收口)并行设计加综合评审产出。用户以 /goal 授权「按推荐方向代为推进」,§9 的 11 个问题全部采纳推荐项(已勾选),用户可随时推翻。其中 Q1 属于修改验收口径,理由见 §9 Q1 与 agent-client-parity P8 卡。 蓝图落点:agent-client-parity(P8-0、P8a–P8g、P4-0、P4a–P4e)、architecture-evolution(A1a/A1b/A1c)。下文「本会话」指设计评审会话(只读调研,未运行客户端)。
这次是只读调研,没有改任何文件,也没有提交。本会话 lian-jing MCP 连接失败(ECONNREFUSED),所以没调 get_diagnostics。下文结论都来自读代码、读蓝图和读本机探针素材,凡是没实测过的地方都标了「待实测」。
0. 评审结论
- 主方案选方案一(最小编排本体:先做同步、零模型的接入,探针做成可选的异步任务,A1 不前置)。它离现有代码最近,能最快交付,而且不改接入包模板,所以不会触发全部子项目重新分发。
- 嫁接方案二:写入前的 plan + digest 确认;用真实稳定入口重放认证链,做不调模型的检查;判定以客观证据为准,对失败做归因;把 bypassPermissions 默认值和 Codex 信任单列为人确认点;先做 spike 再写代码;断言探针无副作用。
- 嫁接方案三:统一「已接入」的判定;客户端适配器注册表;架构适应度测试(让 A1-ready 由测试校验);矩阵每格标证据等级;L3 只在 canary 项目、指纹变化时跑,并按指纹缓存;A1 拆成三段。
- 不采纳:
- 方案二的 onboarding_runs 表和心跳锁。收敛式设计不需要运行状态,v1 用 runtime_logs 就够。
- 方案二「首次接入默认跑 L3」。
- 方案三「每天对全部项目跑 L1」,因为每个项目都要起一次 claude CLI,噪声大。
- 方案三「Rust console CLI + skill 降级 + 接入包升版」,范围膨胀,要求全部子项目重新分发。
- 方案一的「只靠 --allowedTools 收窄 Claude」,因为项目 settings 默认是 bypassPermissions,见风险 R8。
1. 已核实的代码事实(三份方案的关键论断逐条核过,另有补充)
| # | 事实 | 位置 |
|---|---|---|
| F1 | inject_project_mcp 返回 Result<(),String>。sync_claude_local 返回 (),refresh_dsh_patch 失败只写日志。Claude 和 DSH 两端的成败没有结构化结果 |
mcp_inject.rs:591, 1053, 1254 |
| F2 | ensure_standalone_group 只在项目还不在任何组里时才注入。已在组内的项目重跑接入包,不会修复本机注册 |
groups.rs:147-198 |
| F3 | 「已接入」有三套判定:reinject_all_mcp 按组成员(project_group_map),DSH 白名单和根目录迁移按 token,list_all_projects 按 .claude/liangjing.json 是否存在 |
mcp_inject.rs:1417, 806, 1476;mcp_tools.rs:2058 |
| F4 | apply_onboarding_pack 是同步的 #[tauri::command],内部直接调用 buff,再到建组、注入,可能起 claude CLI(单次超时 20 秒),会卡界面 |
onboarding.rs:471 |
| F5 | create_project、batch_create_projects、create_project_from_template 只写 DB,不会触发接入。MCP 里也没有登记项目的工具,所以 agent 复刻不了「新建 → 接入」这条人的操作路径 |
projects.rs:182 |
| F6 | MCP 路径下 verify_onboarding 的运行态(server_observed_runtime_status)把 reachable/handshake 写死为 Ok,工具名取的是全局清单。UI 路径的 collect_runtime_evidence 会真做一次 loopback 握手,但不带认证、也不经过 shim,覆盖不到 F 项 |
settings.rs:561-584, 469-558 |
| F7 | verify_onboarding 包含「origin 指向 Gitea」「CI workflow 存在」「飞轮 commit 数据流入」三项,新项目必然不通过。所以一键接入的「完成」不能定义为 verify 全过 |
onboarding.rs:1093-1151 |
| F8 | 注入用 or_insert 往项目 .claude/settings.json(受跟踪文件)写 permissions.defaultMode=bypassPermissions。一键接入会让这个默认值扩散到每个新项目 |
mcp_inject.rs:1281 |
| F9 | setup_gitea_repo 的 private 默认是 false,即创建公开仓库 |
mcp_tools.rs:388 |
| F10 | mcp_server.rs 已有 INITIALIZED_PROJECT_CLIENTS,记录项目路由的 initialize,并排除 lian-jing-readiness。这是服务端调用轨迹的雏形 |
mcp_server.rs:36-66 |
| F11 | run_claude_cli 超时只杀直接子进程,不杀进程树 |
mcp_inject.rs:505 |
| F12 | release 主 exe 是 windows_subsystem="windows",--lianjing-mcp-header 只能经管道使用 |
main.rs:2 |
| F13 | 内部用到 pool() 的地方约 293 处,分布在 43 个文件。真正的耦合在全局连接池和进程内静态锁(MCP_OP_LOCK、DSH_PATCH_LOCK),拆 crate 解决不了 |
grep 统计 |
| F14 | P1 中 Codex 用的是 workspace-write + approval never。P7 中 -s read-only 下的 MCP 调用被 approval 拦截。DSH 经 app.asar 内的 cli.js 加 ELECTRON_RUN_AS_NODE 启动,Codex 在带哈希的 bin 目录里 |
%TEMP%\probe2、lj-p7-probe |
| F15 | %TEMP% 下已有约 20 个手工探针目录(probe、probe2、probe3、lj-p7-probe、lj-p9-* 等),说明「每次重写探针」这种重复摩擦确实存在 | 本机 |
| F16 | inject_mcp 和 apply_onboarding_pack 的 schema 里有 project_id 参数,因此在项目路由上可见(子项目 agent 能对本项目调用) |
mcp_tools.rs:445 |
2. 三方案比较
| 维度 | 方案一 最小编排 | 方案二 收敛流水线 + 验证金字塔 | 方案三 本体收口 + A1 三段 |
|---|---|---|---|
| 管家准入 | 好:只组合现有本体,不改模板 | 好,但 L3 首次默认跑,额度消耗偏多 | 中:Rust CLI 加 skill 降级要升接入包版本,全部子项目重新分发 |
| 先本体后投影 | 好:onboard_project 本体,加 Tauri 和 MCP 两个投影 | 好:commands/onboard.rs 本体 | 最好:onboard/ 模块加适应度测试,由机器校验 |
| 写前确认 | 只有混合文件闸门 | plan、diff、digest | plan、diff、digest |
| P8 的验收证据 | 静态检查加运行态,但 MCP 路径的运行态是写死的(F6),偏弱 | L1 自握手经过真实 shim,强 | L1 配置回放加协议握手,强 |
| 「已接入」不一致 | 只列为风险 | 只列为风险 | 立卡修复 |
| 成本 | 最低,6 张核心卡 | 最高,10 张卡加运行记录表 | 中高,12 张卡加 console 二进制 |
| 与在途工作冲突 | 低 | P8b 与 P6 冲突 | P8-b1 与 P6 冲突,A1b 牵涉打包 |
| 明显缺陷 | --allowedTools 收窄不成立 | 运行记录、心跳过度设计 | 每天全量 L1 有噪声;CLI 范围膨胀 |
3. 目标与非目标
目标
- 新建或添加项目后,一次确认就完成以下各步,并用零模型的 L1 矩阵验收:canonical context,接入包(规则、skill 双投影、Codex 托管块、蓝图规则),本机三端注册(token、shim、Claude local、DSH 白名单和会话插件)。
- 收敛式幂等:重跑等于修复;稳态下零写入、文件 mtime 不变。
- 每一步都返回结构化状态;
unavailable和unknown不当作成功报出。 - 项目文件改动只报告、不提交;写入前可以预览(见 Q2)。
- P4:三端兼容性表可以自动重跑。L3 按需触发、结果可比对、带指纹。
- 一个本体、两个投影(Tauri async command 和 MCP),同源同边界。
非目标
- 不自动 commit 或 push。不自动建 Gitea 仓库、webhook、CI,不自动装工具链,这些只列进 next_actions。
- 不修改客户端的全局安全设置(Codex 信任表、Claude 全局 permissions),不覆盖手写的同名 MCP 配置。
- L3 不作为接入成功的门槛,也不作为 CI 闸门。不替 agent 判定完成,不替 agent 选模型。
- 不做离线 CLI 写入,不拆 core crate。
- P8 不改接入包模板。
- GUI 形态(DSH 桌面、Codex Desktop、Claude 桌面)不在自动验证范围,归 human_verify。
- 批量登记不自动接入。
4. 一键接入流程
入口分三种:
- UI 新建或模板创建项目成功后,弹出「接入预览」。
create_project本身保持没有副作用。 - 已有项目点「一键接入」。
- agent 在用户确认目标项目后,调用
plan_onboarding(project_id)。
0【自动·只读,约 1 秒】预检。 任一项不过就 failed,零写入。
- 项目已登记;根目录是存在的绝对路径;记录是否为 git 仓库以及 HEAD。
.lian-jing/context.json已有不同的 project_id 时,标blocked(identity_conflict)。- 控制面项目(
mcp_scope=global):Claude local 跳过,DSH 写停止哨兵。 - WSL 项目:Claude local 和 DSH 显式标
unsupported,不静默跳过。 - 探测客户端:claude 加 git-bash、codex(ClientLocator,支持设置里覆盖路径)、
~/.dsh;记录各端版本。 - 检查 MCP
/health可达。 - 检测炼境是否运行在 MSIX 包上下文中。
1【自动·只读,约 3 秒】生成计划(Q2 选 A 时)。
- 项目文件逐项给出动作(create/update/append/noop/skip)、有上限的 diff,以及混合文件预警(P10 检测前移到写入之前)。
- 本机变更清单:token、shim、Claude local(只读快路径判定,计划阶段只允许
claude mcp get)、DSH 托管块和插件、产品组和 buff、.git/hooks。 - 单列两项:
- bypassPermissions 默认值(F8);
- Codex 是否已信任该目录(取决于 P4-0 的 H-P8-1)。
- 生成
digest,覆盖项目文件新内容的哈希和本机动作清单。
【人确认 C1】应用前确认。
- UI:点「确认接入」。agent:必须先把计划给用户看,得到明确同意后,再带
expected_digest加confirmed=true调用。 - 有
blocked项(混合文件、身份冲突、手写同名条目、共享 Claude 项目键)时,确认按钮禁用,需要人逐项选「跳过 / 中止」。- 混合文件只能「先在子项目提交或丢弃块外改动,再重跑」,禁止字符串手术。
2【自动·写】应用。
- 先重算计划;digest 不符就
refused,零写入。 - 按依赖图执行,每步状态为
converged / applied / skipped / not_applicable / blocked(dep=…) / failed / needs_human:- 身份:context.json 和 liangjing.json 兼容投影。
- 项目文件:模板、蓝图规则、Codex 托管块、settings.json 兼容条目,都经 WriteSink 写入,内容不变不写。
lefthook install;缺可执行文件时记needs_human。- 产品组和 buff,作为兼容步骤保留。
- token,然后 shim,然后各客户端适配器:Claude local(经 claude CLI)、DSH 托管块和会话插件。手写同名配置记
manual_kept,不覆盖。
- 某步失败时,只把依赖它的步骤标 blocked,其他照常执行,整体记
partial。不做自动回滚:项目文件靠 git 撤销,本机配置由下次收敛修复,整体撤销走现有的remove_project_mcp。
3【自动·只读,约 5 到 10 秒】验收。
- L0:
verify_onboarding静态检查,加上新的clients字段。 - L1:零模型三端矩阵,见 §6。
- 「完成(done)」只按三端可用子集判定:身份、托管块版本、两份 skill、Codex 块、Claude local、DSH 白名单、lefthook、L1 适用格全部 ready。origin、CI、飞轮三项进入 next_actions,不算失败(F7)。
4【自动·只读】报告。
- 内容:分步状态,L1 矩阵(带证据等级),
git status --porcelain和diff --stat给出的待提交文件,断言 HEAD 没变,附一条建议的 commit message。 - 提示哪些已打开的会话需要重开。
- 给出最近一次 L3 结果,以及它是否已过期。
【人确认 C2,可选】L3 无头探针。 会消耗三端模型额度,约 2 到 3 分钟,默认不勾选。确认后以异步任务运行。
【人确认 C3】提交。 由人,或者经用户同意的子项目 agent,按子项目的 git 工作流提交。炼境不 add、不 commit。
【人确认 C4】next_actions。 机器只列出,不执行:
- Gitea 建仓:要提醒「工具默认建公开仓库」,再配 webhook。
- CI:
plan_cicd_workflow后由人确认,再apply_cicd_workflow。 - 工具链:由人确认后调用
prepare_toolchain(confirmed=true)。 - lefthook 可执行文件。
- 在 Codex 里信任该目录(如果 H-P8-1 成立)。
- 托管块变化后重启 DSH 桌面 GUI。
- Claude 交互模式首次打开目录时的 trust 确认。
【human_verify】 在 Claude Code、Codex、DSH 桌面 GUI 各新开一个会话,调用 read_project_test_account(name=LJ_PROBE_NONEXISTENT),返回「开发需求未登记」即为通过。
目标耗时(估算,未实测):plan 不超过 3 秒;首次 apply 不超过 30 秒(包含 claude CLI 写入);收敛后重跑不超过 10 秒(L1 里包含 claude mcp get 和 codex mcp get)。
5. A1 是否前置:结论是不前置,按触发条件后置
理由
- P8 和 P4 的验收载体是 Tauri command 和 MCP,本来就跑在炼境进程里。探针测的是「客户端连上运行中的炼境 MCP」,炼境离线时没有东西可测。所以「界面不运行也能用的 CLI」对它们没有用例。
- 离线写入有实际危害:
MCP_OP_LOCK、DSH_PATCH_LOCK和 credential store 的锁只在进程内有效,~/.claude.json(经 claude CLI)、~/.dsh/cordis.patch.yml、Credential Manager、SQLite 会被两个进程竞争写;- agent 从 MSIX 客户端里调用时,AppData 写入可能被重定向(P3 已实证)。
- 真正的耦合是全局
pool()(F13)和进程内锁,拆 crate 解决不了。A1 是 L 级,会和进行中的 A4、P6 大面积冲突,把 P8 推迟数周。 - 复用主 exe 做 CLI 还要处理控制台挂接和 single-instance(F12),那是 A1 自己要解决的设计问题。
P8 需要的是 A1 的原则,不是 crate 拆分。 建议把 A1 改写为三段:
- A1a(随 P8 落地):新代码统一放进
src-tauri/src/onboard/。约束如下:- 不 import
tauri::,不调用db::pool(,不写#[tauri::command]; - 连接、CredentialStore、ProcessRunner、ClientLocator 都由调用方注入;
- 结果一律是 serde 结构体。
这些约束由 cargo 架构适应度测试
onboard_module_is_projection_free机器校验。存量代码不扩散旧写法,旧入口按绞杀者模式逐步改成薄包装。
- 不 import
- A1b(CLI 薄客户端,按需):P4e 只做
scripts/probe-clients.mjs,作为全局/mcp的 MCP 客户端,不新增协议面。Rust console 二进制lianjing.exe和 skill 降级等真实需求出现再做,比如「会话启动时 MCP 没连上,需要第二通道」被证明是常态。 - A1c(core crate 拆分,触发式,L 级,届时先走 /architect):满足以下任一条就启动:
- 需要在没有 Tauri/WebView 的环境(例如 Linux CI)里跑本体;
- 确实需要离线写入,并且跨进程锁和包上下文检测已经具备;
- cargo test 的编译时延成为主要的反馈延迟;
- 出现第三个独立的消费者。
需要同步修正的蓝图位置:manifest 第 516 行「P4 依赖 A1」、第 525 行「P4/P8 依赖」、第 866 行注释,以及 A1「为什么」段里「P4/P8 可直接做成命令」的表述。这些改动归卡 P8-0。
6. P4 探针:定位与成本控制
定位。 P4 是三端兼容性表(Can I Use)的自动重跑器,也是 P8 的验收判官。分三层,越便宜的越先跑,前一层失败就不跑后一层:
| 层 | 内容 | 成本 | 何时运行 |
|---|---|---|---|
| L0 静态 | verify_onboarding,加上 clients 静态状态:读 ~/.claude.json 快路径、解析 DSH 块 |
毫秒级 | 每次 verify |
| L1 零模型 | ① 客户端自省:Claude 在项目目录执行 claude mcp get lian-jing(Local 条目、本项目地址、Connected 状态);Codex 执行 codex mcp get lian-jing --json(待 H-P8-1 确认,不可用时退回解析配置);DSH 解析白名单并校验插件哈希。② 认证链重放:按各客户端的方式执行稳定 shim(Claude/Codex 经 cmd /C,DSH 直接执行),拿到认证头后在项目路由上依次调用 initialize、tools/list(与 tools_list_result_for_project 自省值比对,不写死数字)、get_diagnostics(D)、list_project_dev_variables(E)、read_project_test_account(LJ_PROBE_<nonce>)(F)。A 和 B 用静态证据 |
约 5–10 秒,零额度 | 每次接入,以及显式调用 |
| L3 无头会话 | 三端同一只读任务、同一断言,判定以客户端事件流和服务端轨迹为准 | 2–3 分钟,消耗额度 | 只在显式触发时运行;建议在 canary(enterprise-system)项目上、指纹变化时跑 |
矩阵。 A–F 乘三端,另加全局项 G:无凭据泄露,且探针前后 git status 一致、没有非白名单的写调用。
证据等级。 每格标注证据等级,从弱到强为 static < client_introspection < protocol < session_event/server_trace;self_report 只作辅证,避免把 L1 的绿当成真实会话的绿。
格子状态。 取值为 pass / fail / unknown / not_installed / not_applicable / blocked / timeout。
按项目画像给期望值。
- 控制面项目:E、F 为 not_applicable,C 期望全局工具集。
- WSL 项目:DSH 为 unsupported。
L3 判定规则(来自方案二)。
- C–F 只认客观信号:
- Claude:init 事件里的 tools 和 skills;
- Codex:
--json输出中的 mcp_tool_call 事件; - DSH:
--json事件; - 服务端:轨迹中的 clientInfo、认证结果,以及 LJ_PROBE nonce。
- A:模型在不调用工具的前提下逐字摘录一句,摘录必须是规则文件磁盘文本的子串。
- 模型自报的工具数和条目数只记作 model_fidelity,不影响格子结论。
失败归因按三分定位,从环境到投影到本体:
- 环境:
client_not_installed、client_auth_or_quota(记 unknown,不记 fail)、timeout、env_package_context。 - 投影:
route_global、not_connected、codex_project_untrusted、rules_not_loaded、skill_not_visible、client_policy_blocked。 - 适配器:
adapter_drift,即客户端输出格式变了,需要升级炼境适配器。 - 模型:
model_noncompliant。
成本控制
- 从不自动运行。MCP 调用必须带
confirmed=true(与 prepare_toolchain 同一模式),描述里写明会消耗额度、需要 2–3 分钟。只在全局路由开放;项目路由只提供只读的get_client_parity。 clients参数可以只选部分端(例如只跑 DSH,约 15 秒)。L1 已判定未注册的一端直接跳过 L3。- 串行执行,顺序 DSH → Claude → Codex,快的先跑,便于早失败,也便于按时间窗归属。每端用独立的 nonce。
- 超时(P4-0 实测后校准):DSH 60 秒、Claude 210 秒(基线出现过 138 秒)、Codex 120 秒。超时后用 Windows Job Object 杀整个进程树(F11 证明只杀子进程不够)。
- 全机单飞锁:重复调用 start 返回同一个 run_id,这也防止探针会话里的 agent 递归调用。
- 缓存:缓存键由客户端版本、接入包版本、项目工具目录 digest、注入配置哈希、路由类型、probe_spec_version 组成。7 天内命中直接复用,
force=true时重跑。 - 不自动重试。
- 模型:默认用各客户端自己的默认模型;用户可以在设置里为探针单独指定更便宜的模型(这是用户的决定,不是炼境选模型)。
- 子进程卫生:
- 清掉
CLAUDECODE和CLAUDE_CODE_*环境变量,stdin 置空,设MCP_CONNECTION_NONBLOCKING=false和CLAUDE_CODE_GIT_BASH_PATH; - Claude 的收窄参数组合和 Codex 的沙箱/审批组合,都由 P4-0 实测后写死;
- 炼境运行在 MSIX 包上下文中时,报告标注「结果可能不代表真实客户端环境」。
- 清掉
- 产物:脱敏后存到
%USERPROFILE%\.lian-jing\probe-runs\<run_id>\,每个项目保留最近 5 次,不放 AppData。泄露扫描在炼境进程内用真实 token 比对,token 不经过 CLI 或 agent。 - 已知开销:Codex 单次约 3.06 万 token(P1 实测)。三端合计估计 4–8 万 token(估算,未实测)。
准入与退出。 五条准入都满足;F15 的约 20 个临时目录就是「消除重复」的证据。退出条件:三端都原生支持标准、且连续多个版本矩阵不变时,降低 L3 频率或撤销。
7. 任务卡清单(全部为 S/M)
依赖顺序
P8-0 ──> P8a ──> P8b ──> P8c ──┐
└────> P4-0 ─────────> P4a ──┴─> P8d ──> P8e ──> P8g
(P4a 也依赖 P8b) ^
P8f(可选,独立;P8e 端到端验收前完成)─────────┘
A4 合并 ──> P4b ─┐
P4-0 ──> P4c ────┼─> P4d ──> P4e(含 UI 探针按钮)
P4a ─────────────┘
P6 与 P8c 的先后见 Q3
P8 的关键路径不依赖 L3:P8-0 → P8a → P8b → P8c → P4a → P8d → P8e → P8g。
卡片
P8-0 蓝图与依赖修正(S)
- files:.blueprint/modules/agent-client-parity.md,.blueprint/modules/architecture-evolution.md,.blueprint/manifest.yaml
- acceptance:
- agent_verify:[YAML 解析] manifest.yaml 可解析;第 516、525 行的 note 和第 866 行注释不再写「P4/P8 依赖 A1」。[读文件] P8 的 depends 改为 P4a;acceptance 按 Q1 改写,并记录改口径的理由;P4 拆为 P4-0、P4a–P4e;A1 拆为 A1a/A1b/A1c,A1c 写明四个触发条件。
- human_verify:用户确认 Q1、Q2、Q10。
- 注:工作区里这两个模块文件、notes-archive、usage.json 有未提交改动(P6/P8/A4 的状态改为 in_progress),动手前先确认归属。
P4-0 探针前置实测(S,spike,零凭据,只能在主会话或后台任务里执行,不交给 Workflow 子 agent)
- files:.blueprint/modules/agent-client-parity.md(假设实测结果节),src-tauri/tests/fixtures/client_probe/(脱敏夹具)
- acceptance:agent_verify:[主会话 bash] 逐项给出结论并附原始日志路径。
- H-P8-1:在新建的未信任目录里,Codex 是否加载项目
.codex/config.toml;codex mcp get lian-jing --json能否反映项目配置和信任状态。 - H-P8-2:项目 settings 为 bypassPermissions 时,哪组参数(
--permission-mode/--tools/--allowedTools/--disallowedTools)能把可调用工具收窄到 lian-jing 只读三件加 Skill,同时 MCP 仍可调用。 - H-P8-3:Codex 在
-s read-only下 MCP 调用是否被拦截;如果被拦截,记录可用的最窄参数组合。 - H-P8-4:
claude -p读到 system/init 后立即终止,是否零模型调用。可行的话,Claude 的 B、C 两项并入 L1。 - H-P8-5:Codex MCP 工具调用的默认超时。
- H-P8-6:DSH headless 入口的稳定定位方法,以及
--json事件字段。 - 每端一份夹具(事件流、stderr、版本、参数);grep 夹具不含
Bearer和项目 token。任一假设不成立就回到设计,不硬编码绕过。
- H-P8-1:在新建的未信任目录里,Codex 是否加载项目
P8a 「已接入」单一判定(S)
- files:src-tauri/src/mcp_inject.rs,src-tauri/src/mcp_tools.rs(list_all_projects)
- acceptance:agent_verify:
- [cargo test mcp_inject] 用 in-memory DB 加假 CredentialStore:有 token、不在任何组的项目进入
reinject_all_mcp的目标集合;组成员仍在;token 已删的项目不在(锁内复查语义不变)。新函数的连接由调用方传入。 - [cargo test mcp_tools]
list_all_projects的「已注入」与 DSH 白名单、根目录迁移调用同一个函数:删除.claude/liangjing.json不改变判定,删除 token 后判为未注入。
- [cargo test mcp_inject] 用 in-memory DB 加假 CredentialStore:有 token、不在任何组的项目进入
P8b 客户端适配器注册表 + 结构化注入结果 + A1a 适应度测试(M)
- files:src-tauri/src/onboard/mod.rs(新增),src-tauri/src/onboard/clients.rs(新增,ClientAdapter trait 和 claude/codex/dsh 注册表),src-tauri/src/mcp_inject.rs(sync_claude_local 和 ensure_dsh_patch 改为返回结果枚举,保留日志,公开函数签名不变),src-tauri/src/commands/onboarding.rs(verify 增加
clients附加字段),src-tauri/src/mcp_tools.rs(inject_mcp 文案改为按实际结果),src-tauri/src/lib.rs,src/lib/commands.ts - acceptance:agent_verify:
- [cargo test onboard::clients] 注册表恰好列出三端,每端声明 detect、static_status、apply。sync_claude_local 的每条分支映射到一种 ClientOutcome(converged / applied / refreshed / manual_kept / other_project / unavailable{reason} / skipped{control_plane|wsl})。DSH 未安装记 not_installed;块外有手写条目记 manual_kept;缺 claude CLI 时 claude 记 unavailable,其余照常。原有 log_event 的类别和文案不变。
- [cargo test]
onboard_module_is_projection_free:src/onboard/ 下的源码不含tauri::、db::pool(、#[tauri::command]。 - [cargo test onboarding] 新增的
clients字段不改变 checks 计数和 VerifyState;控制面和 WSL 记 not_applicable,不拉低 pass。 - [MCP] 对 enterprise 连续两次 inject_mcp:第二次三端都是 converged,耗时不到 1 秒(未起 claude CLI),四个项目文件的 mtime 不变。
P8c 写入前计划:WriteSink + plan_onboarding(M,Q2 选 A 时做;选 B 时改为「混合文件闸门 needs_confirmation」,S)
- files:src-tauri/src/onboard/plan.rs(新增),src-tauri/src/lib.rs(WriteSink trait,以 write_if_content_changed 作为真实写入汇),src-tauri/src/commands/onboarding.rs(dispatch_* 改经写入汇;MixedFileGuard 前移),src-tauri/src/commands/blueprint.rs(sync_blueprint_rules 改经写入汇),src-tauri/src/mcp_inject.rs(context、Codex 块、settings、liangjing.json 改经写入汇)
- 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 测试全部通过,行为不变。
- [cargo test onboard::plan] 在临时 git 仓库里:
P4a L1 零模型三端检查(M)
- files:src-tauri/src/onboard/probe_l1.rs(新增),src-tauri/src/onboard/clients.rs(各适配器的 probe_l1),src-tauri/src/mcp_server.rs(识别
lian-jing-l1-probe并排除出 has_initialized_project_client),src-tauri/src/mcp_tools.rs(probe_client_parity,tier=l1),src-tauri/src/commands/onboarding.rs(async command),src/lib/commands.ts - acceptance:agent_verify:
- [cargo test onboard::probe_l1] 用进程内桩 MCP 服务加哑 shim:适配器取配置,按客户端方式执行 helper,再依次 initialize、tools/list、tools/call,C/D/E/F 均为 ready,每格带证据等级。shim 输出错误 token 时 F 记 auth_failed;shim 缺失记 shim_missing;端口不可达记 unreachable;客户端未安装记 not_installed,不计失败。期望工具集取自
tools_list_result_for_project。结果 JSON 和日志里不含桩 token。 - [MCP 全局]
probe_client_parity(enterprise, tier=l1)在 10 秒内返回,三端适用格 ready,并附版本号;对 dev-manager-tauri 调用时,Claude 和 DSH 记 not_applicable(control_plane)。
- [cargo test onboard::probe_l1] 用进程内桩 MCP 服务加哑 shim:适配器取配置,按客户端方式执行 helper,再依次 initialize、tools/list、tools/call,C/D/E/F 均为 ready,每格带证据等级。shim 输出错误 token 时 F 记 auth_failed;shim 缺失记 shim_missing;端口不可达记 unreachable;客户端未安装记 not_installed,不计失败。期望工具集取自
P8d onboard_project 编排本体(M)
- files:src-tauri/src/onboard/apply.rs、report.rs(新增),src-tauri/src/commands/onboarding.rs(存量的 apply_onboarding_pack 一并改为
#[tauri::command(async)]) - acceptance:agent_verify:[cargo test onboard::apply] 用临时 git 仓库、in-memory DB,以及假的 CredentialStore、ClientAdapter、ProcessRunner:
- plan 后 apply(expected_digest, confirmed=true):status 为 done,HEAD 不变,repo_changes 与
git status --porcelain一致且都未提交。 - 立即再次 plan:全部 noop;再次 apply:全部 converged,mtime 不变。
- digest 不符或 confirmed=false:refused,零写入。
- Claude 适配器失败:partial,Codex 和 DSH 照常完成,L1 的 Claude 列记 blocked(dep=claude_local);修复后重跑得到 done。
- 未登记的 project_id、空路径或相对路径:failed,零写入。
- 非 git 目录:repo_changes 为 unavailable,status 不是 done。
- 新项目没有 origin、CI、飞轮数据:仍可以是 done,这三项进入 next_actions;Gitea 一项注明「默认公开」。
- 同一项目并发 apply:第二个返回 busy。
- plan 后 apply(expected_digest, confirmed=true):status 为 done,HEAD 不变,repo_changes 与
P8e 投影收口与契约同步(S)
- files:src-tauri/src/mcp_tools.rs(
plan_onboarding、onboard_project的 schema,单独成函数以避开 json! 的递归上限;dispatch 里只留一行委托),src-tauri/src/lib.rs,src/lib/commands.ts,docs/ai-context/project-context.yaml,docs/ai-context/agent-button-system.md,AGENTS.md(架构速查补src-tauri/src/onboard/) - acceptance:agent_verify:
- [cargo test mcp_tools] 全局和项目路由的 tools/list 都包含这两个工具;在项目路由上强制本项目,传跨项目参数返回 scope_violation;
onboard_project的 required 包含expected_digest和confirmed;annotations 中 plan 为 readOnly、apply 为 idempotent。 - [pnpm typecheck] 通过。
- [MCP 全局] 对 enterprise 做 plan,全部 noop;做 apply,结果 done 且各步都是 converged。
- [读文件] project-context.yaml 已补上 plan、digest 和状态语义(T1)。
- [cargo test mcp_tools] 全局和项目路由的 tools/list 都包含这两个工具;在项目路由上强制本项目,传跨项目参数返回 scope_violation;
P8f register_project 控制面投影(S,可选,见 Q7)
- files:src-tauri/src/commands/projects.rs(抽出 create_project 的本体,连接由调用方注入),src-tauri/src/mcp_tools.rs(只开放在全局路由,并在 is_project_scoped_tool 中排除)
- acceptance:agent_verify:
- [cargo test] 空路径、相对路径、不存在的路径、重复登记都被拒绝,不建目录,不执行 git init。
- [MCP 全局] 对临时 git 仓库依次调用
register_project、plan_onboarding、onboard_project,结果为 done;项目路由的 tools/list 不含 register_project。
P8g UI 一键接入(M)
- files:src/components/dashboard/OnboardPanel.tsx(新增),src/components/dashboard/onboardView.ts 及其 .test.ts(新增,报告到视图的纯函数),ProjectModal.tsx,BatchAddModal.tsx(只提供批量入口,不自动接入),src/components/manage/NewProjectModal.tsx,BlueprintModal.tsx(「应用接入包」改为先 plan 再 apply),src/lib/commands.ts
- acceptance:
- agent_verify:[pnpm typecheck && pnpm test] 视图纯函数:blocked 非空时确认按钮禁用,并列出文件和原因;not_installed 和 not_applicable 显示为灰色说明,不显示为红色失败;bypassPermissions 和 Codex 信任单独列出;确认时携带 expected_digest。
- human_verify:
- 用一个临时 git 仓库新建项目,确认弹出预览。
- 确认后 30 秒内看到分步结果、L1 矩阵和「待你提交的 N 个文件」。
- 另试一次在预览里取消,确认目录零改动。
- 在该目录新开 Claude Code、Codex、DSH 桌面 GUI 会话,调用 LJ_PROBE_NONEXISTENT,确认得到「开发需求未登记」。
P4b MCP 服务端调用轨迹环(S,在 A4 合并后做,见 Q8)
- files:src-tauri/src/mcp_observe.rs(新增),src-tauri/src/mcp_server.rs(单一挂点,扩展 F10 中已有的记录点)
- acceptance:agent_verify:[cargo test mcp_observe] 在项目路由上依次 initialize(codex-mcp-client)、tools/list、调用
read_project_test_account(name=LJ_PROBE_A1B2C3D4),分别用无认证、错误认证、正确认证:- 依次记录 auth=none/invalid/valid、route、method、tool、截断后的 clientInfo/UA,以及 nonce;
- Authorization 的值、bearer、其他参数值(例如 SECRET_X)都不进入记录;
- 容量上限按 FIFO 淘汰;
- readiness 和 l1-probe 的请求不进入轨迹;
- 没有任何工具暴露原始轨迹。
P4c 探针判定本体(M,纯函数)
- files:src-tauri/src/onboard/probe/{evaluate,adapters,redact}.rs(新增),src-tauri/src/envoy.rs(把敏感模式抽成共享函数)
- acceptance:agent_verify:[cargo test onboard::probe]
- P4-0 的真实夹具解析出的矩阵与期望一致。
- 合成夹具逐一命中对应归因:route_global、auth_header_missing、token_mismatch、adapter_drift(判 unknown)、model_noncompliant、client_auth_or_quota(判 unknown)、rules_not_loaded,以及 credential_leak(落盘文本已脱敏)。
- 控制面画像下 E、F 为 not_applicable。
- compare 只列出变化的格子;probe_spec_version 不同时标记为不可比。
- 判定函数没有任何 IO。
P4d 探针执行器 + 异步任务 + 持久化(M)
- files:src-tauri/src/onboard/probe/runner.rs(ProcessRunner 和 ClientLocator trait、Job Object、环境清理、包上下文检测),src-tauri/src/onboard/probe/job.rs,src-tauri/src/db.rs(
migrate()新增client_parity_runs表) - acceptance:agent_verify:[cargo test onboard::probe::runner] 用假 ProcessRunner:
- 超时:进程树全部退出,该端记 timeout,其他端照常。
- 未安装:记 not_installed,耗时不到 1 秒。
- L1 判定未注册的一端:零进程。
- 子进程环境不含
CLAUDECODE和CLAUDE_CODE_*,含MCP_CONNECTION_NONBLOCKING=false,stdin 为空;参数等于 P4-0 写死的组合。 - 运行中再次 start:返回同一个 run_id;启动时遗留的 running 记录改标 interrupted。
- 同一指纹 7 天内命中缓存;
force=true时重跑。 migrate()在旧库上重复执行是幂等的。- 探针前后
git status不一致时,记 probe_side_effect,整次失败。
P4e 探针投影 + CLI 薄客户端 + UI 按钮(M)
- files:src-tauri/src/mcp_tools.rs(
start_parity_probe和get_parity_probe只在全局路由;get_client_parity在项目路由只读),scripts/probe-clients.mjs,package.json(probe:clients),src/components/dashboard/OnboardPanel.tsx(探针按钮和矩阵),src/lib/commands.ts,docs/ai-context/agent-button-system.md,docs/ai-context/project-context.yaml,AGENTS.md(「炼境自己的 Agent 按钮清单」加一行) - acceptance:
- agent_verify:
- [MCP 全局]
confirmed=false时拒绝,且不起任何子进程;confirmed=true时 1 秒内返回 run_id。 - [MCP 全局] 轮询 enterprise,6 分钟内 done,三端 B–F 通过、G 通过,与 P1 基线一致;不做任何改动,用 force 立即重跑,diff 为空。
- [MCP 全局]
clients=["dsh"]时 60 秒内完成。 - [MCP 项目路由] 看不到 start_parity_probe。
- [CLI]
pnpm probe:clients <id> --wait:全部适用格通过时 exit 0,否则 exit 1;炼境不可达时 exit 3。
- [MCP 全局]
- human_verify:人看一次矩阵,确认证据等级和回退标注可读。
- agent_verify:
8. 风险
| # | 风险 | 应对 |
|---|---|---|
| R1 | 与在途工作冲突。P6 要改 onboarding.rs 的分发逻辑,和 P8c 改同一处;A4(rmcp)要改 mcp_server.rs 和 mcp_tools.rs,和 P4b、P8e、P4e 冲突 | 按 Q3、Q8 排序。新工具在 dispatch 里只留一行委托。开工前查 origin 和 Codex worktree 分支(目前没有 P6 或 A4 的代码分支) |
| R2 | Codex 可能不加载未信任目录的项目配置,结果是「配置写了但不生效」 | 先由 P4-0 实证;成立的话列为 next_actions,炼境不写 ~/.codex 的信任表(Q5) |
| R3 | bypassPermissions 默认值随一键接入扩散到每个新项目(F8) | plan 中单列,默认值交给 Q4 决定 |
| R4 | P8b 之前直接编排会掩盖失败,把 Claude 或 DSH 的失败报成成功(F1) | P8d 必须排在 P8b 之后 |
| R5 | 隐式副作用链拆平后行为会变:已在组内的项目也会刷新本机注册,失败会显式暴露 | 有意为之,在 PR 中声明;独立产品组作为兼容步骤保留 |
| R6 | L1 只是配置回放,可能 L1 全绿而真实会话失败 | 每格标证据等级;版本变化时提示跑 L3;GUI 形态归 human_verify |
| R7 | L3 的额度消耗和随机性;模型自报不可靠 | C–F 只认客观证据;不自动重试;不作为 CI 闸门;额度或登录问题记 unknown |
| R8 | 项目 settings 默认 bypassPermissions,--allowedTools 在这种模式下大概率不构成限制(待 H-P8-2 实测确认),探针会话可能调用写工具 |
P4-0 写死收窄参数组合;外加副作用断言和服务端轨迹兜底 |
| R9 | Codex 在 read-only 下 MCP 调用被拦截,回退到 workspace-write 后模型可能写文件 | 比对 git status 前后,检查非白名单调用,违反即整次失败 |
| R10 | 环境污染:MSIX 包上下文、继承 CLAUDECODE、GUI 进程的 PATH 不全 |
检测并在报告中标注;客户端路径允许在设置里覆盖 |
| R11 | 杀进程树不彻底(F11),.cmd 包装下的 node 进程会成为孤儿 |
使用 Job Object |
| R12 | MCP 单次调用超时:apply 最坏情况下 claude CLI 要跑 4 次,每次最多 20 秒,可能超过 Codex 的默认工具超时 | 主要调用方是 async 的 Tauri command 和控制面 Claude 会话;等 H-P8-5 实测后再决定是否把 apply 也改成异步任务 |
| R13 | L3 并发归属:用户同时开着的会话可能混进时间窗 | F 项用 nonce 区分;C–E 靠串行、时间窗和 clientInfo;记为已知限制 |
| R14 | 轨迹环越界,存进了参数值或请求头 | 只存元数据,用测试钉死 |
| R15 | 依赖客户端私有实现(DSH 的 app.asar、Codex 的哈希 bin 目录,本机已有两个) | 通过 ClientLocator 和设置覆盖定位;格式变化归因为 adapter_drift;适配器升级需要重建炼境 |
| R16 | T1 契约变更:新 MCP 工具、新 Tauri command、OnboardingVerification.clients、client_parity_runs 表 |
同步 commands.ts、project-context.yaml,并在 migrate() 中补 migration |
| R17 | 真实客户端验收超过 180 秒,会被 Workflow 子 agent 判为卡死 | P4-0、P4e、P8g 的端到端验收只在主会话或后台任务里跑 |
| R18 | 新项目的 verify 必然不全过(F7) | 「完成」只按三端可用子集判定 |
9. 需要用户决定的问题([推荐] 为推荐项)
Q1 P8 验收口径
- [推荐] 改为「L1 零模型矩阵(带证据等级)+ 可选 L3 结果及是否过期」。P8 只依赖 P4a,可以先于 L3 交付。
- 保持原口径「三端无头 A–F 矩阵」。P8 要等 P4 全部完成。
Q2 写入前预览
- [推荐] A:plan → digest 确认 → apply。通过 WriteSink 实现,与 CI 的 plan/apply 同一模式,并关闭 P10 遗留问题。
- B:只做混合文件闸门 + 写后的 git diff 报告。少一张 M 卡,但写入前看不到具体改动。
Q3 P6 与 P8c 的先后
- [推荐] P8c 先落地 WriteSink,P6 基于它实现。
- P6 先合并,P8c 再适配。
- 并行开发(不推荐,onboarding.rs 必然冲突)。
Q4 bypassPermissions 默认值
- [推荐] 保持现状,但在 plan 和报告中单列,允许按项目取消勾选。
- 新项目默认不写(这是行为变更,需要另写理由)。
- 维持现在的静默写入。
Q5 Codex 信任(H-P8-1 成立时)
- [推荐] 列入 next_actions,由用户在 Codex 里自己信任。
- 逐项目确认后,由炼境写
~/.codex/config.toml的信任表(这是在改另一个工具的安全设置,不推荐)。
Q6 L3 什么时候跑
- [推荐] 只在显式触发时跑;建议在 canary 项目上、指纹变化时跑;新项目接入默认不勾选。
- 首次接入默认勾选。
- 另外加每周金丝雀定时探针(默认关闭)。
Q7 register_project 的 MCP 投影(让 agent 能复刻「新建 → 接入」)
- [推荐] 做。只开放在全局路由,写操作必须经用户确认。
- 不做,新建项目只走 UI;agent 只能对已登记的项目验收。
Q8 P4b 与 A4 的先后
- [推荐] A4 先合并,P4b 挂在 rmcp 的 dispatch/认证边界上(P8 不受影响)。
- P4b 先合并,A4 迁移时顺带处理挂点。
Q9 CLI 形态
- [推荐] P4e 只做
pnpm probe:clients薄脚本(放在炼境仓库,调用全局 MCP)。 - 现在就做 Rust console 版
lianjing.exe+ skill 降级说明(要升接入包版本,全部子项目重新分发)。 - 不做 CLI,P4 验收载体改为 MCP。
Q10 A1 改写
- [推荐] 拆成三段:A1a 随 P8 落地(适应度测试),A1b 按需做 CLI 薄客户端,A1c 触发式拆 crate。
- 维持原 A1 卡,并把 P8 排在它之后。
Q11 探针用的模型
- [推荐] 用各客户端默认模型,设置里允许用户为探针单独指定更便宜的模型。
- 固定用便宜模型。
10. 没验什么
- lian-jing MCP 在本会话中没有连上,所以没有用 get_diagnostics 或 verify_onboarding 核对 enterprise 当前的运行态基线。实施 P8-0 之前,应该在炼境在线时复核一次。
- 本次没有运行任何客户端。以下都只是依据蓝图记录或推断,没有实测:
- H-P8-1 至 H-P8-6;
claude mcp get的 Connected 状态是否意味着 helper 已被执行(依据 P1/H2 的记录推断);- bypassPermissions 下
--allowedTools不构成限制(依据对 Claude Code 权限模型的理解,需要实测); - 各项耗时目标和三端合计 token 估算。
- 所有代码结论都来自 master(10434d4)的只读阅读,没有跑 cargo test。工作区里的未提交改动没有动过,也没有判定归属。