- MCP 新增 plan_onboarding(readOnly)与 onboard_project(幂等写操作):schema 各自单独成函数, dispatch 一行委托到 mcp_inject 生产入口(中间只做 JSON 类型解析);全局与项目路由都可见, 项目路由强制本项目、跨项目返回 scope_violation;onboard_project required 含 expected_digest 与 confirmed;共用可选选项 bypass_permissions / skip_targets;进 STRUCTURED_TOOLS 并给一行摘要 - Tauri 新增同名 async 命令(async fn + spawn_blocking),commands.ts 同步 OnboardOptions、 OnboardingPlan、OnboardReport 类型与 planOnboarding / onboardProject 封装 - 统筹决定(P8d 审查 ⑩):L1 Codex 列仅因 codex_project_untrusted 未就绪的格不计入 done, completion 单列「Codex 信任」(pending_user),next_actions 保留 codex_trust;仓库子目录与 worktree 补 next_action repo_layout 说明为何到不了 done;非 git 维持不能 done - 契约同步:project-context.yaml、agent-button-system.md、AGENTS.md 架构速查;P8d 卡记统筹决定落地 - cargo test 413 passed / 0 failed / 6 ignored;pnpm typecheck、pnpm test 通过;三轮 10 处变异全部被杀 Feature-Confirmed: true Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
22 KiB
YAML
48 lines
22 KiB
YAML
# 项目契约路径映射
|
||
# 由炼境根据技术栈扫描生成,技术栈变更后重新运行接入包生成
|
||
# 供 AGENTS.md 元规则 T1 查询:「哪个文件是这个项目的外部契约?」
|
||
|
||
contracts:
|
||
- label: API 接口(Axum)
|
||
paths:
|
||
- "src-tauri/src/mcp_server.rs"
|
||
doc: "MCP 仅监听 127.0.0.1;新项目使用 /mcp/projects/<project_id> scoped route,连接 scope 拒绝跨项目参数和全局管理写工具。scoped route 只服务已登记项目,未知 project_id 返回 404(数据库不可用同样拒绝)。各客户端经 %USERPROFILE%/.lian-jing/bin/lianjing-mcp-header.cmd 稳定入口取认证头(炼境每次启动指向当前 exe;入口不放 AppData,因炼境若在 MSIX 应用的进程树里启动(如由 Claude 桌面版里的 agent 启动),AppData 下新建的目录会被重定向到该应用的私有目录,其他客户端看不到,用户目录根不受此影响;旧 %LOCALAPPDATA% 入口在迁移期同步保留,verify_onboarding 报 Legacy 引导重注入):Codex 写项目 .codex/config.toml,Claude 写 local 条目(优先于 user 级全局;一律经 claude CLI、cwd=项目目录写入,项目键推导、文件锁与配置路径交给 Claude 自身,不直接编辑 ~/.claude.json;项目根或其祖先键下已有一致的托管条目则不起 CLI,否则以项目目录下 claude mcp get 的实际生效条目为准:本项目托管条目刷新/移除,他项目托管条目与手写条目不碰,无法确认现状一律不写;项目目录已不存在时只记录残留不写入),DSH 写 ~/.dsh/cordis.patch.yml 托管块(内嵌炼境登记的「项目根目录→project_id」白名单,仅 headless profile 由 !!js 从启动目录向上精确匹配根目录才选项目地址并取认证,web/acp/tui/桌面版一个进程可服务多个工作区,全局行一律走全局以防跨项目带认证;这些模式由同一托管块引用的会话级插件(随炼境打包,写到 %USERPROFILE%/.lian-jing/dsh/,文件名带版本号)在每个会话创建时按会话工作目录匹配白名单,命中才在该会话作用域挂项目地址并经稳定入口取认证,其他会话不受影响;任意目录里的 context.json 不作授权依据;未命中回退全局;项目增删与端口变更时重写)。Claude local 条目与 DSH 块只替换炼境托管的条目,手写同名配置不覆盖;移除项目时一并清理。旧 /mcp 只作兼容迁移。read_project_dev_variable 与 read_project_test_account 是两种有界开发值读取:仅 scoped 路由、匹配项目的本机 bearer 认证后单项读取;认证失败在 dispatch 前拒绝。路由变更需同步 Agent 按钮体系文档。"
|
||
|
||
- label: MCP 工具接口
|
||
paths:
|
||
- "src-tauri/src/mcp_tools.rs"
|
||
- "src-tauri/src/commands/delivery_status.rs"
|
||
- "src-tauri/src/commands/toolchain.rs"
|
||
- "src-tauri/src/commands/cicd.rs"
|
||
- "src-tauri/src/commands/gitea.rs"
|
||
- "src-tauri/src/commands/dev_variables.rs"
|
||
- "src-tauri/src/envoy.rs"
|
||
- "src-tauri/src/onboard/probe_l1.rs"
|
||
- "src-tauri/src/onboard/plan.rs"
|
||
- "src-tauri/src/onboard/report.rs"
|
||
- "src-tauri/src/commands/projects.rs"
|
||
doc: "工具 schema、dispatch 与调用说明必须同步;使者回传三件套:report_envoy_return 为项目级(三端在 scoped 地址可用;kind/confidence/suggestion/client 为枚举,scope 为 this_project|all_projects|project_type:<类型> 格式校验,content 压成单行且限长;无凭据字段,服务端对私钥头与常见令牌前缀做兜底拒绝,其余靠描述约束——内容会双写进子项目 .blueprint/notes-archive.jsonl 随项目进 Git;同项目同内容未处置时幂等返回原编号),resolve_envoy_return 与 list_envoy_returns 仅控制面(全局地址),子项目 agent 只回传不处置;list 默认只列未处置并报告已处置计数,include_resolved=true 附列回执,超出展示上限须明说不得静默截断,回传格式首行前缀【炼境使者回传】保持兼容(B4 沉默项目统计按前缀匹配);get_project_resource_status 为项目 scoped 只读状态,仅返回 Gitea PR/webhook 的状态与来源,不返回凭证、引用、URL;旧凭证未迁移时返回 pending_migration,不提前宣称 configured。MCP 不提供 reveal_api_key、get_secret 或任意 HTTP/SSH/SQL/部署代理。明文例外只限 read_project_dev_variable 的单项标量与 read_project_test_account 的单张非生产测试账户;均须匹配 scoped 项目及本机认证。register_project_test_account 只写非秘密需求元数据;列表不返回值,禁止批量导出。production delivery profile 由项目维护,炼境只读 Gitea 部署 context 与公开版本探针,双证据相符才能报告 deployed。get_delivery_status 的 PR/CI/review 默认按项目主工作区当前分支与本地 HEAD 查询;可选 pr_number(优先)或 branch 时改由 Gitea 解析 head 分支与 head SHA(指定分支无开放 PR 时取远端分支 head),CI 结论绑定该 SHA,PR 不存在返回 not_found;顶层 target 标明 mode(local_branch/pull_request/branch)、pr_number、branch、head_sha 与来源,git 字段始终反映主工作区;显式给出的非法 pr_number/branch 直接报错,不得静默回退主工作区。外部来源不可用时返回显式降级状态,不得伪造成功。verify_onboarding 必须同时返回静态接入 checks 与 configured/reachable/handshake/tools_ready/reload_required 运行态证据,配置存在不得替代运行可用。CI workflow 使用 plan_cicd_workflow 只读预览,用户确认相同 digest 后方可 apply_cicd_workflow 写入;该接口不接收凭证值且不触发 CI 或部署。unregister_project 与 UI 删除项目共用同一本体(projects::delete_project_registration):持接入写锁先清理 MCP 配置、再在同一事务内解除 Gitea 仓库绑定并删登记(与一键接入串行,P8d 审查 ①),释放写锁后尽力删除 Gitea 侧 webhook 与本机签名凭证;Gitea 轮询跳过所属项目已不存在的遗留绑定,且每个仓库写入前复核绑定仍在、项目仍登记,不向已删除或已解绑的项目 ID 写 commit 与 PR 事件;每个遗留绑定每次运行只记一次日志,日志写明清理路径(对该 ID 调 unregister_project)。register_project(P8f)只开放在全局路由(is_project_scoped_tool 显式排除,项目路由 tools/list 不含):与 UI create_project 共用登记本体 projects::register_project_records(连接注入),只写登记、不建目录、不执行 git init、不接入;拒绝空路径、相对路径、不存在的目录、已存在的 ID,以及同一目录的重复登记。唯一例外:UI 入口不选目录时沿用既有的概念占位登记(RootRequirement::ExistingDirOrPlaceholder,不校验目录、不查重复),MCP 入口不接受占位;是否改为显式占位概念待用户决定。同目录判定:已有登记按 platform 只取有效根目录(Windows 取 win_path、WSL 取 wsl_path,Linux 路径按 wsl.localhost 的 UNC 换算;切换平台残留的另一列不参与),只算字面键 projects::registration_path_key(与 DSH 白名单 mcp_inject::normalize_dsh_root 共用实现:去 Windows 长路径前缀、正反斜杠统一、合并重复分隔符、去尾部分隔符、不区分大小写);新目录在写事务外另算 canonical 键(canonicalize 去长路径前缀,消掉 8.3 短名与 .. 段),两键任一命中即拒绝,错误信息给出已有 project_id。Windows 目录落库为 canonical 规范形式(真实大小写、反斜杠,junction/符号链接/subst 解析到真实目标),WSL 路径原样落库。MCP 入参只收 Windows 绝对路径 path 与可选 name,project_id 由炼境生成(UUID),与落库的规范目录一并以 structuredContent 返回,归入当前空间;目录里遗留别的 project_id 的 .lian-jing/context.json 时回执带 warning(只读提示,不沿用旧 ID);描述要求调用前向用户列出目标并确认。scan_unregistered_projects 的已登记判定与父目录去重改用同一字面键。批量添加对同一目录跳过(计入 skipped);挂载仓库(mount_repo)在任何写入前做同一检查,profile 与 workspace 同事务写入,被拒绝不留孤儿 profile;模板新建在建目录前复用同一检查。probe_client_parity(P4a)只开放在全局路由、readOnly,tier 目前只有 l1:L1 零模型三端检查,Claude 用 claude mcp get 与坏模型名 init 自省、Codex 用 codex mcp get --json、DSH 读托管块白名单与会话插件哈希,再按各客户端方式执行稳定 helper 在其配置的项目地址上重放 initialize、tools/list(与 tools_list_result_for_project 比对)、get_diagnostics、list_project_dev_variables 与测试账户敏感读取(LJ_PROBE_<nonce>);返回 L1Report(A–F × 三端,每格 state/cause/evidence/detail,证据等级 static < client_introspection < protocol;not_installed、not_applicable 不计失败;控制面项目 Claude 与 DSH 整列 not_applicable),认证头只在炼境进程内使用、不进结果与日志;探针 initialize 自报 clientInfo.name=lian-jing-l1-probe,服务端与 lian-jing-readiness 一样不计入 has_initialized_project_client;检查期间本项目路由上的全部 initialize(含探针的 claude mcp get 健康检查与零模型 init,均以 claude-code 身份连接)也不计入,宁可多提示一次重启。协议重放只证明服务端视角:客户端自己未连上时 D–F 记 blocked,客户端看不到某工具(Claude init 工具名、Codex enabled_tools/disabled_tools)时对应格记 tools_mismatch;配置里的 helper 只有逐字等于炼境托管写法(当前或旧 %LOCALAPPDATA% 稳定入口 + 本项目 digest)才执行,否则记 unknown(manual_config);零模型 init 带 --settings {\"disableAllHooks\":true},只挂 type/url/headersHelper。一键接入两个工具(P8e)plan_onboarding(readOnly、幂等)与 onboard_project(非只读、非破坏、幂等:重复执行收敛)在全局与项目路由都开放:项目路由把 project_id 绑定为本项目(schema 改 const、可省略),跨项目参数返回 scope_violation;schema 各自单独成函数,dispatch 只一行委托 mcp_inject::plan_onboarding / mcp_inject::onboard_project(与同名 Tauri command 同一本体,投影不复制判定)。plan_onboarding 的 required 为 project_id,onboard_project 的 required 为 project_id、expected_digest、confirmed;两者共用可选选项 bypass_permissions(布尔,默认 true)与 skip_targets(字符串数组,取 plan 的 blocked[].target,只有 manual_entry / shared_key 可跳过,其余列进来照旧阻断),计划与应用必须同一组选项,否则 digest 不符被拒;选项、expected_digest、confirmed 类型不对在任何 IO 之前报错,缺省 confirmed 视为 false。描述约束 agent:先把计划(要改的文件、blocked、bypass_permissions、next_actions)给用户看,用户明确确认后才带同一 digest 与 confirmed=true 调 onboard_project;Codex 需用户在 Codex 里信任目录(炼境不写信任表);Gitea 建仓工具默认建公开仓库,私有须显式 private=true。两者进 STRUCTURED_TOOLS:structuredContent 为 OnboardingPlan / OnboardReport 原样结构(与 commands.ts 同名类型一致),content 为一行摘要(计划:文件动作计数、本机七步、阻断与跳过、bypassPermissions、Codex 信任、digest 与下一步;报告:本体 summary、待提交接入文件数与 next_actions 名);refused / busy / failed / partial 以报告 status 返回,不作工具错误。list_all_projects 与 scan_unregistered_projects 的引导改为 plan_onboarding → 用户确认 → onboard_project(inject_mcp 只做本机 MCP 注入)。"
|
||
|
||
- label: 被管理项目身份上下文
|
||
paths:
|
||
- "src-tauri/src/mcp_inject.rs"
|
||
- "src-tauri/src/commands/onboarding.rs"
|
||
- ".lian-jing/context.json"
|
||
- ".lian-jing/capability-packs.json"
|
||
- ".claude/liangjing.json"
|
||
doc: "`.lian-jing/context.json` 是 project_id 的工具无关单一事实来源;`.claude/liangjing.json` 仅为兼容投影;`.lian-jing/capability-packs.json` 是炼境分发基线的版本描述。lian-jing skill 以同一模板双投影:`.agents/skills/lian-jing/`(Codex、DSH 读取)与 `.claude/skills/lian-jing/`(Claude Code 不读 .agents/skills),两份内容必须一致。context.json 可选字段 mcp_scope=global 声明控制面项目(如炼境自身):Claude 不写 local 条目、DSH 不入白名单,保留全局地址与跨项目工具。迁移必须保留未知字段;验证必须返回显式状态与 upgrade_hint;升级仅覆盖炼境托管文件,绝不覆盖项目定制或手工 MCP 配置,且不得以客户端全局配置或项目 UI 身份替代项目根目录上下文。apply_onboarding_pack 的模板结果 TemplateResult 可带 warning(混合文件预警):写入前 AGENTS.md、docs/ai-context/git-workflow.md 或 CLAUDE.md 在 git 中已有托管块(ONBOARDING_MANAGED / BLUEPRINT_MANAGED)之外的未提交改动(未跟踪文件带块外内容也算;比较只忽略 CRLF/LF 与文末托管块连同其前空白——即炼境追加托管块带来的差异,块外空行增删与行尾空格均算改动),且本次确实改写了该文件时给出;只提示不阻断、块外内容不动,非 git 仓库或 git 不可用时静默。CLAUDE.md 只由蓝图规则同步改写,仅在预警时补一条结果。模板分发之后的步骤(lefthook 激活、buff)报错时,已改写的混合文件名并入错误信息返回。「已接入」只有一个判定:本机受保护存储里有项目 MCP token(mcp_inject::is_project_injected);DSH 白名单、根目录迁移、批量重注入(产品组成员 + 有 token 的独立项目)与 list_all_projects 同源,.claude/liangjing.json 不作判据。接入经客户端适配器注册表(onboard::clients::registry:Claude、Codex、DSH)逐端收敛并返回结构化结果 ClientOutcome(converged/applied/refreshed/removed/manual_kept/other_project/not_installed/not_applicable{control_plane|wsl}/deferred/missing/unavailable/needs_human/failed);inject_mcp 回执逐端列出,只有 Codex 托管块写入失败算整体失败;项目 .codex/config.toml 已手写同名 server 记 manual_kept,不再整体报错;verify_onboarding 附加 clients 字段(三端只读状态,不计入 checks、不影响 pass)。src-tauri/src/onboard/ 是一键接入本体模块,源码不得依赖 Tauri、不得直接取全局连接池(适应度测试 onboard_module_is_projection_free)。一键接入编排本体 onboard::apply(P8d;生产入口 mcp_inject::plan_onboarding 与 mcp_inject::onboard_project,P8e 的 MCP 工具与 Tauri command 同名投影都只一行委托它们):计划含项目根目录 root(将要写入的目录)、逐文件动作与 diff、本机七步 machine_changes(token、shim、claude、codex、dsh、product_group_buff、git_hooks)、blocked、skipped、bypass_permissions(本次会向 .claude/settings.json 新写入 permissions.defaultMode=bypassPermissions 时为 true)、codex_trust、next_actions 与 digest(绑定 root、文件、本机步骤、阻断项与跳过项);选项 bypass_permissions=false 即按项目取消,settings.json 缺 permissions 时改写显式 defaultMode=default 记住选择(之后的重注入同样只在缺省时写);选项 skip_targets 是人对阻断项逐项选「跳过」的 target:只有 manual_entry(手写同名条目)与 shared_key(共享项目键)可跳过,移入 skipped、对应一端 apply 时记 skipped(manual_kept|other_project)、不调用该端适配器、文件原样保留,身份冲突、混合文件与 needs_human 列进来也照旧阻断;跳过不等于该端可用,done 仍按三端可用子集判定。onboard_project(expected_digest, confirmed) 依次:同一项目已在执行 → busy;预检(未登记、空路径、相对路径、根目录不存在)→ failed;未确认 → refused;接入写锁内重新解析登记(等锁期间登记被删除 → failed;根目录或端口变了 → refused 并附当前计划),再重算计划,digest 不符或有阻断项 → refused 并附当前计划;以上均零写入。之后在写锁内按依赖执行 identity、project_files(与计划同一写入序列)、product_group_buff(只写 DB)、token → shim → 三端适配器(token 或 shim 未完成则 blocked(dep=…);Codex 配置即项目 .codex/config.toml,由 project_files 写入,codex 步只复核,本次写过记 applied),释放写锁后执行 git_hooks(lefthook install,非 git 仓库或仓库子目录 not_applicable,缺可执行文件 needs_human,本地可执行文件存在却起不来 failed 并附启动错误);每步状态 converged/applied/skipped/not_applicable/blocked/failed/needs_human;某步失败只阻断依赖它的步骤,整体 partial,不自动回滚。随后跑 L1:客户端步骤未完成的一端整列 blocked(cell.cause=dependency、cell.dep=步骤名)、不起进程。报告 status=done/partial/failed/refused/busy,done 只按三端可用子集判定(全部步骤 converged/applied/skipped/not_applicable,且 completion 中不带 pending_user 的项全部 ready:verify_onboarding 中除 origin、CI、飞轮、工具链、delivery profile 外的检查全部 ready,三端只读判定已收敛,L1 适用格全部就绪,git 仓库可报告改动且 HEAD 未变)。completion 每项为 {item, source(l0|clients|l1|repo), ready, detail, pending_user};统筹决定(P8d 审查 ⑩,P8e 实现):L1 的 Codex 列里仅因 cause=codex_project_untrusted 未就绪的格只等用户在 Codex 里信任目录(炼境不写 Codex 信任表),不计入「L1 适用格」,completion 单列一项「Codex 信任」(source=l1、ready=false、pending_user=true,不计入 done),next_actions 保留一条 codex_trust(计划已列则不重复,计划预判已信任而 L1 仍未信任时补一条);Codex 列其余原因(含 blocked)照常计入;L1Report.all_applicable_ready 保持 L1 自身的原始判定。非 git 目录 repo_changes 为 unavailable,到不了 done;项目根是仓库子目录或 .git 为文件(worktree、子模块)时 verify 的「lefthook hook 挂载」只认项目根 .git/hooks、不会就绪,到不了 done,next_actions 补一条 repo_layout 说明原因(外层仓库根或实际 hooks 目录)与怎样才能 done;written_files 为本次实际写入的项目文件;repo_changes 为 git status --porcelain --untracked-files=all -- . 与 git diff --stat -- .(限定在项目根内;非 git 为 unavailable),并分出 onboard_files(本次写入的,加上接入写入序列管理且仍未跟踪的,路径相对项目根)与 other_changes(用户原有改动等);建议提交信息与 next_actions 的 commit 一项(git add -- <onboard_files>)只针对接入文件,炼境不 add、不 commit;next_actions 含 commit、Gitea 建仓(注明工具默认建公开仓库)、CI、飞轮、工具链、lefthook、Codex 信任、仓库布局(repo_layout)、重启 DSH GUI、Claude 首次 trust 与重开已打开的会话(不据 reload_required 推断已重载)。一键接入执行了写入时另补一条 category=mcp_inject 的事件(token、shim 与三端步骤都完成记 info,否则 warn),get_diagnostics 的注入状态与设置页注入告警据此更新。删除登记(UI delete_project 与 unregister_project)在同一把接入写锁内先清理 MCP 配置、再删登记的数据库部分;端口变更的批量重注入在写锁内取目标快照。"
|
||
|
||
- label: Tauri command 接口(前后端契约)
|
||
paths:
|
||
- "src-tauri/src/commands/**/*.rs"
|
||
- "src/lib/commands.ts"
|
||
doc: "新增 command 时前后端必须同步。Gitea/API Key 普通 DTO 不含原始凭证;API Key 仅本机 UI 显式 reveal,get_project_resource_status 仅返回非秘密状态;项目开发需求列表 DTO 仅含名称、类型、目标系统、环境、用途、configured 与时间。本机 UI 对 test_account 一次提交账号/密码到同一受保护引用,允许修改或撤销;列表常态不含值,仅人显式点「修改」时经 reveal_project_dev_variable 取回原值(密码默认遮罩),该 command 不进 MCP;旧 scalar 保持兼容。旧 SQLite 明文列按 write→verify→事务切换引用并清除的顺序兼容迁移,失败需可恢复,不能降级明文。Agent readiness 必须保留 configured/reachable/handshake/tools_ready/reload_required 分层;Windows autostart 默认关闭,只允许正式安装版,debug/target 构建必须拒绝启用。probe_client_parity(async,阻塞线程池执行)与 MCP 同名工具共用 mcp_inject::probe_client_parity 本体,返回同一 L1Report(commands.ts 的 ClientParityL1Report);L1 格子可带 dep(只在一键接入编排里出现:cause=dependency,dep 为未完成的接入步骤名)。apply_onboarding_pack 为 #[tauri::command(async)](P8d:buff 建组会注入 MCP、可能起 claude CLI,lefthook install 为子进程且带 60 秒超时;在 Tauri 异步运行时的任务里执行,不占界面线程,但会占住一个运行时 worker,同 buff.rs、groups.rs 的写法),入参与返回不变。一键接入的 Tauri 投影(P8e)plan_onboarding(projectId, options?) → OnboardingPlan 与 onboard_project(projectId, expectedDigest, confirmed, options?) → OnboardReport 为 async fn + tokio::task::spawn_blocking(与 probe_client_parity 一致;不用 #[tauri::command(async)] 包同步函数,以免占住 MCP 服务端所在运行时的 worker 30 秒以上),与 MCP 同名工具一样只委托 mcp_inject 生产入口;options 可省略(OnboardOptions 默认:bypass_permissions=true、skip_targets 为空),计划与应用须传同一组;plan 的错误(未登记、路径无效等)以 Err 返回,onboard_project 的 refused / busy / failed / partial 一律以报告 status 返回,Err 只表示线程异常。commands.ts 的 OnboardOptions、OnboardingPlan、OnboardReport(含 completion[].pending_user、repo_changes.onboard_files / other_changes、written_files、plan.root / skipped)与 Rust 序列化结构一一对应。"
|
||
|
||
architecture_docs:
|
||
- "AGENTS.md"
|
||
- "docs/ai-context/agent-button-system.md"
|
||
|
||
agent_docs:
|
||
- "AGENTS.md"
|
||
- "docs/ai-context/project-brief.md"
|