fix(repo): 避免默认目录模式扫描用户主目录 - #622
Conversation
deepcoldy
left a comment
There was a problem hiding this comment.
首次 Review(Claude)——无阻塞,建议进入复审
在隔离 worktree 中将 PR head dfad7cc2 合入当前 master fdb105a8(PR base 是 b30e8949,master 已前移,故必须重验)后逐项核实。merge-tree 零冲突;master 期间对 command-handler.ts / command-handler.test.ts 的改动(#588 /restart 重构)与本 PR 的 resolveRepoSelection 改动在完全不相交的代码区,clean merge 属实。
三处改动逻辑(互相耦合,缺一不可)
1. getProjectScanDirs(根因修复) — 旧逻辑 workingDirs?.length ? … : parseWorkingDirList(workingDir ?? '~'):只配 defaultWorkingDir 时 workingDirs/workingDir 皆空 → 回退 ~ → HOME 成为扫描根。新增第三档 effectiveDefaultWorkingDir(cfg) ?? '~',扫描根钉在 defaultWorkingDir/启用的 oncall 目录。
2. resolveRepoSelection 重排序 — 删掉进入函数就 scanMultipleProjects(所有 roots) 的前置全量扫描。新顺序:直接命中的候选目录优先(describeProjectDir 描述 git ref)→ 显式/相对路径未命中即 null → 仅 bare name 未命中才递归扫描按 basename 匹配。这正是消除 /repo tmp 在直接命中 ~/Code/tmp 前先递归遍历 HOME(触发 macOS TCC 弹窗)的关键。
3. describeProjectDir worktree 感知(必要补偿) — 重排序后,显式解析一个 linked worktree 会走「直接描述该目录」分支;naive basename(dir) 会显示 linked 目录名(proj-feature)。改为 git worktree list --porcelain 取主 worktree basename,保住原来的 proj (branch) 标签。git 出错时 fallback 到 basename(dir),非 git 目录 isValidGitMarker 提前返回,均优雅降级。
输入域枚举(判定/迁移类改动必做)
workingDirs:undefined /[]/['']/['a','b']/ 误配字符串 —— 新的parseWorkingDirList+ 三档优先级严格比旧的?.length更健壮(旧代码遇['']会当成合法目录;遇误配字符串会逐字符迭代)。workingDir:undefined /''/ 合法值 —— 唯一行为变化即预期的「defaultWorkingDir/oncall 取代 HOME」。- 全局 config 回退路径:
config.daemon.workingDirs恒非空(至少['~']),parseWorkingDirList重解析对已 split 过的数组幂等,行为不变。
resolver 全路径核对(无回归)
| 场景 | 旧 | 新 | 结论 |
|---|---|---|---|
| 显式路径→已扫描 repo | 精确匹配 "name (branch)" | 直接命中 describeProjectDir | 同 |
| 显式路径→linked worktree | projects 内含 worktree,标签=主仓名 | describeProjectDir worktree 感知 | 同(靠改动 3 保住) |
| 显式路径→roots 外 repo | 宽松兜底 describeProjectDir | 直接命中 describeProjectDir | 同 |
| bare→一级项目 | 精确匹配 | 直接命中 | 同 |
| bare→更深嵌套项目 | 按 basename 扫描匹配 | 未命中→扫描按 basename | 同(line 107 覆盖) |
| 相对路径 | 精确/兜底 | 直接命中/null |
同(旧本就把 basename 搜索 gate 在 !isExplicitPath) |
| 唯一语义变化 | bare name 同名时优先深层嵌套 repo | 优先直接命中的顶层目录 | 可辩护的细化,贴合 /cd 语义;picker 仍可选嵌套 |
实测(隔离 worktree,PR 合入当前 master,先装依赖再 build)
pnpm build:✅ 通过(exit 0)test/repo-selection.test.ts:9/9(日志确认直接命中零Scanned行、嵌套仅一行——递归扫描确被跳过)test/session-manager-scan.test.ts:11/11test/command-handler.test.ts:206/206- 三文件同跑(mock-leak 检查):226/226
- 回归面
project-scanner+card-handler-repo-select+card-builder:217/217 git diff --check:clean
PR body 提到的 file-lock 并发用例在本机偶发失败——本 PR 未触碰 file-lock,属已知机器级 flaky,与本改动无关(我未跑全量 10,847,只钉定受影响面)。
结论
根因定位准确(扫描根 + 解析顺序,而非 scanner 权限处理);三处改动耦合正确、测试覆盖到位、跨 CLI/PTY/Tmux/macOS/Linux 公共路径无特判、无吞错。首审未发现阻塞项。待 @codex 复审 + 申晗确认后再合码。
|
To use Codex here, create a Codex account and connect to github. |
复审进行中(Codex)已核对 PR head
会在当前 master + PR head 的隔离验证态运行针对性测试和 |
deepcoldy
left a comment
There was a problem hiding this comment.
Codex 复审:通过,无阻塞
复审锚定 PR head dfad7cc2,并将其 clean merge 到当前 master fdb105a8 的验证态执行;结束后已撤销本地验证 merge,未推送、未合码。
1. resolver 重排:未发现“合法旧路径变 null”
用真实目录 / Git 仓库做了旧、新算法差分,覆盖:bare name 一级 repo、深层 basename repo、含 / 的相对路径、绝对路径、linked worktree、普通目录、cwd fallback、不存在路径、多扫描根,共 9 类。所有实际存在且可访问的路径,旧、新返回值的 nullability 一致。
找到的唯一“旧非 null、新 null”是:git worktree list 尚残留记录、但 worktree 目录已经被删除时,显式传入该失效路径。旧实现会从扫描结果返回一个不存在的目录;新实现按函数契约返回 null。这是纠正无效结果,不是可用路径回归。
另确认一项预期的优先级细化:若前一个扫描根有一级普通目录 shadow,后一个扫描根深处有同名 Git repo,旧实现选深层 repo,新实现选直接命中的一级目录。它与本 PR“直接候选优先”及 /cd 的宽松语义一致,不构成阻塞。
2. describeProjectDir Git 边界
真实 Git fixture 结果:
- main worktree →
主仓目录名 (main); - linked worktree → 仍使用主仓目录名,分支为 linked branch;
- detached、无精确 tag → 主仓目录名 + 7 位 SHA;tag / SHA fallback 的既有 scanner 单测也通过;
- 普通非 Git 目录 →
null; - bare repo 根目录 →
null,因为既有isValidGitMarker只认工作树内.gitmarker。resolver 仍会把 bare 目录作为普通可进入目录返回,因此这是原有扫描边界,不是本 PR 引入的退化。
git worktree list --porcelain 失败时仍回退当前目录 basename,getGitRef 独立降级到 branch → exact tag → short SHA → unknown,没有把失败向外扩散。
3. getProjectScanDirs 回退与幂等性
用真实 registerBot + 全局 config 跑了 7 组 per-bot、3 组 global fallback:
workingDirs → workingDir → effectiveDefaultWorkingDir/defaultOncall → ~优先级符合设计;- comma string、空数组/空项、
~展开后的重复项均按预期处理; defaultWorkingDir优先于同时启用的defaultOncall;- 每组连续调用 5 次结果完全一致,输入 config 未被修改;
- 无 bot 的全局
workingDirs → workingDir → ~回退同样稳定。
验证结果
pnpm build:✅- 受影响回归面 6 文件:443/443(repo selection、session scan、command handler、project scanner、repo card handler、card builder)
- 对抗性真实 Git / resolver 差分:✅
- config 回退与重复调用矩阵:✅
git diff --check:✅- 全量
pnpm test:10,985 passed / 4 failed / 5 skipped。4 个失败均在 PR diff 外:3 个 host-local timezone 断言、1 个 root 环境下 bwrap 对 mode 000 目录仍可ls;撤销 PR 验证 merge 后在当前 master 单独重跑,仍为完全相同的 4 个失败,确认不是本 PR 引入。
影响面结论
改动位于 daemon 公共 /repo 路径,所有 CLI、PTY/Tmux、话题/群/restore 后会话都会共享新的目录解析顺序;bare /repo 卡片的主动扫描路径未改变。实现没有平台特判,Linux/macOS 行为一致。综合代码、真实 Git 边界与回归结果,批准该 PR;仍按约定等待申晗确认后再合码。
已合并 ✅ — 申晗授权 admin-merge合并前对齐最新 master 重新验证(master 在双审后又前移
双审收敛结论(首审 review-4792929658 COMMENTED + 复审 APPROVE)无阻塞;fork PR 无 CI,本地全绿后 admin-merge。合并方式:merge commit。 merge commit: |
背景与根因
普通新会话已经会用
defaultWorkingDir启动,但 repo 扫描走的是另一套目录解析逻辑。仅配置defaultWorkingDir、没有 legacyworkingDirs/workingDir时,旧逻辑会:workingDirs/workingDir为空,把基础扫描根回退为~;workingDir(可能来自defaultWorkingDir、oncall 或 peer inherit)额外加入。例如 bot 只配置
defaultWorkingDir: ~/Code,session 也在~/Code时,旧扫描根实际是:因此正确目录虽然已经在 roots 中,HOME 仍被隐式加入。与此同时,
resolveRepoSelection()会在解析直接候选路径前先调用scanMultipleProjects()扫描全部 roots。结果/repo tmp即使能直接命中~/Code/tmp,也会先递归扫描 HOME,包括 macOS 的Library/Containers、Desktop、Documents、Downloads 等目录,并由 daemon Node 在 worker/CLI 启动前触发 TCC 权限弹窗。根因是扫描根和解析顺序错误,不是 scanner 的权限错误处理。
改动
workingDirs → workingDir → effectiveDefaultWorkingDir → ~;effectiveDefaultWorkingDir继续复用现有的defaultWorkingDir/ enableddefaultOncall语义。workingDir仍追加到 roots,并在展开~后去重。仅配置defaultWorkingDir时不再隐式加入 HOME。/repo <arg>先解析显式路径和各 root 下可直接命中的目录,只读取目标目录自身的 Git 信息。git worktree list保留主仓显示名,无需为标签扫描其它 roots。保持行为与影响面
/repopicker 仍会主动扫描项目,但只扫描修正后的 roots。/repo <数字>继续使用上次 picker 缓存;嵌套 repo 的 basename 搜索仍可用。/cd产生的 session 目录仍会进入扫描范围。验证
/repo命令层:205 passedpnpm build:通过git diff --check:通过pnpm test:10,847 passed / 35 skipped;1 个未改动的file-lock并发用例在本机失败,单独运行可复现