新增: 并行多轮设计文档+审查回填+待决策更新

Plan-driven子流并行Phase1-3/CR-260620-01~03审查回填/④必须做+②已完成
This commit is contained in:
2026-06-20 15:54:51 +08:00
parent 126bee5b24
commit 44ecdf825e
4 changed files with 408 additions and 15 deletions

View File

@@ -23,6 +23,123 @@
## 当前队列
### CR-260620-01 AI 授权目录改进批(working tree 未提交:strip_verbatim比对侧收口+黑名单统一+search_files symlink+bind reload+toast接线+单测·state.rs/tool_registry.rs/mod.rs/project.rs/chat.rs/AllowedDirsPanel.vue/Settings.vue/i18n×2) — ✅ 已审(ISSUES·🟡1⚪2)
- **范围**:三方审查(安全/UX/跨端)交叉印证改进。误弹窗核心根因(strip_verbatim 仅写入侧,比对侧遗漏)收口到 `is_authorized` 单点(candidate strip)。黑名单两套不一致(validate_path contains vs is_in_system_blacklist 分段)统一单一来源 + 补 windows 根/programdata/.ssh。search_files symlink 逃逸(file_type 不跟随)。bind 路径 reload(project.rs create_with_binding/update_project/relocate 三处)。AllowedDirsPanel toast 接线 + pickDir 即加。P0 workspace_root 收紧已**回退**(编译期写死不适合分发,转待决策)。
- **维度**:安全(授权绕过/黑名单/symlink 逃逸)+ 一致性(读写形态/黑名单统一)+ 回归(现有测试 + 新增单测)
- **核验方式**:`git diff` working tree 逐文件核验 + cargo test state:: 18 passed + vue-tsc EXIT 0
- **待审点**:is_authorized strip 收口是否覆盖所有调用方(resolve :334 canon_resolved / check_path :448)/ validate_path 统一后 .ssh 等是否仍挡 / 黑名单 windows 段命中即拒是否误伤合法路径 / bind reload 是否遗漏 path 写入点
---
**复审结论(2026-06-20·主代独立 grep/read 核验源码形态 + cargo test state:: 18 passed)**: ✅ **ISSUES** — 🔴0 🟡1 ⚪2
**范围边界声明**:`git diff HEAD --stat` 显示本批实际改动 13 文件,其中 `AiChat.vue`(confirmNewConversation 去中断弹窗 + 删 isViewingGenerating dead code)+ `useAiConversations.ts`(newConversation 去 generatingConvs.clear)属 **F-09 决策e 真并发**改动,**非授权目录范围**,已由独立 **CR-260620-02** 登记(已审 PASS·⚪2),本审查不覆盖(避免审查污染/越界)。`Cargo.toml` 仅 LF/CRLF 行尾(warning 无实质改动)。本审查严格聚焦授权目录 8 文件。
---
**① is_authorized strip 收口覆盖性 PASS(核验 4 调用方)**:
- `state.rs:356-365 is_authorized` 比对侧 `strip_verbatim(candidate.to_path_buf())` 单点收口 ✅
- `tool_registry.rs:326 is_authorized(&resolved)` — resolved 词法锚定(`PathBuf::from(path)``root.join(path)`),**无 verbatim 前缀**,strip 无害(原样返回),逻辑等价 ✅
- `tool_registry.rs:334 is_authorized(&canon_resolved)` — canon_resolved 经 `std::fs::canonicalize` 返回带 `\\?\` 前缀,**strip 后命中** persistent(写入侧也 strip),形态一致 ✅(误弹窗核心根因修复点)
- `state.rs:460 check_path_authorization is_authorized(&resolved)` — resolved 词法层无前缀,strip 无害等价 ✅
- `audit/mod.rs:187 check_path_authorization` 转调,同源 strip 生效 ✅
- **覆盖完整**:4 个 is_authorized 调用点 + 1 个 check_path_authorization 全部经比对侧 strip,无遗漏调用方。
**② validate_path 凭据防护 PASS(统一后仍挡)**:
- `tool_registry.rs:136-138` `.ssh/.aws/.gnupg + Windows/ProgramFiles/ProgramData` 统一走 `crate::state::is_in_system_blacklist`(分段匹配,消除 contains 子串误伤如 `my\windows\backup`) ✅
- `tool_registry.rs:140-142` AppData 保留 `lower.contains("\\appdata\\")` 单独挡(用户级数据,分段匹配会误伤 `D:\backup\appdata` 合法目录名 — 决策合理,已注释说明) ✅
- `.ssh/.aws/.gnupg` 迁移后 `is_in_system_blacklist:422` 任意路径段命中即拒,validate_path 经 `PathBuf::from(&normalized)` 传入分段匹配,等价且更精准 ✅
- **纵深防御完整**:validate_path(词法前置)+ is_authorized 内黑名单(运行时双保险)+ set_allowed_dirs:652 预校验(防持久化系统目录),三层防线齐全。
**③ 黑名单 windows 段命中即拒误伤面 PASS(分段匹配无子串误伤)**:
- `state.rs:408 if *seg == "windows" { return true }` — seg 是按 `/`/`\` 分隔的**完整路径段**,`E:\my-windows\backup` 分段为 ["e:","my-windows","backup"],`"my-windows" != "windows"` 不误伤 ✅
- `test_blacklist_no_false_positive:892` 印证(`E:/my program files backup/x` 不拒)✅
- `test_blacklist_windows_root_programdata_creds:983` 印证 win.ini/explorer.exe/ProgramData/.ssh/.aws/.gnupg 全拒 + devflow 自身 + `D:\backup\appdata` 不误伤 ✅
- Windows 根段命中即拒(含根本身,不再限定 system32 子目录):防 win.ini/hosts 等敏感文件,合理增强 ✅
- **`.ssh/.aws/.gnupg` 段在 `cfg!(windows)` 块外**(跨平台):设计正确,Unix 凭据目录同样需挡 ✅
**④ search_files_recursive symlink 逃逸 PASS**:
- `tool_registry.rs:1797-1804``entry.file_type().await?`(不跟随 symlink)+ symlink 一律 `continue`(不匹配/不递归),消除 workspace 内 symlink → 外部目录的递归逃逸 ✅
- metadata 延迟到命中后才 `entry.metadata().await?`(避免对每个条目都跟随 symlink 读元数据,性能 + 安全双收益) ✅
-`list_dir_recursive` 行为对齐(注释 :1798 标注)✅
**⑤ P0 回退完整性 PASS(workspace_root 三处同源)**:
- `state.rs:490` / `tool_registry.rs:275` / `mod.rs:282` 三处均用 `env!("CARGO_MANIFEST_DIR").parent().parent()`,**无残留 project_root_path** ✅
- `state.rs:485-489` 加分发适配警告注释(编译期写死不适合分发,转待决策),诚实标注已知限制 ✅
- `mod.rs:281 workspace_root_str` 额外 canonicalize(trust key 归一化用,与 run_command 默认 working_dir 对齐),用途独立于 is_authorized 比对,无需 strip ✅
- **default_with_root:340/reload:619 空白名单时保留 workspace_root** 兜底,向后兼容(开发机自用有效)✅
**⑥ set_allowed_dirs/get_allowed_dirs 一致性 PASS**:
- `state.rs:664-665 set_allowed_dirs` 返回时过滤 workspace_root(与 `get_allowed_dirs:680` 一致),内部根不暴露前端 ✅
- AllowedDirsPanel.vue:74 删 `isWorkspaceRoot` 前端宽松匹配(原 `endsWith('/src-tauri')` 在分发场景失效),改由后端统一过滤,前后端读写形态一致 ✅
**⑦ AllowedDirsPanel 即时持久化 + toast PASS**:
- emit toast 通道(Settings.vue `@toast="showToast"`),替代 console.error 用户零反馈 ✅
- pickDir 选完即加(消除二次点"添加"摩擦)+ 单按钮双态(newDir 空→选目录/有值→添加),UX 改进 ✅
- dirs 由 `setAllowedDirs` 返回值回填(canonicalize + strip 后真实路径),读写不漂移 ✅
- i18n zh-CN/en settings.ts 4 toast key + allowedDirsSelectDir + desc 对齐(workspace_root→devflow 自身)中英对称 ✅
**⑧ chat.rs:552 docstring 修正 PASS**:PerConvState 已删,改 AppState.session 进程级会话临时授权语义,文档与代码一致 ✅
**⑨ 回归 PASS**:cargo test state:: **18 passed 0 failed**(含 3 新增 + 15 原有)。test_blacklist_windows_system32:878 在新"windows 段即拒"逻辑下仍 pass(更早 return true)。test_blacklist_no_false_positive 在 `.ssh/.aws/.gnupg` 增强后仍 pass(测试用例合法路径不含这些段)✅
---
**🟡 MED-1:bind_directory / create_project(path) AI 工具路径 reload 残留(注释承诺未兑现)**
| file:line | 问题 | 建议 |
|-----------|------|------|
| `tool_registry.rs:545-549`(bind_directory handler 注释)+ `:523-525`(create_project 带 path 路径) | **注释承诺"通过 app_handle emit 事件让 state reload,或下次工具调用前 reload 兜底",但代码未实际触发 reload**。bind_dir_to_project:375 经 `repo.update_field(id, "path", path)` 直接写 DB,**绕过 project.rs IPC**(create_with_binding/update_project/relocate 三处 reload 覆盖不到)。register_project_tools 闭包无 AppState 参数,确实无法直接 reload。reload_allowed_dirs 仅在启动(state.rs:553 init)+ project.rs 三 IPC + set_allowed_dirs(:661)触发,**"下次工具调用前 reload 兜底"是空头承诺** — 无该机制(主代 grep reload_allowed_dirs 全 src 印证,无工具调用前置 reload 路径)。后果:AI 调 bind_directory 绑定新目录后,该目录未进内存白名单,后续文件工具仍误弹窗,直到用户手动点 Reload 或重启。 | 二选一:(a) register_project_tools 闭包捕获 AppState 句柄(若可获取),bind_dir_to_project 后调 `state.reload_allowed_dirs().await`;(b) process_tool_calls 前置轻量校验:每次工具调用前比对 projects.bind_directory 与内存白名单差集,差集非空触发 reload(兑现"下次工具调用前 reload 兜底"注释)。当前不影响安全(只是 UX 误弹窗,用户可手动 Reload),降 MED。 |
**⚪ LOW-1:strip_verbatim 单前缀 strip + 大小写敏感(理论边界,实际安全)**
`state.rs:471-478 strip_verbatim``s.strip_prefix(prefix)`(prefix 为 `\\?\`/`\\.\`/`\??\` ASCII 小写),Windows OS 生成的 verbatim 前缀**始终固定小写**(Rust std canonicalize 保证),大小写敏感 strip 不会漏。仅 strip 一个前缀(无嵌套场景)。理论边界:若 LLM 传入大写 `\\?\`(非 canonicalize 来源)则 strip 失败 — 但 LLM 传入路径走 validate_path + check_path_authorization 词法层,不经 canonicalize,不会带 verbatim 前缀。实际安全,降 LOW 标注。
**⚪ LOW-2:reload_allowed_dirs 项目绑定目录读 list_all(无 active 过滤)**
`state.rs:601-606 reload_allowed_dirs``repo.list_all()` 全量项目(含回收站软删项目?)的 bind_directory 合并入 persistent。若软删项目 path 仍被读入,会残留无效授权目录(指向已"删除"项目路径)。建议核验 ProjectRepo::list_all 是否过滤 `status != 'deleted'`,或 reload 时 filter active 项目。当前影响:无害(目录仍存在,授权不影响安全,仅白名单略冗余),降 LOW。
---
- **🟡 MED-1**: bind_directory / create_project(path) AI 工具路径 reload 残留(注释承诺未兑现,UX 误弹窗,非安全)
- **⚪ LOW-1**: strip_verbatim 大小写敏感 strip 理论边界(实际 OS 固定小写,安全)
- **⚪ LOW-2**: reload_allowed_dirs 读 list_all 可能含软删项目 path(白名单略冗余,无害)
- **待修项回流 todo**: 🟡 **1 项**(tool_registry.rs:545-549 bind_directory/create_project AI 工具路径 reload 兑现 — 二选一:闭包捕获 AppState / process_tool_calls 前置差集 reload)
### CR-260620-02 F-09 决策e newConversation 跟进(working tree 未提交:AiChat.vue confirmNewConversation 去中断弹窗+删 isViewingGenerating dead code / useAiConversations.ts newConversation 去 generatingConvs.clear()) — ✅ 已审(PASS·⚪2)
- **复审结论(2026-06-20·审查 agent 独立 git diff + grep/read 核验源码形态)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: `git diff` AiChat.vue/useAiConversations.ts 逐行核验 + vue-tsc --noEmit EXIT 0 + 独立 grep 全 src/ 无残留。
**① confirmNewConversation 调用路径 PASS**: 唯二调用方均正确直达 `store.newConversation()`:
- AiChat.vue:7 `@confirm-new-conversation="confirmNewConversation"`(ConversationSidebar emit)
- AiChat.vue:18 `@new-conversation="confirmNewConversation"`(TopBar emit)
- 去弹窗后 handler 体仅剩 `await store.newConversation()`(:248-250),无残留 isViewingGenerating/confirmDialog 中断逻辑 ✅
**② isViewingGenerating 删除完整性 PASS**: grep 全 src/ 命中 AiChat.vue 已无 isViewingGenerating。其余 3 处命中(MessageList.vue:196 / MaxRoundsCard.vue:42 / DirAuthDialog.vue:50)均为各组件**本地独立 computed**(`store.state.streaming && store.isGenerating(state.activeConversationId)`),非 import AiChat 的——AiChat 删除不影响它们。vue-i18n/tsconfig noUnusedLocals 编译通过(vue-tsc EXIT 0)✅
**③ newConversation 去 clear 后旧 conv 收尾 PASS**: 事件路由按 conversation_id 正确分发,旧 conv 后台 loop 完成时收尾无依赖 generatingConvs 全清:
- useAiEvents.ts:461-469 `handleEvent` 外围 — 非当前会话事件(`!isCurrent`)仅 AiCompleted/AiError 执行 `state.generatingConvs.delete(convId)` + `loadConversations()`,不污染当前视图 ✅
- useAiEvents.ts:362(AiCompleted)/ :415(AiError)— `state.generatingConvs.delete(event.conversation_id || '')` 按 conv_id 删除(per-conv),非全清 ✅
- 去 clear 后旧 conv 后台完成时按 conv_id 正确从 Set 移除,侧栏态正确刷新(侧栏 `isGenerating(id)` 读 Set,双会话生成可见)✅
**④ switchConversation 对齐 PASS**: newConversation 真对齐 switchConversation 并行语义。两者均:不中断后台 loop / 不清 generatingConvs(per-conv 跟踪保留) / 事件按 conv_id 路由。switchConversation:91-205 grep 核验无 generatingConvs.clear() / 无 loop 杀逻辑 ✅
**⑤ queue 清空边界 PASS(含 WATCH)**: `state.queue`(ai.ts:90)是**单一全局数组**非 per-conv。drainQueue(useAiSend.ts:246-267)调 sendMessage 续发,路由到 `state.activeConversationId`(非队列起源 conv)。故 queue 语义是"active-conv 视图的待发意图"非跨 conv 跟踪。newConversation 清 `queue = []`(:80)实际**正确且更安全**:防 conv A 排队消息在 conv A 的 AiCompleted 触发 ai-drain-queue 后误发到新 conv B(注释"防旧会话排队消息带进新会话 drain"准确)。**🟡→⚪ WATCH-1**:switchConversation(:91-205)**不清 queue**,真并发下若 conv A 排队消息 + 切到 conv B,A 的 AiCompleted 会把队列项发到 B(误路由,既有逻辑非本 CR 引入,登记待评估)。
**⑥ 后端依赖 PASS**: 独立 Read conversation.rs:154-201 `ai_conversation_create` 确认决策e 后端已落地:
- :163-166 注释明确「新建会话不杀旧 loop」(B-260615-10 单 loop 软复位废弃)
- :188 仅切 `active_conversation_id = Some(id.clone())`
- :196-198 注释「旧 conv 的 per_conv 不清除(决策e:后台 loop 继续跑)」+ :198 `session.conv(&id)` 为新会话建独立 per_conv
- 前端去弹窗/去 clear 跟进无后端依赖,后端先行已就绪 ✅
**⑦ confirmDialog 基础设施不受影响 PASS**: grep AiChat.vue confirmDialog 仍有 5 活跃消费方(:230 confirmDeleteConversation / :236 confirmClearChat / :376 tryForceSend / :545 handleClearContext 等),useConfirm 模块仍 import(:136)。仅 confirmNewConversation 退出消费,无回归 ✅
- **⚪ WATCH-1**: queue 全局数组非 per-conv。switchConversation 不清 queue,真并发下「conv A 排队 + 切 conv B」时 A 的 AiCompleted→ai-drain-queue 会把队列项误发到 B。newConversation 清 queue 是正确收口但两路径行为不一致。建议统一(switchConversation 也清 queue,或 queue 改 per-conv)。既有逻辑非本 CR 引入。
- **⚪ LOW-1**: stale doc 注释。去弹窗后三处子组件注释仍描述旧行为(「带二次确认」/「生成中走确认弹层」/「生成中需二次确认」):TopBar.vue:163 / ConversationSidebar.vue:226 / ConversationSidebar.vue:242。纯文档不影响行为,建议顺手同步。另 i18n key `confirmNewConvWhileGenerating`(zh-CN:120 / en:119)已成死 key(0 消费方,仅 2 文件定义残留),可选清理。
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-01 batch68 未提交改动批量(41文件+754/-2434:虚拟滚动删除+timestamp打戳+audit吞错修复+fetch_models合并+分组折叠+todo精简) — ✅ 已审(PASS·🟡1 WATCH)
- **复审结论(2026-06-18·主代独立 grep/read 核验 diff)**: ✅ **PASS** — 🔴0 🟡1 ⚪2
@@ -1426,6 +1543,50 @@
- **🟡 WATCH-1**: **workspace_root 去固定根「动态白名单完整语义」声明部分兑现**`state.rs:362-365` 删除固定放行成立(diff 核验),但 `reload_allowed_dirs`(:577)仍无条件 `set.insert(workspace_root_path())`(pre-existing 非 e4c2e31 引入)。即用户从 Settings KV 删除工程根后,下次 reload 会强制把工程根塞回 persistent → `is_authorized` 经 persistent 命中照样放行 → 「用户删白名单后需授权」的预期无法兑现。本次只删了 is_authorized 的硬编码,未触及 reload 兜底注入。**非破坏性**(默认行为不变向后兼容),但 commit message「动态白名单完整语义」与实现存在落差。无对应「删除白名单后需授权」的对抗测印证(test_allowed_dirs_workspace_root_authorized:741 用 default_with_root 经 persistent 命中,不覆盖删除场景)。
- **⚪ LOW-1**: DEBUG_RULES 5 关键词权重(0.6-0.7)为经验值,无量化校准依据(db 分析 591ff4a0 仅统计偏好率未回归权重调优)。后续可据真实意图误判数据校准。
---
### CR-260620-03 MED-1 修复(path 绑定 Medium 工具审批后 reload 白名单)+ LOW 清理(死 i18n key + stale 注释)(working tree 未提交:chat.rs ai_approve/ai_authorize_dir 两处 reload + i18n×2 删 confirmNewConvWhileGenerating + TopBar.vue/Sidebar.vue 注释同步·commit-policy-deferred) — ✅ 已审(PASS·⚪1 WATCH)
- **结论(2026-06-20·审查 agent 独立 git diff + grep/read 核验源码形态 + vue-tsc EXIT 0)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: `git diff HEAD` 5 文件逐行核验 + `npx vue-tsc --noEmit` EXIT 0 + 独立 grep 全 src/ 无残留。
**① MED-1 path 工具集完整性 PASS(无遗漏)**:
- `grep registry.register` tool_registry.rs 全 AI 工具清单核验:**写 `projects.path` 的 AI 工具恰为三个**:
- `update_project`(tool_registry.rs:481,Medium)— `repo.update_field(id, field, value)`(:495),白名单含 path 字段(is_allowed_column 放行)
- `create_project`(tool_registry.rs:501,Medium)— 可选 path 走 `bind_dir_to_project`(:525)
- `bind_directory`(tool_registry.rs:534,Medium)— 走 `bind_dir_to_project`(:544)
- `matches!(approval.tool_name.as_str(), "bind_directory" | "create_project" | "update_project")`(chat.rs:484/:649)三工具全覆盖 ✅
- 其他 Medium 工具(create_task/update_task/advance_task/create_idea/append_file/rename_file)**不写 projects.path**,无需 reload ✅
- `import_project`/`relocate_project_path`/`import_projects_batch``commands/project.rs` IPC(用户前端操作,**非 AI 工具,不经 ai_approve**),由各自 reload 路径覆盖(CR-260620-01 已审),**AI 工具集无遗漏** ✅
**② MED-1 reload 时机正确性 PASS(两处执行点核验)**:
- `ai_approve`(chat.rs:479-487):reload 在 `if exec_result.is_ok()` 内(成功才 reload,失败不污染白名单)+ `emit_data_changed` 后(顺序无依赖,emit 仅 app.emit 给前端)+ `matches!` 限定 path 工具 ✅
- `ai_authorize_dir`(chat.rs:644-652):同结构,正确 ✅(注:此处的 matches! 永不命中,见 ⑤)
- `state.reload_allowed_dirs()`(state.rs:586-640)副作用核验:读 Settings KV(:589)+ 读 `repo.list_all()` DB(:600)+ `std::fs::canonicalize` IO(:629)+ 写 `allowed_dirs.write()` RwLock 短临界区(:637-639)+ 保留 session 临时授权(:638-639 std::mem::take)。性能可接受(单用户桌面,项目数量级低,非热路径),无副作用风险 ✅
**③ MED-1 执行分支正确性 PASS(Medium 走 ai_approve 审批非内联)**:
- 三 path 工具 risk 注册仍是 `RiskLevel::Medium`(tool_registry.rs:483/506/536 独立 grep 印证)✅
- `ai_approve`(chat.rs:352 消费 pending + :379 拒绝 path_auth 挂起 + :463-467 执行)是 Medium/High 审批执行入口 ✅
- chat.rs:510 注释明确「create_idea 是 Medium risk → 经审批执行(本路径),非 process_tool_calls 内联执行」语义对齐 ✅
**④ MED-1 无回归 PASS(仅 path 工具 reload)**:
- `matches!` 精确限定三个工具名,其他 Medium 工具审批执行(create_idea/create_task/update_task/advance_task/append_file/rename_file)不触发 reload,行为不变 ✅
- reload 在 `exec_result.is_ok()` 内,失败路径不 reload(避免白名单错乱)✅
**⑤ LOW 死 key 确认 PASS(0 消费方)**:
- `grep confirmNewConvWhileGenerating` 全 src/ **No matches found** — 中英两 i18n 文件定义处已删,0 消费方 ✅
- vue-tsc --noEmit EXIT 0 印证无类型回退 ✅
**⑥ LOW 注释准确 PASS(3 处同步 F-09 决策e)**:
- TopBar.vue:163 `/** 新建会话(父 confirmNewConversation,F-09 决策e 直接新建无中断确认) */` — 准确对齐 CR-260620-02 F-09 决策e(去弹窗直接新建,旧 conv 后台 loop 继续不中断)✅
- ConversationSidebar.vue:226 `* - confirm-new-conversation:新建对话(F-09 决策e 直接新建,旧后台继续不中断),交父 confirmNewConversation`
- ConversationSidebar.vue:242 `/** 新建对话(F-09 决策e 直接新建,旧后台继续不中断),父调 confirmNewConversation() */`
---
- **⚪ WATCH-1(ai_authorize_dir 第二处 reload 永不命中)**: chat.rs:644-652(ai_authorize_dir 内)的 `matches!(approval.tool_name.as_str(), "bind_directory" | "create_project" | "update_project")` reload 是**冗余防御性代码,运行时永不触发**。核验:ai_authorize_dir 触发条件是 `process_tool_calls 预校验文件工具路径未命中白名单`(chat.rs:554 docstring + :579-581 强制 `approval.path_auth.is_some()`,否则报错「非路径授权挂起」),而 path_auth 挂起的 pending 只会是 read_file/write_file/list_directory 等**文件工具**(预校验路径未命中白名单触发),**绝不会是 bind_directory/create_project/update_project**(这三个是 Medium 风险审批,走 ai_approve 消费 `path_auth=None` 的 pending,chat.rs:379 防御性拒绝 path_auth 挂起进 ai_approve)。即 ai_authorize_dir 的 approval.tool_name 永远不会命中三个 path 工具 → :649 reload 死分支。**无害**(防御性兜底,若未来架构调整把 path 工具也走路径授权流程则自动生效),但当前为冗余。建议保留(对称防御,防未来重构遗漏)或加注释标注「当前 ai_authorize_dir 不消费 path 工具 pending,此 reload 为对称防御兜底」。不阻塞,降 WATCH。
- **待修项回流 todo**: **无** 🔴/🟡 项(⚪ WATCH-1 冗余防御,保留即可)
- **待修项回流 todo**: **无** 🔴/🟡 项(🟡 WATCH-1 已修:reload_allowed_dirs 删无条件 insert workspace_root,KV 有配尊重用户 persistent,兑现"用户删白名单后工程根需授权"动态白名单完整语义)
---