feat(projects/parity): register_project 控制面投影,登记拒绝同目录重复登记并落库规范目录(P8f) #37

Merged
lanrtop merged 6 commits from feat/agent-client-parity-p8f into master 2026-10-03 12:20:10 +08:00
Owner

做了什么

P8f:给 agent 补上「登记项目」的控制面投影,并让所有新建登记入口拒绝同一目录的重复登记(承接 PR #34 审查 L1/L2)。

  • 登记本体 projects::register_project_records(conn, input, requirement)(连接注入):UI create_project 与 MCP register_project 共用。只写数据库,不建目录、不执行 git init、不接入。拒绝空 ID、空路径、相对路径、不存在的目录、空名称、同一目录已登记、已存在的 ID。workspace 插入由 INSERT OR IGNORE 改为 INSERT,ID 冲突不再静默成功。重复检查和插入在同一个 IMMEDIATE 事务里;目录校验和比较键在事务外计算,因为要读磁盘。
  • MCP register_project:只在全局路由开放(is_project_scoped_tool 显式排除,项目路由调用返回 scope_violation)。入参是 path(Windows 绝对路径)和可选的 name;project_id 由炼境生成 UUID,归入当前空间。返回 structuredContent(project_id / name / 落库的规范目录 / space_id / 可选的 warning)。描述里要求调用前先向用户列出目标并取得确认。
  • 同目录判定:已有登记按 platform 只取有效根目录,只算字面键 registration_path_key,和 DSH 白名单共用 normalize_dsh_root。新目录同时算字面键和 canonical 键(canonicalize 后去掉 \\?\ 前缀),所以 8.3 短名和 .. 段也能识别。错误信息给出已有的 project_id。
  • 落库规范化:Windows 目录按 canonical 形式落库(真实大小写、反斜杠);WSL 路径原样落库。
  • 其他入口:批量添加遇到同一目录时跳过(计入 skipped)。模板新建在建目录前做同一检查。挂载仓库抽出了 mount_repo_records 本体:检查放在任何写入之前,profile 和 workspace 在同一事务里写入。scan_unregistered_projects 改用同一比较键。
  • T1:docs/ai-context/project-context.yaml 已同步。Tauri command 签名没变,src/lib/commands.ts 不用改。

自验

  • 全量 cargo test:342 passed / 0 failed / 5 ignored,编译 0 warning(master 是 325:首版新增 10 条,审查处置又新增 7 条)。
  • pnpm typecheck 通过。
  • 变异验证(先暂存、改源码、跑 projects / git_repos / mcp_tools、再从暂存区还原),两轮共 9 处,全部被杀:
    • 第一轮 4 处,对应用例共 7 条失败:落库不做规范化、关闭 UI 占位、mount 事务改成出错也提交、scan 改回字面比较。
    • 第二轮 5 处,对应用例共 9 条失败:去掉 canonical 键、已有行不按 platform 取列、warning 恒为空、分发读错参数名、字面键不去长路径前缀。
    • 首版另有 9 处变异,全部被杀,见卡片 verification。
  • 改了的既有断言(报备):register_project_at 测试原来断言「存入去掉首尾空白的路径」,现在改为「存 canonical 规范形式」,入参也换成正斜杠 + 尾部分隔符 + 首尾空白。这一处正是审查 M1 要求的行为变化。ID 冲突测试的「原登记不被改写」改为和 canonical 形式比较。WSL Linux 路径查找测试改为直接按字面键查,避免测试访问 \\wsl.localhost 唤起本机 WSL。

审查处置(独立只读审查 bd4ebaf,结论 fix_then_merge,10 条)

# 级别 处置
M1 中 已修:MCP 入参原样落库,导致 scan 自相矛盾。现在 canonical 落库,scan 也改用同一比较键
M2 中 已恢复原行为:UI 不选目录时仍可建概念占位登记(RootRequirement::ExistingDirOrPlaceholder),MCP 入口仍拒绝空路径。是否改为显式的占位概念,需要统筹会话问用户
L1 低 已修:已有登记按 platform 只取有效根目录,切换平台后残留在另一列的路径不再误拦
L2 低 已修:新目录在事务外另算 canonical 键,写锁内不读磁盘
L3 低 已修:mount_repo 的检查放到���入之前,整体包进事务,被拒绝时不留孤儿 profile。前端先克隆再挂载属于 UI 范围,没改
L4 低 部分修:registration_path_key 改为复用 DSH 的两个函数(改为 pub(crate))。统一抽 crate::path_key 转后续小修,因为会动 P4a / P8d 的热点文件 mcp_inject.rs
I1 信息 转后续小修:update_project 改路径时不拦同目录。修的时候要满足「有效根目录实际变化才检查,并排除自身」,否则存量重复登记连改名都会被拦
I2 信息 转后续小修:project_root_with_conn 对 UNC 形式的 wsl_path 会错误拼接(已有缺陷,mcp_inject.rs 是热点文件)。代码注释和卡片里「同口径」的说法已改准确
I3 信息 已修:目录里遗留了别的 project_id 的 context.json 时,回执和摘要里带 warning(只读提示,不沿用旧 ID)
I4 信息 已修:分发抽成 register_project_call(conn, args),按 schema 声明的参数名构造入参来测试

待用户决定

  • M2 概念占位:UI「添加项目」可以不选目录,建出无路径的占位登记(mcp-server 模块记录了用户保留的 2 个占位)。本 PR 维持这个原行为。是否改为显式的占位概念,或者取消占位,需要用户决定。
  • acceptance 里「空路径拒绝」对 MCP 入口成立;UI 入口的占位按上面的原行为作为例外保留。

没验什么

  1. acceptance 里的 [MCP 全局] 链路 register_project → plan_onboarding → onboard_project 依赖 P8e。链路实测要等 P8e 合并后由统筹会话完成,这一步会写运行中的 DB,卡片保持 🔵。
  2. 没有在运行中的炼境上调过 register_project:没有重编译安装,而且调用会写运行中的 DB。
  3. IMMEDIATE 事务防并发重复只有结构上的保证,没有并发测试。
  4. 模板新建的重复检查没有单测,只通过共用函数的测试间接覆盖。
  5. UI 添加项目的报错和占位登记只看过代码路径,没有人工点过。
  6. canonicalize 在 junction、符号链接、subst 盘、映射网络盘上的行为(解析到真实目标、映射盘变成 UNC)没有实测;测试只覆盖了本机 TEMP 的 8.3 短名和 .. 段。
  7. 已有登记一侧只算字面键:已有行如果以 8.3 短名或经 junction 的路径落库,就查不出来;\\wsl$ 和 \\wsl.localhost 两种写法也没有归一。详见卡片 known_limits。

后续小修建议

  • update_project 改路径时复用重复检查(I1)。
  • project_root_with_conn 对不以 / 开头的 wsl_path 原样使用(I2)。
  • 抽出 crate::path_key,统一登记、DSH 白名单、Codex 信任判断、WSL UNC 换算(L4)。
  • 前端挂载仓库时先做检查再 gitClone(L3 的 UI 部分)。

🤖 Generated with Claude Code

## 做了什么 P8f:给 agent 补上「登记项目」的控制面投影,并让所有新建登记入口拒绝同一目录的重复登记(承接 PR #34 审查 L1/L2)。 - **登记本体** `projects::register_project_records(conn, input, requirement)`(连接注入):UI `create_project` 与 MCP `register_project` 共用。只写数据库,不建目录、不执行 git init、不接入。拒绝空 ID、空路径、相对路径、不存在的目录、空名称、同一目录已登记、已存在的 ID。workspace 插入由 `INSERT OR IGNORE` 改为 `INSERT`,ID 冲突不再静默成功。重复检查和插入在同一个 IMMEDIATE 事务里;目录校验和比较键在事务外计算,因为要读磁盘。 - **MCP `register_project`**:只在全局路由开放(`is_project_scoped_tool` 显式排除,项目路由调用返回 scope_violation)。入参是 `path`(Windows 绝对路径)和可选的 `name`;project_id 由炼境生成 UUID,归入当前空间。返回 structuredContent(project_id / name / 落库的规范目录 / space_id / 可选的 warning)。描述里要求调用前先向用户列出目标并取得确认。 - **同目录判定**:已有登记按 platform 只取有效根目录,只算字面键 `registration_path_key`,和 DSH 白名单共用 `normalize_dsh_root`。新目录同时算字面键和 canonical 键(canonicalize 后去掉 `\\?\` 前缀),所以 8.3 短名和 `..` 段也能识别。错误信息给出已有的 project_id。 - **落库规范化**:Windows 目录按 canonical 形式落库(真实大小写、反斜杠);WSL 路径原样落库。 - **其他入口**:批量添加遇到同一目录时跳过(计入 skipped)。模板新建在建目录前做同一检查。挂载仓库抽出了 `mount_repo_records` 本体:检查放在任何写入之前,profile 和 workspace 在同一事务里写入。`scan_unregistered_projects` 改用同一比较键。 - T1:`docs/ai-context/project-context.yaml` 已同步。Tauri command 签名没变,`src/lib/commands.ts` 不用改。 ## 自验 - 全量 `cargo test`:342 passed / 0 failed / 5 ignored,编译 0 warning(master 是 325:首版新增 10 条,审查处置又新增 7 条)。 - `pnpm typecheck` 通过。 - 变异验证(先暂存、改源码、跑 projects / git_repos / mcp_tools、再从暂存区还原),两轮共 9 处,全部被杀: - 第一轮 4 处,对应用例共 7 条失败:落库不做规范化、关闭 UI 占位、mount 事务改成出错也提交、scan 改回字面比较。 - 第二轮 5 处,对应用例共 9 条失败:去掉 canonical 键、已有行不按 platform 取列、warning 恒为空、分发读错参数名、字面键不去长路径前缀。 - 首版另有 9 处变异,全部被杀,见卡片 verification。 - 改了的既有断言(报备):register_project_at 测试原来断言「存入去掉首尾空白的路径」,现在改为「存 canonical 规范形式」,入参也换成正斜杠 + 尾部分隔符 + 首尾空白。这一处正是审查 M1 要求的行为变化。ID 冲突测试的「原登记不被改写」改为和 canonical 形式比较。WSL Linux 路径查找测试改为直接按字面键查,避免测试访问 `\\wsl.localhost` 唤起本机 WSL。 ## 审查处置(独立只读审查 bd4ebaf,结论 fix_then_merge,10 条) | # | 级别 | 处置 | |---|---|---| | M1 | 中 | 已修:MCP 入参原样落库,导致 scan 自相矛盾。现在 canonical 落库,scan 也改用同一比较键 | | M2 | 中 | 已恢复原行为:UI 不选目录时仍可建概念占位登记(`RootRequirement::ExistingDirOrPlaceholder`),MCP 入口仍拒绝空路径。**是否改为显式的占位概念,需要统筹会话问用户** | | L1 | 低 | 已修:已有登记按 platform 只取有效根目录,切换平台后残留在另一列的路径不再误拦 | | L2 | 低 | 已修:新目录在事务外另算 canonical 键,写锁内不读磁盘 | | L3 | 低 | 已修:mount_repo 的检查放到���入之前,整体包进事务,被拒绝时不留孤儿 profile。前端先克隆再挂载属于 UI 范围,没改 | | L4 | 低 | 部分修:registration_path_key 改为复用 DSH 的两个函数(改为 `pub(crate)`)。统一抽 `crate::path_key` 转后续小修,因为会动 P4a / P8d 的热点文件 mcp_inject.rs | | I1 | 信息 | 转后续小修:update_project 改路径时不拦同目录。修的时候要满足「有效根目录实际变化才检查,并排除自身」,否则存量重复登记连改名都会被拦 | | I2 | 信息 | 转后续小修:project_root_with_conn 对 UNC 形式的 wsl_path 会错误拼接(已有缺陷,mcp_inject.rs 是热点文件)。代码注释和卡片里「同口径」的说法已改准确 | | I3 | 信息 | 已修:目录里遗留了别的 project_id 的 context.json 时,回执和摘要里带 warning(只读提示,不沿用旧 ID) | | I4 | 信息 | 已修:分发抽成 `register_project_call(conn, args)`,按 schema 声明的参数名构造入参来测试 | ## 待用户决定 - **M2 概念占位**:UI「添加项目」可以不选目录,建出无路径的占位登记(mcp-server 模块记录了用户保留的 2 个占位)。本 PR 维持这个原行为。是否改为显式的占位概念,或者取消占位,需要用户决定。 - acceptance 里「空路径拒绝」对 MCP 入口成立;UI 入口的占位按上面的原行为作为例外保留。 ## 没验什么 1. acceptance 里的 [MCP 全局] 链路 register_project → plan_onboarding → onboard_project 依赖 P8e。**链路实测要等 P8e 合并后由统筹会话完成**,这一步会写运行中的 DB,卡片保持 🔵。 2. 没有在运行中的炼境上调过 register_project:没有重编译安装,而且调用会写运行中的 DB。 3. IMMEDIATE 事务防并发重复只有结构上的保证,没有并发测试。 4. 模板新建的重复检查没有单测,只通过共用函数的测试间接覆盖。 5. UI 添加项目的报错和占位登记只看过代码路径,没有人工点过。 6. canonicalize 在 junction、符号链接、subst 盘、映射网络盘上的行为(解析到真实目标、映射盘变成 UNC)没有实测;测试只覆盖了本机 TEMP 的 8.3 短名和 `..` 段。 7. 已有登记一侧只算字面键:已有行如果以 8.3 短名或经 junction 的路径落库,就查不出来;`\\wsl$` 和 `\\wsl.localhost` 两种写法也没有归一。详见卡片 known_limits。 ## 后续小修建议 - update_project 改路径时复用重复检查(I1)。 - project_root_with_conn 对不以 `/` 开头的 wsl_path 原样使用(I2)。 - 抽出 `crate::path_key`,统一登记、DSH 白名单、Codex 信任判断、WSL UNC 换算(L4)。 - 前端挂载仓库时先做检查再 gitClone(L3 的 UI 部分)。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
lanrtop added 6 commits 2026-10-03 12:17:32 +08:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- projects:从 create_project 抽出登记本体 register_project_records(连接注入),
  UI create_project 与 MCP register_project 共用;只写数据库,不建目录、不执行 git init、不接入。
  拒绝空 ID、空路径、相对路径、不存在的目录、空名称、同一目录已登记、已存在的 ID;
  重复检查与插入放进同一 IMMEDIATE 事务;workspace 插入去掉 OR IGNORE,ID 冲突不再静默成功
- 同目录判定 registration_path_key:去长路径前缀、斜杠统一、合并重复分隔符、去尾部分隔符、
  不区分大小写;win_path 与 wsl_path 两列都比较,WSL Linux 路径按 wsl.localhost 换算;
  错误信息给出已有 project_id(根治 PR #34 审查 L1/L2:同目录两条登记互相抢占 Codex 托管块)
- mcp_tools:新增 register_project(path 必填、name 可选,project_id 由炼境生成 UUID,归入当前空间,
  structuredContent 返回);只开放在全局路由,is_project_scoped_tool 显式排除;描述写明须用户确认目标
- 批量添加对已登记目录计入 skipped,模板新建与挂载仓库复用同一重复目录检查
- project-context.yaml「MCP 工具接口」段同步(T1)

cargo test 335 passed / 0 failed / 5 ignored(新增 10 条),0 warning;pnpm typecheck 通过;
9 处变异全部被对应用例捕获。

Feature-Confirmed: true
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
卡片保持 🔵:acceptance 的 [MCP 全局] register_project → plan_onboarding → onboard_project
依赖 P8e 才有的两个 MCP 工具。另记行为变化(UI 添加项目改为拒绝空路径、相对或不存在的路径、
ID 冲突)、其他登记入口的取舍、没验什么、known_limits 与有意偏差。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- M1:Windows 目录 canonicalize 去长路径前缀后落库,register_project 回执带落库目录;
  scan_unregistered_projects 的已登记判定与父目录去重改用同一比较键
- M2:UI 添加项目不选目录时沿用既有概念占位(RootRequirement),MCP 入口仍拒绝空路径
- L1:已有登记按 platform 只取有效根目录,切换平台残留列不再误拦
- L2:新目录在写事务外另算 canonical 键,识别 8.3 短名与 .. 段
- L3:mount_repo 抽本体,重复检查前移、profile 与 workspace 同一 IMMEDIATE 事务
- L4(部分):registration_path_key 复用 normalize_dsh_root 与 strip_verbatim_prefix
- I3:目录遗留别的 project_id 的 context.json 时回执带 warning
- I4:register_project 分发抽成函数,按 schema 参数名测试
- 改动的既有断言:register_project_at 落库由「去首尾空白的原样路径」改为规范形式

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(blueprint/agent-client-parity): P8f 补独立审查处置、改动断言报备与 known_limits,链路实测仍待 P8e 合并(P8f)
All checks were successful
Push & PR Check / check (push) Successful in 1m6s
Push & PR Check / check (pull_request) Successful in 1m1s
04470bd368
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lanrtop merged commit 55dc6371bc into master 2026-10-03 12:20:10 +08:00
Sign in to join this conversation.
No reviewers
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: lanrtop/dev-manager-tauri#37
No description provided.