From 04470bd368aecfb7b024c89271ab5d9a6a758ed8 Mon Sep 17 00:00:00 2001 From: lanrtop Date: Sat, 3 Oct 2026 13:15:53 +0900 Subject: [PATCH] =?UTF-8?q?docs(blueprint/agent-client-parity):=20P8f=20?= =?UTF-8?q?=E8=A1=A5=E7=8B=AC=E7=AB=8B=E5=AE=A1=E6=9F=A5=E5=A4=84=E7=BD=AE?= =?UTF-8?q?=E3=80=81=E6=94=B9=E5=8A=A8=E6=96=AD=E8=A8=80=E6=8A=A5=E5=A4=87?= =?UTF-8?q?=E4=B8=8E=20known=5Flimits=EF=BC=8C=E9=93=BE=E8=B7=AF=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B=E4=BB=8D=E5=BE=85=20P8e=20=E5=90=88=E5=B9=B6=EF=BC=88?= =?UTF-8?q?P8f=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .blueprint/modules/agent-client-parity.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.blueprint/modules/agent-client-parity.md b/.blueprint/modules/agent-client-parity.md index 5e87eaf..42a5360 100644 --- a/.blueprint/modules/agent-client-parity.md +++ b/.blueprint/modules/agent-client-parity.md @@ -287,19 +287,20 @@ skill 只是五个接入面之一,兼容层覆盖全部五面。 ### 🔵 P8f — register_project 控制面投影 - status: in_progress - complexity: S -- files: src-tauri/src/commands/projects.rs, src-tauri/src/mcp_tools.rs, src-tauri/src/commands/templates.rs(实施扩展), src-tauri/src/commands/git_repos.rs(实施扩展), src/components/dashboard/BatchAddModal.tsx(实施扩展), docs/ai-context/project-context.yaml(T1) +- files: src-tauri/src/commands/projects.rs, src-tauri/src/mcp_tools.rs, src-tauri/src/commands/templates.rs(实施扩展), src-tauri/src/commands/git_repos.rs(实施扩展), src/components/dashboard/BatchAddModal.tsx(实施扩展), src-tauri/src/mcp_inject.rs(审查处置:路径键两函数改 pub(crate) 供复用), docs/ai-context/project-context.yaml(T1) - depends: P8-0 - acceptance: "agent_verify: [cargo test] 空路径、相对路径、不存在的路径、重复登记都被拒绝,不建目录、不执行 git init。[MCP 全局] 对临时 git 仓库依次调用 register_project、plan_onboarding、onboard_project,结果为 done;项目路由的 tools/list 不含 register_project。" - 为什么(Q7):create_project 只写 DB、MCP 里也没有登记项目的工具,agent 复刻不了人「新建 → 接入」的操作路径(违反同源同边界)。只开放在全局路由,写操作须用户确认;抽出 create_project 本体,连接由调用方注入。 -- 实现(2026-10-03,分支 feat/agent-client-parity-p8f):projects.rs 抽出登记本体 `register_project_records(conn, input)`(连接注入),UI `create_project` 只做取连接的薄包装,MCP `register_project` 经 `register_project_at(conn, path, name)` 换算入参后调同一本体——边界只写一处。本体只写数据库:不建目录、不执行 git init、不接入;依次拒绝空 ID、空路径、相对路径、不存在的目录、空名称、同一目录已登记、已存在的 ID;重复检查与 profile+workspace 插入放在同一 IMMEDIATE 事务(沿用 envoy 先查后插的做法),并发登记同一目录时后到者等前者提交后再查。workspace 插入由 `INSERT OR IGNORE` 改为 `INSERT`,ID 冲突不再静默返回成功。MCP `register_project`(schema 单独成函数,插在 scan_unregistered_projects 之后):入参 path(Windows 绝对路径,必填)与可选 name(缺省取目录名);project_id 由炼境生成 UUID(与 UI 选目录新建一致),归入当前空间(UI 首页按当前空间列项目);返回 JSON 并列入 STRUCTURED_TOOLS,structuredContent 带 project_id、name、path、space_id,摘要写明「只写登记、尚未接入,接入先 plan_onboarding 预览、经用户确认再 onboard_project」。is_project_scoped_tool 显式排除 register_project(与 unregister_project、setup_gitea_repo 并列),项目路由 tools/list 不含它,项目路由调用返回 scope_violation;annotations 为非只读、非破坏、非幂等。描述写明调用前须向用户列出目标目录与名称并取得确认。 -- 重复路径规范化(`projects::registration_path_key`,纯字面):去首尾空白 → `/` 统一为 `\` → 去 `\\?\` 长路径前缀(`\\?\UNC\x` 还原为 `\\x`)→ 合并重复分隔符(保留 UNC 开头的 `\\`)→ 去尾部分隔符 → 小写。比较对象是每条已有登记的 win_path 与 wsl_path 两列:Windows 路径原样;WSL 的 Linux 路径按 `\\wsl.localhost\\…` 换算(与 mcp_inject::project_root_with_conn 同口径),wsl_path 已是 Windows/UNC 形式的原样使用。命中即拒绝,错误写明「该目录已登记为项目 project_id=<已有 ID>(<登记路径>)……请直接使用该 project_id」。 -- 其他登记入口(UI 取舍):① 模板新建(create_project_from_template)要先建目录、再跑初始化命令、最后入库,过不了本体「目录须已存在」,所以保持原流程不变,只在建目录前复用 `ensure_path_not_registered`(目录已删但登记仍在时,不再新建出第二条同目录登记);② 批量添加(batch_create_projects)保持「跳过不报错」语义,同一目录已登记(含同批前项)计入 skipped,提示文案改为「ID 已存在或目录已登记」,不加存在性与绝对路径校验(扫描结果必为已存在目录);③ 挂载仓库(mount_repo)给出路径时同样拒绝同一目录。 -- 行为变化(报备):UI「添加项目」此前允许不选目录、允许相对或不存在的路径、ID 冲突时静默成功;现在与 MCP 共用本体,这些情况都报错(空路径登记曾让接入链路把文件写进炼境进程 cwd,见 mcp_inject::validate_registered_path 的 2026-07-15 回归)。WSL 平台以 wsl_path 为准校验,Linux 路径的存在性经 `\\wsl.localhost` 判定,settings.wsl_distro 与实际发行版不符时会被拒绝(此时 project_root 同样会解析错,提前暴露)。 -- verification: "2026-10-03:全量 cargo test 335 passed / 0 failed / 5 ignored(master 为 325,新增 10 条),编译 0 warning;pnpm typecheck 通过。projects 新增 7 条:registration_path_key 归并大小写、斜杠方向、尾部分隔符、重复分隔符、长路径前缀,UNC 保留双分隔符,不同目录不归并;空串、空白、相对路径(含 cwd 下真实存在的 src)、盘符相对路径、不存在的目录全部拒绝,零行写入、无孤儿 profile、不存在的目录未被创建;同一临时目录以大写、小写、正斜杠、尾部分隔符等 5 种写法及改走 WSL 平台再登记均被拒绝,错误含 project_id=first,表内仍 1 行,目录内没有 .git;WSL Linux 路径的登记能按 UNC 写法查到;ID 冲突报错且原登记不被改写;register_project_at 生成 UUID、名称取目录名、归入当前空间、存去掉首尾空白的路径,同目录再登记的错误含已有 project_id;批量添加跳过已登记目录与同批重复目录。mcp_tools 新增 3 条:全局 tools/list 含 register_project(required=[path]、非只读非破坏、描述含用户确认、不建目录、git init、project_id),项目路由 tools/list 不含,schema 加上 project_id 后 is_project_scoped_tool 仍为 false;项目路由调用 register_project 返回 scope_violation;成功回执 structuredContent 带 project_id、摘要写明尚未接入。变异验证(先暂存,改源码,跑 projects:: 与 mcp_tools::,再从暂存区还原)9 处全部被杀:去掉重复目录检查(改 IMMEDIATE 事务前后各验一次)、比较键不小写、不去尾部分隔符、不统一斜杠方向、去掉目录存在校验、去掉绝对路径校验、批量添加不跳过同目录、is_project_scoped_tool 不排除 register_project。" -- 没验什么:① acceptance 的 [MCP 全局] 链路 register_project → plan_onboarding → onboard_project 依赖 P8e 才有的两个 MCP 工具,本卡只实现与测试 register_project;**链路实测待 P8e 合并后由统筹会话完成**(要在运行中的炼境上登记一个临时 git 仓库,属写运行中 DB,按授权规则处理),卡片保持 🔵。② 没在运行中的炼境上调过 register_project(未重编译安装,且会写运行中 DB)。③ IMMEDIATE 事务防并发重复只有结构保证,没有并发测试。④ 模板新建与挂载仓库的重复目录检查没有单测(Tauri command 直接取连接池,模板路径还要建目录、跑命令),由共用的 find_registration_by_path 测试间接覆盖。⑤ UI 添加项目的报错只看了代码路径(ProjectModal 以 toast 显示错误),没有人工点过。⑥ 本卡为 S 级,未做独立审查。 -- known_limits: "① 规范化只做字面:`..`、符号链接、junction、8.3 短名与长名不归一;WSL 路径也按不区分大小写比较(Linux 上仅大小写不同的两个目录会被误判为同一目录,极少见)。② update_project 把路径改成一个已登记目录不拦(不在本卡范围);存量的同目录重复登记(如待用户处理的 beast-mode / 940dfbb4)不受影响,仍按统筹文档 §3 由用户执行数据合并。③ 批量添加的 wsl_convert 模式把 E:\x 存成 /mnt/e/x,与同目录的 Windows 登记比较键不同,查不出这类跨写法重复。④ scan_unregistered_projects 判定已登记仍是区分大小写的字面比较,可能把只差大小写的已登记目录列成未登记;对它调 register_project 会被本卡的重复检查拒绝并给出已有 project_id。" -- 有意偏差:① 未加 confirmed 参数——任务要求是「描述写明写操作须用户确认目标」;登记只写炼境 DB、可经 unregister_project 撤销,按 unregister_project 的先例只在描述里约束。② MCP 不收 project_id,统一由炼境生成 UUID,避免 agent 自拟 ID 与已有命名冲突。③ 重复目录检查扩到批量添加、模板新建、挂载仓库三个入口(files 标「实施扩展」):PR #34 审查 L1/L2 的根因是同一目录多条登记,只堵 create_project 和 MCP 两个入口治不了根。 -- T1:新 MCP 工具 register_project 已同步 docs/ai-context/project-context.yaml「MCP 工具接口」段(paths 补 projects.rs,doc 补路由、本体、规范化与入参语义);Tauri command 签名未变,src/lib/commands.ts 无需改动。 +- 实现(2026-10-03,分支 feat/agent-client-parity-p8f):projects.rs 抽出登记本体 `register_project_records(conn, input, requirement)`(连接注入),UI `create_project` 只做取连接的薄包装(requirement=ExistingDirOrPlaceholder),MCP `register_project` 经 `register_project_at(conn, path, name)` 换算入参后调同一本体(requirement=ExistingDir)——边界只写一处。本体只写数据库:不建目录、不执行 git init、不接入;依次拒绝空 ID、空路径(UI 不选目录的概念占位除外,见审查 M2)、相对路径、不存在的目录、空名称、同一目录已登记、已存在的 ID;目录校验与比较键在写事务外算(要读磁盘),重复检查与 profile+workspace 插入放在同一 IMMEDIATE 事务(沿用 envoy 先查后插的做法),并发登记同一目录时后到者等前者提交后再查。Windows 目录落库为 canonical 规范形式(审查 M1),本体返回落库的根目录。workspace 插入由 `INSERT OR IGNORE` 改为 `INSERT`,ID 冲突不再静默返回成功。MCP `register_project`(schema 单独成函数,插在 scan_unregistered_projects 之后):入参 path(Windows 绝对路径,必填)与可选 name(缺省取目录名);project_id 由炼境生成 UUID(与 UI 选目录新建一致),归入当前空间(UI 首页按当前空间列项目);返回 JSON 并列入 STRUCTURED_TOOLS,structuredContent 带 project_id、name、path、space_id,摘要写明「只写登记、尚未接入,接入先 plan_onboarding 预览、经用户确认再 onboard_project」。is_project_scoped_tool 显式排除 register_project(与 unregister_project、setup_gitea_repo 并列),项目路由 tools/list 不含它,项目路由调用返回 scope_violation;annotations 为非只读、非破坏、非幂等。描述写明调用前须向用户列出目标目录与名称并取得确认。 +- 同目录判定(审查后定稿):字面键 `projects::registration_path_key` = 去首尾空白 → `/` 统一为 `\` → 去 `\\?\` 长路径前缀(`\\?\UNC\x` 还原为 `\\x`)→ 交给 `mcp_inject::normalize_dsh_root`(合并重复分隔符、保留 UNC 开头的 `\\`、去尾部分隔符、小写)——与 DSH 白名单共用同一实现,口径不分叉(审查 L4 的部分处置)。已有登记一侧:每条只取按 platform 的有效根目录(Windows 取 win_path、WSL 取 wsl_path;取列与 Linux 路径换算同 mcp_inject::project_root_with_conn,wsl_path 已是 UNC 时原样使用——project_root_with_conn 对这种值会错误拼接,是已有缺陷,见审查 I2),切换平台残留在另一列的旧路径不参与(审查 L1),只算字面键、不读磁盘。新目录一侧:在写事务外算字面键与 canonical 键(`std::fs::canonicalize` 去 `\\?\` 前缀,消掉 8.3 短名、`.`/`..` 段,审查 L2),任一命中即拒绝,错误写明「该目录已登记为项目 project_id=<已有 ID>(<有效根目录>)……请直接使用该 project_id」。 +- 其他登记入口(UI 取舍):① 模板新建(create_project_from_template)要先建目录、再跑初始化命令、最后入库,过不了本体「目录须已存在」,所以保持原流程不变,只在建目录前复用 `ensure_path_not_registered`(目录已删但登记仍在时,不再新建出第二条同目录登记;此时目录不存在,只有字面键);② 批量添加(batch_create_projects)保持「跳过不报错」语义,同一目录已登记(含同批前项)计入 skipped,提示文案改为「ID 已存在或目录已登记」,不加存在性与绝对路径校验(扫描结果必为已存在目录);③ 挂载仓库抽出本体 `git_repos::mount_repo_records(conn, input)`:给出路径时在任何写入之前拒绝同一目录,profile 与 workspace 在同一 IMMEDIATE 事务写入,被拒绝或 workspace 冲突都不留孤儿 profile(审查 L3);④ scan_unregistered_projects 抽出 `scan_unregistered_projects_with(conn)`,已登记判定与父目录去重改用同一字面键(审查 M1 连带),末尾提示改为「经用户确认后 register_project 登记」。 +- 行为变化(报备):UI「添加项目」此前允许相对或不存在的路径、ID 冲突时静默成功;现在与 MCP 共用本体,这些情况都报错(UI 只能经文件夹选择器给路径,实际只影响「选过的目录后来被删」与 ID 冲突两种情形)。**不选目录的概念占位保持原行为**(审查 M2:首版曾一并拒绝,与 mcp-server 模块「2 个空路径概念占位按用户意图保留」冲突,已恢复;MCP 入口仍拒绝空路径)。Windows 目录落库由原样改为 canonical 规范形式(UI 选择器给的本就是规范长名,实际只影响 MCP 入参)。WSL 平台以 wsl_path 为准校验,Linux 路径的存在性经 `\\wsl.localhost` 判定,settings.wsl_distro 与实际发行版不符时会被拒绝(此时 project_root 同样会解析错,提前暴露)。 +- verification: "2026-10-03 审查处置后:全量 cargo test 342 passed / 0 failed / 5 ignored(首版 335,审查处置新增 7 条),编译 0 warning;pnpm typecheck 通过。审查处置新增:projects 4 条——ui_registration_keeps_pathless_placeholder(UI 入口空串/空白/WSL 空路径占位成功且互不拦截、占位不能复用已存在 ID、MCP 入口拒绝占位)、duplicate_lookup_matches_dotted_or_short_input_by_canonical_key(已有行以正斜杠+尾部分隔符落库,新目录以 `sub\\..` 与本机 8.3 短名 TEMP 写法给出,均靠 canonical 键命中)、duplicate_lookup_ignores_stale_column_of_other_platform(platform=wsl 的行残留 win_path 不挡该 Windows 目录登记)、register_project_at_warns_when_directory_carries_another_project_id(遗留 context.json 的旧 ID 进 warning,文件不被改写);git_repos 1 条 mount_rejections_leave_no_orphan_profile(同目录拒绝挂载与 workspace ID 冲突都不留 profile,正常挂载成功);mcp_tools 2 条——register_project_dispatch_follows_schema_parameter_names(按 schema 声明的参数名构造入参经分发函数登记,回执 path 为规范目录、缺 path 报「路径为空」)、scan_unregistered_matches_registrations_regardless_of_spelling(正斜杠大写带尾分隔符的登记与反斜杠登记都不被列为未登记,两种写法的父目录只扫一次)。改动的既有断言(报备):register_project_at 测试原断言「存入去掉首尾空白的路径」改为「存 canonical 规范形式」,入参改为正斜杠+尾部分隔符+首尾空白(审查 M1 的要求本身);ID 冲突测试的「原登记不被改写」改为与 canonical 形式比较;同目录写法测试补 canonical 大写、`sub\\..`、`sub\\..\\.` 三种写法并断言本体返回规范根目录;WSL Linux 路径查找测试改为直接用字面键查(find_registration_by_path 现会 canonicalize,测试不应访问 \\\\wsl.localhost 唤起本机 WSL);其余测试只补 requirement 参数。变异验证(先暂存,改源码,跑 projects::、git_repos::、mcp_tools::,再从暂存区还原)两轮 9 处全部被杀:第一轮 4 处——落库不规范化(taken_id、register_project_at、同目录写法、分发 4 条失败)、关闭 UI 占位(占位测试失败)、mount 事务改为出错也提交(mount 测试失败)、scan 改回字面比较(scan 测试失败),共 7 条失败;第二轮 5 处——去掉 canonical 键(`..`/8.3 查找、同目录写法、register_project_at 与批量添加的 8.3 再登记失败)、已有行不按 platform 取列(残留列与 WSL Linux 路径两条失败)、warning 恒为空(warning 测试失败)、分发读错参数名(分发测试失败)、字面键不去长路径前缀(键测试失败),共 9 条失败。首版(审查前):全量 cargo test 335 passed / 0 failed / 5 ignored(master 为 325,新增 10 条),编译 0 warning;pnpm typecheck 通过。projects 新增 7 条:registration_path_key 归并大小写、斜杠方向、尾部分隔符、重复分隔符、长路径前缀,UNC 保留双分隔符,不同目录不归并;空串、空白、相对路径(含 cwd 下真实存在的 src)、盘符相对路径、不存在的目录全部拒绝,零行写入、无孤儿 profile、不存在的目录未被创建;同一临时目录以大写、小写、正斜杠、尾部分隔符等 5 种写法及改走 WSL 平台再登记均被拒绝,错误含 project_id=first,表内仍 1 行,目录内没有 .git;WSL Linux 路径的登记能按 UNC 写法查到;ID 冲突报错且原登记不被改写;register_project_at 生成 UUID、名称取目录名、归入当前空间、存去掉首尾空白的路径,同目录再登记的错误含已有 project_id;批量添加跳过已登记目录与同批重复目录。mcp_tools 新增 3 条:全局 tools/list 含 register_project(required=[path]、非只读非破坏、描述含用户确认、不建目录、git init、project_id),项目路由 tools/list 不含,schema 加上 project_id 后 is_project_scoped_tool 仍为 false;项目路由调用 register_project 返回 scope_violation;成功回执 structuredContent 带 project_id、摘要写明尚未接入。变异验证(先暂存,改源码,跑 projects:: 与 mcp_tools::,再从暂存区还原)9 处全部被杀:去掉重复目录检查(改 IMMEDIATE 事务前后各验一次)、比较键不小写、不去尾部分隔符、不统一斜杠方向、去掉目录存在校验、去掉绝对路径校验、批量添加不跳过同目录、is_project_scoped_tool 不排除 register_project。" +- 没验什么:① acceptance 的 [MCP 全局] 链路 register_project → plan_onboarding → onboard_project 依赖 P8e 才有的两个 MCP 工具,本卡只实现与测试 register_project;**链路实测待 P8e 合并后由统筹会话完成**(要在运行中的炼境上登记一个临时 git 仓库,属写运行中 DB,按授权规则处理),卡片保持 🔵。② 没在运行中的炼境上调过 register_project(未重编译安装,且会写运行中 DB)。③ IMMEDIATE 事务防并发重复只有结构保证,没有并发测试。④ 模板新建的重复目录检查没有单测(Tauri command 直接取连接池,还要建目录、跑命令),由共用的 ensure_path_not_registered/find_registration_by_path 测试间接覆盖;挂载仓库已抽本体并补单测。⑤ UI 添加项目的报错与占位登记只看了代码路径(ProjectModal 以 toast 显示错误),没有人工点过。⑥ canonicalize 在 junction、符号链接、subst 盘、映射网络盘上的行为(解析到真实目标、映射盘变 UNC)没有实测,测试只覆盖本机 TEMP 的 8.3 短名与 `..` 段。⑦ 审查 M2 的产品决定(是否保留无目录的概念占位、是否改为显式占位概念)没有问过用户,本 PR 只恢复原行为。 +- known_limits: "① 已有登记一侧只算字面键:已有行以 8.3 短名或经 junction 的路径落库、新目录用长名或真实路径给出时查不出(UI 选择器给的是规范长名,P8f 之后 MCP 落库也是规范形式,新增登记不会再产生这种行);WSL 路径也按不区分大小写比较(Linux 上仅大小写不同的两个目录会被误判为同一目录,极少见);`\\\\wsl$` 与 `\\\\wsl.localhost` 两种写法不归一。② update_project 把路径改成一个已登记目录不拦(审查 I1,转后续小修);存量的同目录重复登记(如待用户处理的 beast-mode / 940dfbb4)不受影响,仍按统筹文档 §3 由用户执行数据合并。③ 批量添加的 wsl_convert 模式把 E:\\x 存成 /mnt/e/x,与同目录的 Windows 登记比较键不同,查不出这类跨写法重复。④ scan_unregistered_projects 只按 win_path 推导父目录、已有行只算字面键:8.3 短名登记的目录仍可能被列为未登记,对它调 register_project 会被 canonical 键拒绝并给出已有 project_id。⑤ Windows 目录落库为 canonical 形式,junction、符号链接、subst 盘解析到真实目标,映射网络盘变成 UNC:经 junction 路径打开的客户端会话,与按字面比较登记路径的消费者对不上(DSH 白名单同时写字面键与 canonical 键,不受影响)。⑥ 路径比较键仍有多份实现:normalize_trust_path(Codex 信任判断)未并入,WSL 发行版查询与 Linux 路径转 UNC 在 projects.rs、mcp_inject.rs、publish.rs 各有一份(审查 L4,转后续小修)。" +- 有意偏差:① 未加 confirmed 参数——任务要求是「描述写明写操作须用户确认目标」;登记只写炼境 DB、可经 unregister_project 撤销,按 unregister_project 的先例只在描述里约束。② MCP 不收 project_id,统一由炼境生成 UUID,避免 agent 自拟 ID 与已有命名冲突。③ 重复目录检查扩到批量添加、模板新建、挂载仓库三个新建入口(files 标「实施扩展」):PR #34 审查 L1/L2 的根因是同一目录多条登记,只堵 create_project 和 MCP 两个入口治不了根;编辑入口 update_project 仍未堵(审查 I1,转后续小修)。 +- T1:新 MCP 工具 register_project 已同步 docs/ai-context/project-context.yaml「MCP 工具接口」段(paths 补 projects.rs,doc 补路由、本体、同目录判定、落库规范化、UI 占位例外、warning 与入参语义,以及 scan_unregistered_projects 与 mount_repo 的同口径);Tauri command 签名未变,src/lib/commands.ts 无需改动。 +- review: "2026-10-03 独立只读审查 bd4ebaf,结论 fix_then_merge,10 条发现处置:M1 [中] MCP 入参原样落库(正斜杠、尾部分隔符、长路径前缀、8.3、`..`),scan_unregistered_projects 按字面比较会自相矛盾 → 已修:本体对 Windows 目录 canonicalize 后去长路径前缀落库,register_project_at 回执 path 为落库值、名称缺省按规范形式取目录名(相对路径不 canonicalize,交本体拒绝);scan 的已登记判定与父目录去重改用 registration_path_key。M2 [中] UI 不选目录的概念占位被取消,超出 P8f 范围且与 mcp-server 模块记录的用户用法冲突 → 已恢复原行为:本体新增 RootRequirement,UI 入口有效路径为空时只写 profile+workspace、不校验目录、不查重复,MCP 入口仍拒绝;是否改为显式占位概念交统筹会话问用户(需求澄清),本 PR 不预设答案。L1 [低] 已有登记两列都比,残留列误拦 → 已修:按 platform 只取有效根目录(registration_root),查询补 platform 列。L2 [低] 8.3、`..`、末尾点等漏判 → 已修:新目录在写事务外另算 canonical 键,两键比对已有行的字面键(已有行不读磁盘,写锁内无 I/O)。L3 [低] mount_repo 检查在插 profile 之后且无事务 → 已修:抽 mount_repo_records(连接注入),检查前移、整体 IMMEDIATE 事务;前端先 gitClone 再 mount 的白克隆属 UI 范围,未改。L4 [低] 路径比较键多份实现 → 部分修:registration_path_key 改为复用 mcp_inject::normalize_dsh_root + strip_verbatim_prefix(两函数改 pub(crate));normalize_trust_path、WSL 发行版查询与 UNC 换算的第三份拷贝统一抽 crate::path_key 转后续小修(会动 P4a/P8d 热点 mcp_inject.rs)。I1 [信息] update_project 仍可造同目录登记 → 转后续小修:UI 编辑整表提交,须「有效根目录实际变化才检查 + 排除自身」,否则存量重复登记(beast-mode)连改名都会被拦,单独做更稳。I2 [信息] project_root_with_conn 对 UNC 形式的 wsl_path 错误拼接(已有缺陷)→ 不在本 PR 改(mcp_inject.rs 热点),转后续小修;卡片与代码注释里「同口径」改为只指取列与 Linux 路径换算。I3 [信息] 重登目录遗留旧 context.json 无提示 → 已修:register_project_at 只读检查,project_id 不同时回执与摘要带 warning(不沿用旧 ID)。I4 [信息] 分发分支无测试 → 已修:分发抽成 register_project_call(conn, args),测试按 schema 声明的参数名构造入参;IMMEDIATE 并发仍无测试(见没验什么③)。" ### 📋 P8g — UI 一键接入 - status: todo