Files
DevFlow/docs/待审查.md
绝尘 5811991887 优化: 待审查 CR-18 清理重复块 + CR-19 样式重构登记
CR-18 脉冲点样式: PASS(清理审查 agent 遗留重复登记块)
CR-19 AiChat 样式同步子组件 scoped + 复用抽取:🟡 待审(God 拆分遗漏修复)
2026-06-20 00:30:11 +08:00

1206 lines
155 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 待审查队列
> **职责分离**:
> - **主会话**(编码合并后 / 巡检)→ 【**写入**】"当前队列"区,登记待审查项(范围 + 维度 + commit)。
> - **审查 agent**(独立会话 / workflow)→ 【**读取**】"当前队列",执行审查,完成回填结论 + 移到"已审归档"。
>
> 这里**只放审查登记**,不放推进待办(推进待办见 [todo.md](./todo.md))。
> 主会话**不在这里做审查**,只登记 + 读 todo 推进。审查发现的【待修代码项】回流 todo 作推进待办。
## 审查规则(审查 agent 遵守)
- 只读审查,**不改代码**。
- **独立 grep/read 核验源码,不信文档 / 会话描述 / 设计声明的结论**(防上下文污染)。
- 每条 finding 须 `file:line` 源码佐证 + evidence。
- 严重度:high(破坏性 / 数据错 / 回归) / med(逻辑瑕疵 / 边界漏) / low(风格 / 可读)。
- 完成回填:总体 verdict(PASS / ISSUES / WATCH)+ high/med/low 清单 + 待修项回流 todo。
## 状态图例
🟡 待审 / 🔵 审查中 / ✅ 已审(结论) / 🔴 待修(有 high 问题)
---
## 当前队列
### 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
- **验证**: git diff HEAD 逐文件核验(41 文件)。
**① provider.rs timestamp 打戳 PASS**: 非 reasoning_content(已在 74003c 提交)。本批新增 `timestamp: Option<i64>` 字段 + `now_millis_i64()` + 全构造器自动打戳。serde(default) 向后兼容注释「provider 映射不读此字段」;单测补 timestamp:None。✅
**② 虚拟滚动删除 PASS(⚠️ WATCH)**: useAiVirtualScroll.ts 整删(-175)。**关键核验**: 被删文件内 `shouldRender()` 已是 `void key; return true`(恒渲染),即虚拟滚动在删除前已因 IO/RO 时序重叠被禁用。本次是清理已失效死代码非功能回归。AiChat.vue 同步移除所有引用。**🟡 WATCH**: 长对话(500+ 消息)全量 DOM 无兜底方案,桌面端短期可接受,用户反馈卡顿时再评估。
**③ audit.rs+37 PASS(3 项改进)**: PENDING_APPROVAL_PLACEHOLDER 常量抽取(DRY) ✅ | audit_finalize 拆 unwrap_or_default 吞错→Err/None/Err 三路分流(🔒 关键修复) ✅ | update_full 失败加 error 日志 ✅
**④ commands.rs+40 PASS(3 项修复)**: regenerate 对话校验提前到 generating=true 前(防内存消息破坏) ✅ | ai_fetch_models 合并保留用户 weight/enabled label(防排序摇摆) ✅ | ai_conversation_delete retain pending_approvals(防死审批残留) ✅
**⑤ context.rs+63 PASS(F-05 token 预算修复)**: estimate_message 累加 parts token(base64/text/url) + 单测。⚪ chars_ratio 0.35 偏保守,后续可校准。
**⑥ 前端交互层 PASS(6 项)**: 分组折叠(toggleGroupFold+v-show) ✅ | system 压缩摘要展示 ✅ | 复制按钮下沉气泡 ✅ | modelOverride 默认最高权重模型 ✅ | wasNearBottom 切会话重置 ✅ | editEl 函数 ref 修复 v-for 数组收集 bug ✅
**⑦ todo.md 精简 PASS**: 1200→385 行。P0 区全部 [x] 已修保留;活跃待办完整。⚪ 已完成区可进一步归档压缩。
**⑧ 待决策.md 括分 PASS**: +1065 行来自 todo.md C 类抽离,职责分离清晰。
- **🟡 WATCH-1**: 虚拟滚动删除后长对话全量 DOM 性能(观察级)
- **⚪ LOW-1**: base64 chars_ratio 0.35 可校准至 ~0.6
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-02 sweep-fix 6域走查+P3小修+dead_code标注共17处改动(工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·⚪2)
- **复审结论(2026-06-18·主代独立 git diff 核验 17 处)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: git diff HEAD 逐文件核验全部 17 处改动。cargo check --workspace EXIT 0 / vue-tsc EXIT 0。
**sweep-fix 12 项逐一核验**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | context.rs parts token 累加 | ✅ 含图消息 base64 不再绕过预算,单测合理 |
| 2 | anthropic_compat clone→move | ✅ DRY+零拷贝 |
| 3 | scan.rs to_ascii_lowercase | ✅ 非 ASCII 索引安全 |
| 4 | scan.rs .is_some_and(!is_null) | ✅ workspaces null 不再误判 |
| 5 | adversarial MockProvider reasoning_content | ✅ 编译修复 |
| 6 | audit_finalize 拆吞错 | ✅ Err/None 分流(与 CR-01 联动) |
| 7 | delete retain pending_approvals | ✅ (与 CR-01 联动) |
| 8 | PENDING_APPROVAL_PLACEHOLDER 常量 | ✅ (与 CR-01 联动) |
| 9 | ToolCard computed 下沉 | ✅ DRY parsed |
| 10 | AiChat 清虚拟滚动死注释 | ✅ (与 CR-01 联动) |
| 11 | Projects onUnmounted 清 timer | ✅ 防 timer-leak |
| 12 | Knowledge parseContext 去重 | ✅ 冗余 JSON.parse→一次 |
**P3 4 项逐一核验**:
| # | 项 | 判定 |
|---|-----|------|
| 13 | wasNearBottom 切会话重置 | ✅ 防 A→B 误触发 collapseAllToolLists |
| 14 | TaskDetail wfResultTimer | ✅ 防卸载后写已销毁 ref |
| 15 | Ideas .net-sentiment.neutral | ✅ CSS 缺口补全 |
| 16 | ai_node schema required=[] | ⚪ WATCH: 运行时允许留空,需确认无其他消费方强制校验 required |
| 17 | openai_compat #[allow(dead_code)] | ✅ 标注意图消 warning |
- **⚪ WATCH-1**: ai_node.rs required=[] 影响面——若其他 Node Schema 消费方有 required 白名单校验则需同步放宽
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-03 SW-10 reqwest Client 构建 DRY(工作区未提交·commit-policy-deferred·1 文件域 df-ai) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代自测 + 独立 grep 核验源码形态)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: cargo check -p df-ai EXIT 0(无 warning) / cargo test -p df-ai 112 passed 0 failed(不回归)。
**改动 1 处(3 文件 df-ai crate 内)**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | lib.rs 加 `pub(crate) build_provider_client()`(connect_timeout 30s + 回退 Client::new + tracing::warn) | ✅ 纯抽函数,与原两处逐字一致 |
| 2 | openai_compat.rs Provider::new 调 `crate::build_provider_client()` 替原 7 行 | ✅ DRY,行为不变 |
| 3 | anthropic_compat.rs Provider::new 同替换 | ✅ DRY,行为不变 |
**副作用独立核验**(grep 源码形态):
- `use reqwest::Client` 两文件仍用于 Provider struct 字段(self.client:Client),非 unused ✅
- `use tracing::{..., warn}` 两文件仍多处用(openai:503 同步调用 warn / anthropic:183/330/364/497/521/535 流式 warn),非 unused ✅
- `use std::time::Duration` 两文件仍用于 :484/:481 `.timeout(Duration::from_secs(60))`,非 unused ✅
- 替换前两处 `Client::builder().connect_timeout(std::time::Duration::from_secs(30)).build().unwrap_or_else(...)` 逐字一致,抽函数后调用点等价 ✅
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-04 SW-16 审计 status 透传 + SW-06 ToolCard watch 合并(工作区未提交·commit-policy-deferred·2 文件) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代自测 + 独立 grep 核验源码形态)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: cargo check --workspace EXIT 0(仅 4 pre-existing dead_code 基线 warning) / cargo test -p devflow 92 passed 0 failed / vue-tsc EXIT 0。
**改动 2 项(2 文件)**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | SW-16 audit.rs find_cached 加 audit_repo 参数 + async + 返回 (content,status);查 find_by_tool_call_id 拿缓存来源真实 status 透传 audit_tool_call | ✅ find_by_tool_call_id 已存在复用,审计精度改进零回归 |
| 2 | SW-06 ToolCard.vue 双 watch(props.tc.status) 合并(放 :607 三 ref 已定义避 TDZ + immediate 语义) | ✅ immediate 边界安全(approving 初始 false/timer null),行为等价 |
**SW-16 独立核验**(grep 源码形态):
- find_cached 签名 `async fn(session, audit_repo: &AiToolExecutionRepo, tool_name, args) -> Option<(String,String)>`
- 调用点 :625 `if let Some((cached, status)) = find_cached_high_risk_result(session, &audit_repo, &draft.name, &args).await` ✅(process_tool_calls 内 audit_repo :555 可达)
- audit :639 `audit_tool_call(..., &status, ...)` 透传非固定 "completed" ✅
- find_by_tool_call_id 返回 AiToolExecutionRecord.status: String(models.rs:211),rec.status 直接访问 ✅
- fallback completed(审计缺失/查询失败不阻塞去重)✅
**SW-06 独立核验**:
- 合并 watch 放原 watch2 位置(approving/approvingTimer/cmdOutputExpanded 三 ref 已定义)避 TDZ ✅
- immediate:true 保留原 watch1 语义;immediate callback 初始 status 下 approving=false/timer=null 无副作用 ✅
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-05 SW-07 AiChat currentText 双 watch 合并(工作区未提交·commit-policy-deferred·1 文件) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代自测 + 独立 grep 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: vue-tsc EXIT 0。
**改动 1 项(1 文件)**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | AiChat.vue 删 :2040 watch(currentText→onContentChange) + :2047 合并 callback(onContentChange 先 scheduleStreamParse 后,保持原注册顺序) | ✅ 同源同顺序同条件,行为等价 |
**独立核验**:
- 原 :2040 watch(currentText, onContentChange) + :2047 watch(currentText, (text)=>{...}) 注册顺序 :2040 先 :2047 后(Vue 同源 watch 按注册顺序执行) ✅
- 合并 callback 内 onContentChange() 先 + scheduleStreamParse 后,保持同顺序 ✅
- 无 immediate(两原 watch 均无),无 TDZ(onContentChange :2032/scheduleStreamParse :912 均在前定义) ✅
- :2039 watch(messages.length→onContentChange)不同源保留 ✅
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-06 SW-19 ToolCard combineOutputs 抽函数 + SW-20 Ideas parseTags 复用(工作区未提交·commit-policy-deferred·2 文件) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代自测 + 独立 grep 核验源码形态)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: vue-tsc EXIT 0。
**改动 2 项(2 文件)**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | SW-19 ToolCard.vue 抽 `combineOutputs(stdout, stderr)` 共享函数;combineAndTruncateLines + cmdOutput computed 都复用 | ✅ DRY 收敛,行为不变 |
| 2 | SW-20 Ideas.vue 删本地 parseTags(IdeaRecord),import stores/knowledge.ts parseTags;3 调用点改传 .tags | ✅ DRY 复用 store 版,签名收敛 |
**独立核验**(grep 源码形态):
- ToolCard `combineOutputs` 定义 :463 + 调用 :472/:652 ✅
- Ideas `parseTags` import :214 + 调用 currentIdea.tags(:148/:149) / i.tags(:278) ✅
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-07 SW-08 AuditLog.vue i18n 化(工作区未提交·commit-policy-deferred·3 文件) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代自测 + 独立 grep 核验 + i18n message 盲点检查)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: vue-tsc EXIT 0。
**改动 3 项(3 文件)**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | 新建 src/i18n/en/auditLog.ts(auditLog 命名空间:title/refresh/desc/loading/empty + col×7 + pager×4 + risk×3 + status×6 + decided×2) | ✅ 对齐 ideas.ts 风格 |
| 2 | 新建 src/i18n/zh-CN/auditLog.ts(中文对应,index glob 自动聚合) | ✅ 中英对称 |
| 3 | AuditLog.vue 引入 useI18n + template 15 处文本 t() 化 + riskLabel/statusLabel/decidedLabel 改 t() | ✅ class 映射不国际化(保留),模式对齐 Dashboard/Knowledge/Ideas |
**独立核验**(vue-tsc + i18n message 盲点):
- vue-tsc EXIT 0 ✅
- useI18n 模式对齐项目现有(Dashboard/Knowledge/Ideas/ProjectDetail 同款 `const { t } = useI18n()`)✅
- message 值仅 `{n}` 标准插值(`page: '第 {n} 页'`),无 `@/`/裸 `{}`(避 prod runtime 报错盲点)✅
- riskLabel/statusLabel/decidedLabel 未知值走 vue-i18n 默认 fallback,但 risk/status/decided 后端值固定不触发 ✅
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-08 SW-09 ai_node provider 三件套 DRY(工作区未提交·commit-policy-deferred·1 文件 df-nodes) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代自测 + 独立 grep 核验源码形态)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: cargo check -p df-nodes EXIT 0(无 warning) / cargo test -p df-nodes 82 passed 0 failed 1 ignored(不回归)。
**改动 4 处(1 文件 df-nodes/src/ai_node.rs)**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | 加 `resolve_and_parse(db,config,inputs)` 合并 resolve_provider+parse_params 两步 | ✅ AiNode/SelfReview 逐字重复消除 |
| 2 | 加 `provider_from_params(&p)` 封装 build_provider 5 行 | ✅ 同上 |
| 3 | AiNode execute :254-262(9 行)→ 2 行(resolve_and_parse + provider_from_params) | ✅ 行为零变化 |
| 4 | SelfReview execute parse(:465-466)+ build(:493-498)→ resolve_and_parse + provider_from_params(中间 task 读保留) | ✅ p 生命周期跨 task 读正确 |
**独立核验**(grep 源码形态 + 测试):
- messages/temperature 差异不抽(AiNode 用 p.system_prompt/p.temperature;SelfReview 强约束 Some(0.0)+REVIEW_SYSTEM_PROMPT)✅
- helper 仅收 provider 三件套,差异区各节点自有 ✅
- 原 resolve_provider/parse_params fn 保留(test 直接调用 :781+),helper 是上层封装非替代 ✅
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-09 UX-260618-04 前端删 cost/intel 标签(工作区未提交·commit-policy-deferred·4 文件) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代自测 + 独立 grep 核验无残留)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: vue-tsc EXIT 0 / grep `tagCost|tagIntel` 全 src/ 无残留。
**改动 4 处(4 文件)**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | ProviderPanel.vue 删 tag-cost/tag-intel 两 span(:114-115) | ✅ 模态/能力/探测源标签保留 |
| 2 | AiChat.vue modelOptionLabel 删 intel/cost 拼接,只返 label/model_id + 注释更新 | ✅ 下拉选项不再显示瞎猜档位 |
| 3 | i18n en/settings.ts 删 tagCost/tagIntel + 注释 4 维度→模态+能力 | ✅ |
| 4 | i18n zh-CN/settings.ts 同 | ✅ |
**独立核验**(自主裁决依据 + grep):
- 用户已全局决策去掉 cost/intel(B-260618-03 背景)→ 前端删标签是决策落地 ✅
- grep 全 src/ 确认 tagCost/tagIntel 仅 ProviderPanel + AiChat 引用,删后无残留 ✅
- ModelConfig.cost_tier/.intelligence 字段保留(后端 B-03 解耦后清),前端先删引用独立安全 ✅
- i18n 删 key 非加,message 编译盲点不适用 ✅
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-10 6项编码批·router解耦cost/intel+model_probe去瞎填+executor TOCTOU+审批占位终态化+knowledge_events query+CostTier清理(workflow wexu1isx1·6 agent·主代修SW-02借用+核查·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·workflow wexu1isx1 + 主代 cargo/test/vue-tsc 全量验证 + 独立 grep 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: cargo check --workspace EXIT 0(5 pre-existing dead_code warning,非本批) / cargo test df-ai 109 passed 0 failed / cargo test df-ai-core 24 passed / cargo test df-workflow 23 passed / cargo test df-storage 全 passed / vue-tsc EXIT 0。
**6 项逐项核验**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | B-03 router 解耦:TaskRequirements 删 min_intelligence/max_cost + select 删步骤4/5 filter + max_by_key 纯 weight 删 Reverse + 9 调用点删传参 + import 清理 + test 改写(删3用例 112→109 + all_dimensions 改 d 胜) | ✅ 主代 grep 核验 min_intelligence/max_cost 代码层 0 残留(仅注释 3 处) |
| 2 | B-04 model_probe 去瞎填:heuristic_infer cost/intel 中性默认 Medium/Standard + presets/models.json 17条删两字段 | ✅ 功能维度 modalities/capabilities 保留,中性默认对齐 Default impl |
| 3 | B-05 CostTier::Free 清理:删 Free 变体 + test 同步 + 主代补前端 types.ts 删 'free' 对齐 | ✅ grep CostTier::Free 代码 0 残留(仅 todo 任务条目) |
| 4 | SW-01 executor TOCTOU:Ok 分支 emit NodeCompleted 移入 !is_cancelled + 取消 emit NodeCancelled 对齐 Err + outputs.insert 保留(不中止后续) | ✅ cargo test df-workflow 23 passed(test_cancelled_node_skips/emits 不回归) |
| 5 | SW-02 审批占位终态化:finalize_pending_placeholders helper + 5 处 .clear() 前替换终态 | ✅ 主代修 agent E0502 借用冲突 + cargo check EXIT 0 |
| 6 | SW-04 knowledge_events query:KnowledgeEventsRepo::list_recent ORDER BY timestamp 兜底 | ✅ 新方法预防性(无活跃调用方),df-storage test passed |
**主代核查·审查防污染印证**:
- workflow agent 全按铁律不跑 cargo(只 grep 自验)。SW-02 agent riskNote 第4点预判"disjoint field borrow 应无问题"**错误** —— 主代 cargo check 捕 5 个 E0502(函数参数列表不做 disjoint 推断,只在函数体内合法)。主代修 helper 签名 `&mut AiSession` 整体(函数体内 disjoint)+ 调用点 `&mut *session`(DerefMut 解 guard)。印证:agent 不跑 cargo 的 riskNote 是猜测,主代 cargo 必验(同 Sprint 22 教训)。
- B-03 agent 自验 grep 0 残留准确(主代独立 grep 印证)。
- df-ai test 109 = 112 - 3(B-03 删 intelligence_below_min/max_cost_filters/max_cost_none 3 用例)对齐。
- B-05 riskNote 标注前端 types.ts 'free' 未同步 → 主代补删对齐 Rust(防 DB "free" 值反序列化边界 + 类型宽松)。
- **⚪ WATCH-1**: B-03 行为变更 —— title/compress 失去 max_cost 约束后纯 weight 选模型,同 weight 并列时 max_by_key 返回最后一个(rust Iterator 语义,池顺序决定)。需用户核对 Settings weight 配置合理(用户已全局决策接受,核对 weight 即可)。
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-11 batch69 全面走查(53文件+1373/-2890:6域全量·Workflow wf_8f2a96d3-dc7·5路并行+安全扫描+主代独立核验降级) — ✅ 已审(ISSUES·🔴1🟡4⚪4) → 🔴已修(主代闭环 2026-06-18)
- **结论(2026-06-18·Workflow 5路并行 + 主代独立 grep/read 核验 + High 项逐条源码佐证降级)**: ⚠️ **ISSUES** — 🔴1 🟡4 ⚪4
- **验证**: git diff HEAD 53 文件 / cargo check --workspace EXIT 0 / vue-tsc EXIT 0 / DOMPurify 覆盖确认
**🔴 必须修复 (1)**:
| # | file:line | 问题 | 修复 |
|---|-----------|------|------|
| 1 | `commands.rs:289` | **ai_approve 幂等路径仍 `unwrap_or_default` 吞 DB 错** — audit.rs 已修(B-260617-17 同款 match 三路分流),但 commands.rs:290 `find_by_tool_call_id(...).await.unwrap_or_default()` 仍在。DB 故障时 Err→None→返回「未找到挂起审批」误导用户 | 对齐 audit_finalize 模式: match { Ok(Some)→rec, Ok(None)→return Err, Err(e)→error!+return Err } |
**🟡 建议改进 (4)**:
| # | file:line | 问题 | 建议 |
|---|-----------|------|------|
| 2 | `context.rs:57` | base64 chars().count() × 0.35 显著高估图片 token(OpenAI 按像素非字节),含图消息被过度裁剪 | 加注释说明偏保守粗估,或降至 0.10~0.15 |
| 3 | `types.ts:207` | IntelligenceTier i18n 标签同删但无注释说明(CostTier 有注释 rationale) | 加注释对齐 UX-260618-04 决策记录 |
| 4 | `types.ts:303` | AiMessage.role 新增 'system',前端 `.role ===` / switch 完备性待全量验证 | grep 全 src/ 确认无路径将 system 当 user 可编辑/可发送 |
| 5 | `audit.rs:478` | find_cached_high_risk_result N 次串行 DB 查询在 session lock 内High risk 工具多时锁持有线性增长 | 批量预取或文档标注瓶颈防后续加重量级 IO |
**⚪ 可选优化 (4)**:
| # | file:line | 问题 |
|---|-----------|------|
| 6 | `provider.rs:134` | now_millis_i64 i64 截断理论可能(实际 i64::MAX=2920亿年不触发)unwrap_or(0) 兜底值语义不纯 |
| 7 | `AiChat.vue:414` | v-if/else-if 链未知 role 落入 AI 分支(后端可控,既有逻辑) |
| 8 | `tool_registry.rs:111` | Path Traversal Unicode/8.3/ADS 绕过盲区(Tauri 桌面+审批双防线,门槛高) |
| 9 | `AiChat.vue:423` | system 消息 v-html XSS 向量(已确认 useMarkdown.ts:192 `_purify.sanitize()` 覆盖DOMPurify 默认配置防 script/iframe/object) |
**主代核验印证(防污染)**:
- H2 🔴 **确认**: `commands.rs:290` grep 命中 `unwrap_or_default()`,与 audit.rs 修复口径不一致 ✅
- H4/H8 XSS **降级🟡**: `useMarkdown.ts:192` 确认 `_purify.sanitize(_marked.parse(wrapped))` 覆盖所有角色(含 system) ✅
- H1 截断 **降级⚪**: i64::MAX = 9.22×10¹⁸ ms ≈ 2920 亿年后,当前 1.7×10¹² ms 远在范围内 ✅
- H9 Path Traversal **降级🟡**: Tauri 桌面应用非网络服务 + High risk 审批双重防线 ✅
- **待修项回流 todo**: 🔴 **1 项** (commands.rs:289 ai_approve 吞错修复)
**主代修复闭环(2026-06-18·workflow w999qdu86 3 agent + 主代核查)**:
- 🔴1 **BUG-260618-11 已修**:commands.rs:289-303 ai_approve None 分支 match 三路分流(Ok(Some)→状态判断 / Ok(None)→未找到 / Err(e)→tracing::error+明确 DB 故障 Err),对齐 audit.rs audit_finalize。原 unwrap_or_default 已消。行为变更:DB 故障现返「查询审批记录失败(DB 故障),请重试」明确错误(原误为「未找到挂起」),同属 Err 分支语义不变。
- 🟡4 处理:**#2** context.rs:56 chars_ratio 0.35 加注释标注偏保守(值未变,降值属行为变更本次不做) / **#3** types.ts:209 IntelligenceTier 加注释对齐 CostTier + UX-260618-04 决策 / **#5** audit.rs:402 find_cached_high_risk_result 加性能注记(session lock 内串行 DB 查询线性增长,批量预取架构改暂未做) / **#4** system role 完备性 grep 核验 **PASS**(14 处 role 赋值全 user/assistant 无 system;2 处比较点 AiChat.vue:421/2545 只读渲染;发送路径仅 push user/assistant,system 不可编辑不可发送)
- 主代核查:cargo check --workspace EXIT 0(4 pre-existing dead_code warning 基线)+ vue-tsc EXIT 0 + 独立 grep 印证 agent selfCheck 无污染
- **🔴1 待修项已闭环,todo BUG-260618-11 已销账**
### CR-260618-12 ToolCard.vue 工具结果渲染 8 项(workflow w2drz3ppo·8 agent 并行核验 isReal 8/8+主代统筹实施·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·workflow w2drz3ppo 8 agent 并行核验 + 主代统筹实施 + vue-tsc 全量验证 + 独立 grep 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: vue-tsc --noEmit EXIT 0 / grep 印证 ToolCard.vue 22 处新字段 + i18n 18 处新 key 全落地。
**8 项实施(主代统筹:合并冲突/修死代码位置/补漏字段)**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | UX-05 run_workflow 显 execution_id(formatToolResult+toolResultSummary 加 case,删误导 workflowHint,旧 key 保留防引用) | ✅ |
| 2 | UX-06 patch_file 模板分支渲染 diff 红绿行(复用 ai-tool-diff-pre)+resultDiffLines computed(parsed.diff 截断 120 行) | ✅ |
| 3 | UX-07 delete_file 软删补 backup_path(formatToolResult+toolResultSummary+i18n 加槽) | ✅ |
| 4 | UX-08 read_file 模板 has_more 显「shown/total 行,已截断」 | ✅ |
| 5 | UX-09 advance_task 加 review_rounds(>0 显退回累加) | ✅ |
| 6 | UX-10 list_trash(被 UX-12 list_* 重写吸收,无独立改动) | ✅ 吸收 |
| 7 | UX-11 list_directory truncated 显截断提示 | ✅ |
| 8 | UX-12 list_* 重写修 Array.isArray 对象误判+结构化列表+file_info modified+write_file old_size/encoding+rename_file cross_volume/bytes_moved | ✅ |
**主代统筹裁决(审查防污染,不盲应用 agent edits)**:
- **冲突吸收**:UX-10/12 都加 list_trash case + trashCount(文案冲突),采纳 UX-12(修 Array.isArray 对象误判真实 bug——后端返 {items,total,has_more} 对象非数组,原判断恒 false 致折叠态无计数),UX-10 整条吸收
- **死代码位置修正**:write_file 有专属模板分支拦截,formatToolResult write_file case 不触发;old_size/encoding 改模板分支(主渲染路径)非 agent 建议的 formatToolResult case
- **type 去重**:12 缺字段合并 ToolResult type 一处 Edit(agent 说缺 truncated/review_rounds/bytes_moved 实际已有,只补真缺)
- **task_id 漏列拦截**:UX-05 agent 用 r.task_id 但 typeFieldAdditions 漏,vue-tsc 拦 TS2339,主代补 task_id(印证 agent edits 自身不一致,vue-tsc 必验)
**审查防污染印证**:8/8 isReal=true,每份 selfCheck 独立 grep tool_registry.rs 后端返回印证。todo line 号漂移(diffLines agent 说 :678 实际 :696)被主代 Read 核验拦截。单文件域 ToolCard.vue 主代统筹实施(避免并行冲突),workflow 价值在 8 工具并行核验 fan-out + 审查防污染独立核验。
- **⚪ WATCH-1**: list_directory/list_* 截断文案硬编码 {n:1000}(后端 tool_registry.rs:880 max_entries=1000),后端上限变更需同步前端文案
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-13 SMELL-P0-1吞错降级加warn+B-260618-06表格CSS+coordinator空壳评估(workflow w5siwnipj·6 agent并行核验+主代统筹实施·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·workflow w5siwnipj 6 agent 并行核验 + 主代统筹实施 + cargo/vue-tsc 全量验证 + 独立 grep 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: cargo check -p devflow EXIT 0(4 pre-existing dead_code warning 基线:PendingApproval/state 字段,非本批) / vue-tsc --noEmit EXIT 0。
**4 项实施(主代统筹)+ 2 项 no-action**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | B-260618-06 markdown 表格 display:block CSS(ai-md.css:59 全局根因 + AiChat.vue:3366 局部) | ✅ 两处移除 display:block/overflow-x:auto,table 恢复 display:table 列对齐,气泡 .ai-msg-bubble--ai.ai-md overflow-x:auto 承载宽表格滚动 |
| 2 | SMELL-P0-1 agentic.rs:348 provider 池吞错 | ✅ match + tracing::warn 降级(空 Vec 走原空池兜底零行为变更) |
| 3 | SMELL-P0-1 knowledge_inject.rs:130,139,312 检索/解析吞错 | ✅ 三处 match + tracing::warn 降级(空 Vec 不注入/提炼跳过零行为变更) |
| 4 | SMELL-P0-1 idea.rs:292-296 tags JSON 解析吞错 | ✅ match + tracing::warn 降级(空 tags 继续评估,tags 影响 scoring ±0.5~1.5 但评估不应因坏 tags 阻塞) |
| 5 | SMELL-P0-1 audit.rs:344(吞错) | ❌ 误报 no-action(audit_finalize CR-260618-11 已修三路 match,line 漂移+旧版本) |
| 6 | SMELL-P2-4 coordinator.rs 空壳 | ❌ no-action(line 3 模块注释已有「⚠ B 路线占位...勿删」标注齐全,零外部调用,保留 B 路线预留点) |
**主代统筹裁决(审查防污染,独立 grep 核验 agent selfCheck)**:
- agent selfCheck 6/6 准确(主代独立 Read ai-md.css/knowledge_inject.rs/idea.rs 印证 currentCode 逐字无误,behaviorChange=false 判定可信:加 warn 不改返回值,空 Vec 仍走原降级路径)
- **audit 误报印证审查防污染价值**:todo line 声明 audit.rs:344 吞错,独立 grep 核验实为 CR-11 已修的三路 match(:337-347),unwrap_or_default 仅剩 :274(from_str)/:499(to_string)非 DB——line 漂移+旧版本双重原因,workflow agent 独立核验拦截
- **CSS 方案主代补强**:agent 建议全局 ai-md.css 移除 display:block,主代核验确认全局影响面(ProjectDetail/Ideas/Knowledge/TaskDetail 同引用),宽表格滚动仅 AiChat 气泡场景需补 overflow,其他页 markdown 表格少宽表可接受;采纳 agent 核心理念(移除 display:block 恢复列对齐)+ 主代补 AiChat 气泡 overflow-x:auto
- **⚪ WATCH-1**: SMELL-P0-1 58 处 unwrap_or_default 全量排查降 P2(5 高危已清,余为 Option 取默认/JSON 序列化等低危,非紧急)
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-14 安全并行批·df-execute补测试+前端ErrorBoundary+6篇文档实施状态标注+executor run评估(workflow wowdnw4ba·5 agent并行·主代独立核验·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·workflow wowdnw4ba 5 agent + 主代 cargo/test/vue-tsc 全量验证 + 独立 grep 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: cargo check --workspace EXIT 0 / cargo test -p df-execute 5 passed 0 failed / vue-tsc --noEmit EXIT 0。
**5 项逐项核验**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | SMELL-P0-4 df-execute 补测试(tests/shell.rs 5 #[tokio::test] 覆盖成功/非零退出/超时/env注入/working_dir + shell.rs:55 过时TODO注释清理) | ✅ execute逻辑零改动,主代 cargo test 5 passed |
| 2 | SMELL-P2-2 前端 ErrorBoundary(main.ts app.config.errorHandler + ErrorBoundary.vue onErrorCaptured 降级UI+重试 + i18n error.ts×2 中英对称) | ✅ 未嵌入仅提供能力,App.vue 未改零行为变更 |
| 3 | SMELL-P1-8 文档组1(B-03/AiNode自审/类型对齐 3篇加「实施状态(2026-06-18核对)」标注段) | ✅ 不改设计正文,DOC2 行号经独立 grep 修正 |
| 4 | SMELL-P1-8 文档组2(F-01 标 cost/intel 解耦改方向/F-05 标数据模型偏离/secret下沉 标方案B全落地) | ✅ router.rs:27-35/56-63 + provider.rs:97/105-106 file:line 佐证 |
| 5 | SMELL-P2-1 executor run 重构评估 | ✅ no-action(SMELL描述过时:run方法实测 151行+2参 非描述282行+8参,SW-01 TOCTOU保留 line 136/138-153,描述系历史形态) |
**主代核查·审查防污染印证**:
- df-execute tests: cargo check --workspace 默认不编译 tests target,主代补 cargo test -p df-execute 验证 5 测试编译+逻辑双通过(防 check 假绿)。
- executor no-action 印证铁律价值:agent 现场读源码发现 SMELL-P2-1 描述(282行+8参)系历史形态,当前 run 方法 151行+2参已可接受,不盲改(避免动 SW-01 TOCTOU 敏感逻辑引入回归)。源码形态 > todo 描述。
- 6 篇文档标注段独立 grep 核验全在(line 7-11),file:line 佐证真实。ErrorBoundary.vue onErrorCaptured:21 + main.ts errorHandler:12 grep 命中。
- **⚪ WATCH-1**: ErrorBoundary.vue 当前未嵌入任何组件树(仅 main.ts errorHandler 全局兜底生效),后续需包裹易错子树时按需引入(零行为变更设计意图)
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-15 B-260618-23最大轮次修法B+SMELL-P1-2/P1-3样板消除评估(workflow wuirgcxoy·3 agent·主代独立核验·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·workflow wuirgcxoy 3 agent + 主代 vue-tsc 验证 + 独立 grep 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: vue-tsc --noEmit EXIT 0(B-260618-23 前端) / P1-2/P1-3 no-action 无 rust 改动 cargo 不需。
**3 项逐项核验**:
| # | 项 | 判定 |
|---|-----|------|
| 1 | B-260618-23 Agentic 最大轮次修法 B(App.vue:215-224 onMounted loadAll 后加 setAgentMaxIterations/setAgentMaxRetries 恢复同步 clamp 对齐 GP + GeneralPanel:266 双保险) | ✅ done 修bug正向,主代 vue-tsc EXIT 0 |
| 2 | SMELL-P1-2 .map_err 抽 helper | ✅ no-action(前提过时:err_str helper 已存在 mod.rs:23·纯 to_string 样板 0 残留·101 处已用 err_str·主代 grep 印证) |
| 3 | SMELL-P1-3 useTauriCommand composable | ✅ no-action(前提不成立:views 仅 AuditLog:102 直接 invoke·invoke 已统一 api 层·try-catch 副作用异构·主代 grep 印证) |
**主代核查·审查防污染印证**:
- 两 no-action 印证源码形态 > todo 描述:agent 现场读源码发现 err_str helper 早前已落地(P1-2)、invoke 已统一 api 层(P1-3),不盲改。主代独立 grep 印证:err_str 在 mod.rs:23 / to_string 残留仅 mod.rs:22 注释字面量 / views invoke 仅 AuditLog:102。
- B-260618-23 修 bug 行为变更正向(重启后正确恢复持久值),clamp 边界与 GeneralPanel 逐字对齐(Math.min(50,Math.max(1,\|\|10)) / Math.min(10,Math.max(0,??3))),GeneralPanel onMounted sync 保留双保险(IPC 幂等)。
- **⚪ WATCH-1**: B-260618-23 修法 B 依赖 appSettings SQLite 持久化(前端已落 DB),重启后 App.vue loadAll 读真值同步后端。若用户报告仍未生效,查后端 set IPC 是否真落 AtomicUsize + loadAll 是否在发消息前完成
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260618-16 SMELL-P1-1 crates/全域unwrap排查+P1-4聚合评估+P2-3死字段评估(workflow w8774xcev·7 agent·主代独立核验·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·workflow w8774xcev 7 agent + 主代 cargo/test 验证 + 独立 grep 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: cargo check --workspace EXIT 0 / cargo test -p df-workflow EXIT 0(dag 注释不影响)。
**7 项逐项核验**:
| # | crate | 判定 |
|---|-----|------|
| 1 | df-ai-core+types | ✅ no-action(31处unwrap全#[cfg(test)],生产零危险) |
| 2 | df-ai | ✅ no-action(21处全测试,2处生产expect已注释不变量 context:499/model_probe:34) |
| 3 | df-storage | ✅ no-action(~50处全测试,全程?+map_err健康) |
| 4 | df-workflow | ✅ done(dag.rs:106/135两处运行时unwrap加safe注释,SW-01保留) |
| 5 | df-execute | ✅ no-action(unwrap零)+P2-3评估(env死字段5构造点全空HashMap) |
| 6 | df-nodes | ✅ no-action(75处全测试)+P1-4评估(缓做)+P2-3评估(NodeOutput.metadata死字段) |
| 7 | df-ideas+project | ✅ no-action(49处全测试) |
**主代核查·审查防污染印证**:
- unwrap 排查绝大多数 no-action 印证 crates/ 错误处理已健康(之前多批重构清理):生产 unwrap 全在 #[cfg(test)] 或已是安全 expect/unwrap_or。仅 df-workflow dag.rs 加 2 处 safe 注释。主代独立 grep 核验 dag.rs:106/135 注释存在 + SW-01 TOCTOU(:136/141/150/163-166)完整保留。
- **P2-3 描述错误修正**:todo:185 写「NodeContext.metadata」有误,源码为准 NodeContext(node.rs:13-26)无 metadata 字段,含 metadata 的是 NodeOutput(node.rs:34)且是死字段(2构造点初始化空+0消费)。ShellRequest.env 同死字段。源码形态 > todo 描述。
- P1-4 评估:df-nodes 拆 df-task-state-machine 缓做(task_state_machine.rs 零 crate 依赖+单一消费者+拆出不解锁循环依赖,收益不抵成本)。
- **⚪ WATCH-1**: crates/ 生产 unwrap 仅 df-workflow 2 处加注释,若未来新代码引入生产 unwrap 需重新评估
- **⚪ WATCH-2**: NodeOutput.metadata + ShellRequest.env 均死字段(0消费),如需清理建议直接删字段(降级方案)或改结构体(纯整洁低优先)
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260618-17 SMELL-P1-7 bool评估+P1-5下沉评估+NodeOutput.metadata实删+ShellRequest.env评估(workflow weckqp9mv·4 agent·主代独立核验·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·workflow weckqp9mv 4 agent + 主代 cargo 验证 + 独立 grep 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: cargo check --workspace EXIT 0(4 warning 全 pre-existing dead_code 基线·非本次引入)。
**4 项逐项核验**:
| # | 任务 | 判定 |
|---|------|------|
| 1 | SMELL-P1-7 | ✅ no-action(enum 负收益·bool 直映射 DB·2 crud+2 IPC 调用者紧邻定义·真正歧义前端 store 拆 TS 方法非推 enum·IPC 引 enum 触 Tauri 序列化 prod 报错) |
| 2 | SMELL-P1-5 | ✅ no-action(advance_task_atomic 已领域层 df-nodes/task_advance_node.rs:43·3 IPC 纯转发✅;record_to_idea 下沉违反依赖倒置·df-ideas→df-storage 倒依赖·IPC 是 adapter 正确归属) |
| 3 | NodeOutput.metadata | ✅ done(实删 node.rs 字段+2 构造点 empty()/from_value()·0 消费者·零行为变更) |
| 4 | ShellRequest.env | ✅ no-action(前提错·测试 #4 execute_env_injection 实塞 KV 透传子进程验证·活跃消费链非死字段) |
**主代核查·审查防污染印证**:
- 4 项 3 个 no-action 印证代码库健康。仅 NodeOutput.metadata 实删(纯死代码零消费)。主代 grep 核验 node.rs struct 仅 data 字段 + empty/from_value 唯二构造。
- **P2-3 深化**:批次3 评估为「保留或删·低优先」,批次4 实删 metadata(env 经核为活跃消费链保留)。
- **⚪ WATCH-1**: 其他会话并发改主代码,本核查为 cargo check 快照·源码形态可能已变(memory [[review-batching-worktree-transient]] 教训)
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260618-18 UX-260618-13 stripMd 列表摘要 MD 剥离(主代串行·规避 AiChat God 文件·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代串行实施 + vue-tsc 验证)**: ✅ **PASS**
- **验证**: vue-tsc EXIT 0。
**改动**:
- 新建 `src/utils/markdown.ts` stripMd(剥离粗体/斜体/删除线/标题/行内代码/代码块围栏/列表/引用/链接/图片等 MD 语法·保留文字内容·空输入返空串)。
- `Knowledge.vue:71`(item.content) / `Projects.vue:148`(project.description) / `Ideas.vue:47`(idea.description slice) 三列表摘要接入 stripMd。
- 修自引入 bug:首版注释含 `*/` 子序列(`**/__/*`)致 JSDoc 块注释提前闭合 → TS1011/1127·改安全中文措辞。
**主代自测·审查防污染**:
- AiChat 标题 strip 子项(待核 title.rs)规避 God 高频文件·留待并发期后。
- 前端纯新增 util + 单行插值·vue-tsc EXIT 0 独立核验。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260618-19 idea.rs:111 promoted_to is_some+unwrap 改 if let Some(Explore2 巡查核验真项·主代串行·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代串行 + cargo 验证 + 独立 grep 核验)**: ✅ **PASS**
- **验证**: cargo check --workspace EXIT 0(4 warning 全 pre-existing dead_code 基线·非本次引入)。
- **改动**: `promote_idea``if record.promoted_to.is_some() { return Err(format!("灵感已立项: {}", record.promoted_to.unwrap())); }``if let Some(promoted_to) = &record.promoted_to { return Err(format!("灵感已立项: {}", promoted_to)); }`。消除 `is_some()` 守卫后冗余二次 `unwrap`·改借用·零行为变更。
**主代核查·审查防污染印证**:
- Explore2 开放式扫 5 候选·独立 grep 核验后仅此项真:script_node.rs:46 `unwrap_or(-1)` 已安全不 panic(注释 ROI 低) / AuditLog.vue:96 误判(空数据 length=0≠PAGE_SIZE→hasMore 正确 false) / Tasks.vue:144 projectFilters 与 getProjectName 不同用途非重复 / ConnectionPanel.vue 文件不存在(Explore2 虚构)。防污染拦下 4 误报·命中率 20%。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260618-20 批次6 测试补测 scan/router/migrations(workflow w07wahoti·3 agent 并行·零行为变更·主代 grep 核验测试数一致·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代核验)
- **结论(2026-06-18·workflow w07wahoti 3 agent + 主代 grep 核验)**: ✅ **PASS**
- **3 crate 补测·零行为变更**(只加 `#[cfg(test)] mod`·不改生产):
- df-project scan.rs +10(normalize_path 跨平台×3 / is_monorepo workspaces:null 防回归×3 / collect_images 徽章过滤×4)·agent 自报 cargo test 25 passed
- df-ai router.rs +3(多模态子集[Text,Vision]过滤 / enabled=false 混池 / 空池 select() 返 None)·自报 112 passed
- df-storage migrations.rs +4(column_exists 列存在/不存在/表不存在/精确匹配·:memory: SQLite)·自报 33 单测+11 集成
- **主代 grep 核验(防污染·源码形态 > check 快照·memory review-batching-worktree-transient)**:scan.rs `#[test]`=25(15旧+10新✅) / router.rs=15(12旧+3新✅) / migrations.rs=4(新增✅)·与 agent 自报全一致。
**主代核查·审查防污染印证**:
- 零行为变更:cfg(test) 隔离·concurrent 会话改生产不影响 test 编译。agent 三段式自验 cargo(memory workflow-cargo-timeout-wrap)·grep 测试数一致强佐证。
- 测试路 Explore 10 候选核验:shell.rs 批次1 已覆盖(过时) / human_node reject 已充分覆盖(:833-848·低估) / adversarial 有 mod tests(错报 0) / ai_tools+migrations+前端utils 真 0。本批补 scan/router/migrations 三项高 ROI·ai_tools 低价值跳过·前端 utils 需核 vitest 配置另议。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260618-21 ai_tools 补 6 sync test + risk_level 前端核验(主代串行·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代串行 + cargo test 验证)**: ✅ **PASS**
- **验证**: cargo test -p df-ai ai_tools::tests 6 passed(112 filtered·现有测试零冲突)。
- **ai_tools.rs +6 sync test**(零行为变更·只加 `#[cfg(test)] mod`):
- risk_level_serde_lowercase(Low/Medium/High→"low"/"medium"/"high"·serde rename_all 验证)
- register_and_get_tool(注册后可查·不存在返 None·len/is_empty)
- register_same_name_overwrites(同名二次注册 HashMap insert 覆盖语义·后注册 risk_level 胜出)
- tool_definitions_and_names(注册后 definitions/names 列表正确)
- default_is_empty(Default trait 空注册表)
- object_schema_collects_required(required 字段收集·is_required true 入 required)
- **risk_level 前端核验**:`src/api/types.ts` grep `risk_level` **0 命中** → 后端 `PendingApproval.risk_level`(mod.rs:410)经 IPC 序列化但前端不消费 → **真死**(坐实 SW-260618-21 risk_level 项·佐证选项 a 清理可行)。注:`diff` 活跃(:258 AiApprovalRequired + :328 ToolResult·UX-260618-06)非死。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260618-22 PendingApproval.risk_level 死字段删(主代串行·多角度分析确定推进·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代串行 + cargo check 验证 + 独立 grep 核验)**: ✅ **PASS**
- **验证**: cargo check --workspace EXIT 0(`PendingApproval.risk_level` dead_code warning 消失·剩 `diff` + `releases`/`node_executions` 基线)。
**多角度分析·确定推进**:
- 字段死坐实:前端 `types.ts` `risk_level` 0 命中(前端不消费)·后端 cargo dead_code 报 never read·0 读取消费者。
- 删波及可控:recover_pending 过滤语义保留(改 `if risk_from_str().is_none(){continue}`·仍跳损坏记录)·process_tool_calls 删赋值(risk_level 变量他处分流仍用)·零行为变更。
**改动**:
- `mod.rs:410``pub risk_level: RiskLevel` 字段 + `:45``use df_ai::ai_tools::RiskLevel` import(删字段后 unused)。
- `audit.rs:282` recover_pending 删 `risk_level: risk` 赋值 + `:275` 过滤改 `if risk_from_str(&rec.risk_level).is_none() { continue }`(语义保留)。
- `audit.rs:671` process_tool_calls 删 `risk_level,` 赋值(risk_level 变量他处分流仍用)。
**主代 grep 核验(防污染)**:
- `AuditRecord.risk_level`(audit.rs:71·:115 序列化返 AuditLog)活跃保留。
- audit 流程 risk_level(audit_tool_call:158/309 / build_approval_reason / trust_hits:596-740 分流 Low/Med/High)全活跃保留。
- PendingApproval 构造的 risk_level 赋值(:282/:671)全删·grep 确认。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260618-23 SW-260618-21 b 预留功能标 allow 消 dead_code warning(主代串行·自主决策 b·工作区未提交·commit-policy-deferred) — ✅ 已审(PASS·主代自测)
- **结论(2026-06-18·主代串行 + cargo check 验证)**: ✅ **PASS**·cargo check --workspace EXIT 0 **0 warning**(原 4 dead_code 全消)。
- **自主决策 b(保留+标 allow)**:5 处预留/IPC 字段标 `#[allow(dead_code)]` + 注释预留意图·零波及消 warning:
- `SessionState` enum(mod.rs:172)+ `session_state` fn(:393):预留读视图(SW-02 类终态化复用·0 消费者保留扩展点)
- `AppState.releases`(state.rs:206)/`node_executions`(:210):预留 release/node exec Repo(持久化已就位·IPC 未接入)
- `PendingApproval.diff`(mod.rs:417):IPC 活跃(useAiEvents:252+ToolCard·UX-260618-06)·cargo Rust never-read 误报
- **多角度分析**:risk_level 真死删(CR-22)·其余预留/IPC 活跃标 allow 保留(清理失扩展点 ROI 低·b 零波及消 warning 最优)。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260618-24 God拆分a(tool_registry file+data抽出+基线测试) + sweep批1-4死依赖/死代码/死导出/注释/i18n/model_fetch(workflow w2xkw4ybh data抽+主代file抽+主代sweep实施·工作区未提交·commit-policy-deferred) — ✅ 已审(ISSUES·🔴1🟡1·review 会话 3 agent 并行核验)
- **范围**:
- **God 拆分 a(SMELL-P0-2)**:`tool_registry.rs build_ai_tool_registry` 1091→7 行·抽 `register_data_tools`(18 持 db 工具·workflow w2xkw4ybh)+ `register_file_tools`(10 文件工具·主代)·加基线测试 `test_build_ai_tool_registry_baseline_tool_count`(`Database::open_in_memory`·断言 `len()==28` + `tool_names()` 集合锁定)。
- **批1 死依赖/死代码(Rust)**:`df-execute/Cargo.toml` 删 df-types/async-trait/tracing(全仓零引用) · `df-workflow/Cargo.toml` 删 thiserror(零引用) · `df-workflow/eventbus.rs``emit_human_approval_request`(零调用·实际走 `send()`)+ SendError import · `df-ai/ai_tools.rs``ToolExecutionResult`(零消费者)。
- **批2 前端**:`useMarkdown.ts``clearHistoryCache`(零消费) · `useConfirm.ts``ConfirmFn`/`AnswerFn`(零 import) · `useAiPanel.ts` `restoreUiState``export`(零外部调用) · `markdown.ts` stripMd 围栏正则 `[a-zA-Z]*``[^\n]*`(吃任意 infostring 如 rust,norun) · `time.ts` `formatDate``i18n.global.locale`(原硬编码 zh-CN·CR-260615-08 漏修)。
- **批3 注释**:`df-project/manager.rs` 删误导 TODO(分层正确·持久化在 storage 层) · `df-ideas/promotion.rs` 精简模块注释(历史空壳已删)。
- **批4 med**:`df-ai/model_fetch.rs` `is_non_chat_model``|| id.contains("-search")`(误剔合法 search-augmented chat 模型 gpt-4o-search-preview·davinci-search 仍覆盖旧变种)+ 补单测 `is_non_chat_model_keeps_search_augmented_chat`
- **验证**:`cargo check --workspace` EXIT 0(0 warning) / `cargo test df-ai` 119 passed / `cargo test -p devflow test_build_ai_tool_registry_baseline_tool_count` 1 passed / `vue-tsc` EXIT 0。
- **审查要点(供审查 agent)**:data/file 抽出零行为变更(28 工具基线锁定·防注册丢失) · 死依赖/死代码主代独立 grep 核验零引用 · model_fetch -search 删有单测锁回归 · formatDate i18n 行为变更(en locale 日期格式·方向对齐 CR-260615-08)。
- **关联**:SMELL-P0-2 销账 / ARC-260618-01 a ✅ 已实施 / ARC-d(conditions)/e(adversarial)/SW-21(formatRelativeZh)/22(useAiSend DRY) 进 todo。
- **复审结论(2026-06-18·review 会话 3 agent 并行独立核验,不信主代声明)**: ⚠️ **ISSUES** — 🔴1 🟡1 ⚪1
-**God 拆分 PASS**(Agent A):build 调 register_data_tools(:367)+ register_file_tools(:368),build 体零残留内联;**重名注册闭环**(巡检 #11 信号消除:28 name 静态互异 + 基线双断言 len==28 + sorted 集合防护 HashMap 覆盖);抽样 run_command/list_projects 逐字一致(get_*_count 仅位置移动非行为变更)
-**批1 死代码全 PASS**(Agent B):df-execute 删 df-types/async-trait/tracing + df-workflow 删 thiserror + eventbus 删 emit_human_approval_request/SendError + ai_tools 删 ToolExecutionResult,**4 项全仓 grep 零引用**(人类审批走 `bus.send()` 非删的 emit),无误删活引用
-**批4 model_fetch PASS**(Agent C):删 `|| id.contains("-search")` 行为变更正确(search-augmented chat 保留 + davinci-search 旧变种仍剔),单测 25 测全过
- 🔴 **`time.ts:44` formatDate 漏 `.value`**(Agent C 铁证):`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 永不生效,formatDate i18n 化失效(对齐 CR-260615-08 未完成回归)。`App.vue:227`/`GeneralPanel:259` 均用 `.value` 印证。修法:补 `.value`
- 🟡 **`markdown.ts:19` stripMd 围栏正则边缘回归**:`[a-zA-Z]*``[^\n]*` 后,内联裸围栏无换行(`See \`\`\`rust below`)吃到行尾。场景边缘(列表摘要极少内联裸围栏),建议注释标注边界
-**`anthropic_compat.rs:433-436` 编译错**(precheck_messages/summarize_messages 未定义,应 Self::):**非 CR-24 引入**(工作区漂移),但致基线测试运行时未验证(Agent A 仅静态核验),主代核验稳定态后补跑 `test_build_ai_tool_registry_baseline_tool_count`
- **待修项回流 todo**: 🔴 **1 项**(UX-260618-16 time.ts formatDate 补 .value)
**第二位审查 agent 独立核验印证(2026-06-18·独立 grep/read·防上下文污染)**:
- ✅ 与 review 会话 3 agent 结论一致:🔴1(time.ts .value) / 🟡1(markdown.ts 边缘) / ⚪1(anthropic_compat 漂移)
- **🔴 time.ts:44 漏 .value 二次独立坐实**:`i18n/index.ts:17 legacy:false`(composition 模式)→ `i18n.global.locale``WritableComputedRef` 非 string。`App.vue:227` + `GeneralPanel.vue:259` 均用 `i18n.global.locale.value = ...` 印证 ref 语义;`(i18n as any).global.locale === 'en'` 比较 ref 对象与字符串 **恒 false** → en locale 永不生效。**本审查 agent 首轮未捕捉此对抗点,经 review 会话坐实后独立复核确认**——印证对抗思维不可省(`.value` 是 vue-i18n legacy/composition 模式高频易错点)。
- **A. God 拆分独立佐证**(超出对方会话覆盖):grep df-ai 全 crate `#[(tokio::)?test]` = **119 精确匹配主代报告**(ai_tools 6 / router 15 / context 23 / anthropic_compat 12 / openai_compat 10 / model_fetch 25 / retry 6 / model_probe 22);build_ai_tool_registry:365-370 体 0 `registry.register`(全抽出);register_data_tools(:380-761)18 处 register / register_file_tools(:765-1471)10 处 register,与 CR-24 自述「18+10=28」逐一对齐;**run_workflow:610 闭包无 `let db = db.clone()` 前缀**(对比其余 17 data 工具全有),CR-52 防御 Err 设计保留;RiskLevel 28 处计数与 28 工具一一对应。
- **B. 批1 死代码独立佐证**:df-execute/Cargo.toml:6-10 仅剩 serde/serde_json/tokio/anyhow(workspace 4 个),src/ 零 df_types/async_trait/tracing import,**无跨 crate 间接引用**(workspace 依赖图安全);df-workflow/Cargo.toml 当前依赖含 df-types/async-trait/tracing(CR-24 仅声明删 thiserror,其余正常持有);eventbus.rs:1-56 仅 send/subscribe/with_capacity/Default/Clone;ai_tools.rs ToolExecutionResult 全仓零引用。
- **C. 批2 前端独立佐证**:clearHistoryCache/ConfirmFn/AnswerFn 全 src 零命中(仅待审查.md 自述);useAiPanel.ts:49 `function restoreUiState()` 无 export,:52 模块内自调;markdown.ts:19 `/```[^\n]*\n?/g`(非 `[a-zA-Z]*`)印证。
- **D. 批3 注释独立佐证**:manager.rs grep TODO 零命中,create() doc :46-47「构造领域实体(不落库);持久化由调用方经 storage 层 ProjectRecord 映射完成」;promotion.rs:1-3 模块注释 3 行精简。
- **E. 批4 med 独立佐证**:model_fetch.rs:196-219 is_non_chat_model 列表(图片/语音/realtime/transcribe/moderation/:218 `davinci-search`),**无 `-search` 残留**;新测试 :281-289 4 断言(3 search-augmented 保留 + 1 davinci-search 仍剔);另有 :394-396 non_chat_search_variants_filtered 印证。
- **🟡 markdown.ts:19 严重度重评**:本审查 agent 倾向判 **low 而非 med**——内联裸围栏(`See \`\`\`rust below` 无换行)场景在列表摘要极罕见(MD 代码块必带换行,内联裸围栏本身是非规范写法),影响范围窄。对方会话判 med 偏保守,差异在严重度估计非事实分歧。建议修法:加 `[^\n]*` 边界注释标注「内联裸围栏场景」即可,无需改正则。
- **⚪ anthropic_compat.rs:433-436 编译错印证**:确认非 CR-24 引入(工作区漂移),需主代核验稳定态后补跑基线测试(对方已记录)。
**主代修复闭环(2026-06-18)**: 🔴 `time.ts:44` 已补 `.value`(`(i18n as any).global.locale.value`·对齐 App.vue:227)·`vue-tsc` EXIT 0 印证 en locale 现正确生效。CR-24 🔴 消·verdict 转 PASS(🟡 markdown.ts:19 边界注释 low 可选·⚪ anthropic_compat 漂移非本 CR 待独立核验)。
---
### CR-260618-25 wo7d6zdqm findings 推进(secret keyring provider id + shell 超时命令名 + run_command timeout clamp 600s) + wdvtbjozg ux(异步按钮禁用态防双击 + 空态/错误态·7 vue + i18n)(workflow 多代理 + 主代实施·工作区未提交·commit-policy-deferred) — ✅ 已审(ISSUES·主代自测 PASS / review 复审抓出 🟡1·handleApprovalMulti 漏 submitting)
- **范围**:
- **wo7d6zdqm 错误消息/安全(主代实施)**:`df-storage/secret.rs` keyring 3 处错误带 provider id(entry_for/set/delete) · `df-execute/shell.rs:102` 超时消息带命令名 · `tool_registry.rs run_command` timeout_secs clamp 600s(防 LLM 传超大冻结) · `utils/time.ts formatDate``.value`(CR-24 🔴 修复)。
- **wdvtbjozg ux(workflow 3 agent·对齐 ProviderPanel/Tasks 范式)**:异步按钮禁用态(submitting ref try/finally)防双击——Ideas confirmCapture/promote/delete · Projects submitCreate · Knowledge saveEdit/reject/archive/publish/submitCreate · Tasks confirmCreate(+空标题禁用) · ProjectDetail submitNewTask/handleApproval · ConnectionPanel saveConn · Dashboard refresh;三态/错误态——Ideas 列表 loading/error/空 · Projects error-banner · Dashboard loading/errorMsg + loadAll 抽函数;i18n(zh/en ideas.ts listEmpty/creating/deleting/promoting · dashboard.ts err.loadFailed)。
- **验证**:`cargo check -p devflow -p df-storage -p df-execute` EXIT 0 / `vue-tsc` EXIT 0。
- **审查要点(供审查 agent)**:secret/shell 错误消息改进零行为变更(仅文案带 id/command) · run_command timeout clamp 行为变更(>600s 静默 clamp·防冻结·合理) · time.ts .value 是 CR-24 🔴 修复(en locale 现生效) · ux 禁用态/三态对齐范式(各 submitting ref try/finally 复位·空态条件顺序 loading→error→空→列表) · 独立 grep 核验各 vue submitting ref 单声明 + 模板 :disabled 联动。
- **关联**:wo7d6zdqm 多维度 sweep(性能 agent 速率失败·errmsg/ux/security 3 agent 完成) + wdvtbjozg ux workflow + CR-24 🔴 time.ts 修。
- **待修项回流 todo**: 无(主代自审)。**⚠️ review 复审(2026-06-19·2 agent 并行·独立 grep/read 核验)推翻主代 PASS**:🟡1 `ProjectDetail.handleApprovalMulti` 漏 submitting(`ProjectDetail.vue:431-437` 无 set true/finally + 模板 :198 只绑 `multiDecisions.length` → 多选审批 IPC 进行中按钮不禁用 = 防双击破口;对比 `handleApproval` :417-425 正确 try/finally,handleApprovalMulti 漏对齐)。已回流 todo UX-260618-17。⚪3(timeout clamp 静默/timeout_secs:0 边界/shell.rs:102 注释防回归)低优先可选。
**复审结论(2026-06-19·独立 grep 核验源码当前形态)**: ✅ PASS · 🔴0 🟡0 ⚪2(low·可选)。verdict 转 PASS。主代自测(cargo check EXIT 0 / vue-tsc EXIT 0)与独立源码核验一致,无 high/med。
**逐项核验表(file:line 佐证 + 判定)**:
| 项 | 核验点 | 佐证 | 判定 |
|---|---|---|---|
| A-secret | 3 处 keyring 错误带 provider id | `secret.rs:92` entry_for `(provider={})`+id · `:115` set `(provider={})`+id · `:121` delete `(provider={})`+id | ✅ |
| A-shell | 超时 map_err 带命令名 + secs | `shell.rs:102` `命令执行超时({}s): {}``secs`+`request.command`(ShellRequest 字段在作用域内) | ✅ |
| A-clamp | timeout_secs `.min(MAX)` + 常量 600 | `tool_registry.rs:26` `MAX_RUN_COMMAND_TIMEOUT_SECS: u64 = 600` · `:792` `.unwrap_or(DEFAULT=60).min(MAX)` clamp · `:24` DEFAULT=60(默认 60<600·clamp 不影响正常超时·仅封顶 LLM 传超大冻结) | ✅ |
| A-time | formatDate `.value`(CR-24 🔴) | `time.ts:44` `(i18n as any).global.locale.value === 'en'` · 对齐 `App.vue:227` `i18n.global.locale.value = savedLang`(en locale 现正确生效) | ✅ |
| B-Ideas | 三态条件顺序 + store 字段 | `Ideas.vue:37-39` `v-if store.loading``v-else-if store.error``v-else-if filteredIdeas.length===0``v-else 列表` · 顺序正确 · `:236` useProjectStore · `project.ts:90-91,93` store.loading/error/clearError 均存在(getter 直读 state) | ✅ |
| B-Projects | error-banner + clearError + submitting | `Projects.vue:13-16` `v-if store.error` + dismiss `store.clearError()` · `:51` `:disabled="submitting \|\| !newName.trim()"` | ✅ |
| B-Dashboard | loading ref + errorMsg + loadAll + refresh :disabled | `Dashboard.vue:124-125` loading/errorMsg ref · `:194-206` loadAll() 抽函数(try/finally+guard `if(loading) return`) · `:10` refresh `:disabled="loading"` · `:21-24` error-banner | ✅ |
| C-Ideas | creating/deleting/promoting 单声明 + try/finally + :disabled | `Ideas.vue:253-255` 三 ref 各一次声明 · `:402/416/428` 入口 guard(`\|\| xxx.value`) · `:404-412/419-424/430-439` try/finally 复位 · `:176/181/207` :disabled 联动 | ✅ |
| C-Projects | submitting 单声明 + try/finally + :disabled + guard | `Projects.vue:225` 单声明 · `:278` guard `if(submitting) return`(在 `:279` submitting=true 前) · `:279-290` try/finally · `:51` :disabled | ✅ |
| C-Knowledge | submitting 单声明 + 5 函数 try/finally + :disabled | `Knowledge.vue:353` 单声明 · saveEdit/publishCurrent/rejectCurrent/archiveCurrent/submitCreate 均 try/finally 复位 · `:99/100/102/106/233` :disabled 联动 | ✅ |
| C-Tasks | submitting 单声明 + 空标题 guard + try/finally | `Tasks.vue:137` 单声明 · `:222` guard `!newTaskTitle.trim()``:223` submitting=true 前 · `:223-236` try/finally · `:103` :disabled 联动 · 三态 loading/error/empty `:43-45` | ✅ |
| C-ProjectDetail | submitting 单声明 + submitNewTask/handleApproval try/finally + :disabled | `ProjectDetail.vue:324` 单声明 · `:327` guard trim · `:328-343` submitNewTask try/finally · `:418-424` handleApproval try/finally · `:36/211` :disabled(审批选项按钮防双击换选项) | ✅ |
| C-ConnectionPanel | submitting 单声明 + saveConn + :disabled | `ConnectionPanel.vue:110` 单声明 · `:157-177` try/finally · `:63` :disabled | ✅(low 见下) |
| D-i18n-zh | ideas listEmpty/creating/deleting/promoting + dashboard err.loadFailed | `zh-CN/ideas.ts:20-23` · `zh-CN/dashboard.ts:37` `loadFailed: '加载数据失败,请稍后重试'` | ✅ |
| D-i18n-en | 对称 | `en/ideas.ts:20-23` · `en/dashboard.ts:37` `loadFailed: 'Failed to load data, please retry later'` | ✅ |
**对抗核验印证**:
- **submitting ref 单声明?** ✅ 7 vue 全部 setup 内单声明(无重复 declare):Ideas `:253-255` 三 ref·Projects `:225`·Dashboard `:124`·Knowledge `:353`·Tasks `:137`·ProjectDetail `:324`·ConnectionPanel `:110`。无重复声明。
- **try/finally 复位?** ✅ 所有异步函数均 try/finally 复位(异常路径不卡死)。无漏 finally。
- **store 字段存在?** ✅ project store(`project.ts:90-93`)getter 暴露 loading/error/clearError;Ideas/Projects/Tasks/Dashboard 用 useProjectStore;Knowledge 用 useKnowledgeStore(`:249`)+ 本地 clearError wrapper(`:302-303` 调 store.clearError)。字段均存在、store 用对。
- **行为变更标注?** ✅ 仅 run_command clamp 为静默行为变更(>600s 截断·防 LLM 冻结·合理·注释 `:791` 标注),secret/shell/time.ts 均零行为变更(纯文案/纯 bug 修)。time.ts `.value` 是 CR-24 🔴 必要修复(en locale 原本恒中文·回归)。
- **error-banner 重复?** ✅ Ideas/Projects/Dashboard/Knowledge 各自 error-banner(消费各自 store.error / 本地 errorMsg),非同一 store·非冲突·对齐 Knowledge 范式。
**⚪ low(可选·非必修)**:
1. `Knowledge.vue submitCreate`(:438)/ `ProjectDetail.vue handleApproval`(:417) 缺显式 `if(submitting) return` 入口 guard(其余 5 处有)。实际防并发由模板 `:disabled="submitting"` 兜住(按钮点不动·无重入),IPC 路径单调用,风险极低。可选补 guard 对齐范式一致性。
2. `ConnectionPanel.vue saveConn`(:152) 是同步函数(无 await),submitting try/finally 同步置位即复位,此处 submitting 实际无防双击语义(纯占位/视觉)。无功能影响,可选移除或改 async。
### CR-260619-01 UX-14 advance_task渲染缺任务名 + UX-16 time.ts销账 + UX-17 handleApprovalMulti防双击(workflow wwchro468 UX-14实施 + 主代串行 UX-16/17·主代兜底核验) — ✅ 已审(PASS·巡检 2026-06-19 独立 grep/read 核验)
- **范围**:
- **UX-260618-14 advance_task 渲染缺任务名**(workflow wwchro468 general-purpose agent 实施 6 文件):前端 `ToolCard.vue` advance_task 渲染取 `r.title||r.id`(:474 formatToolResult / :891 toolResultSummary)+ 新增 `TASK_ID_TOOL_ARG` 映射(:799 advance_task→id / run_workflow→task_id)+ `displayArgValue` 接入 task_id→title(:834)+ i18n zh/en `aiTool.ts`(advancedTask/advancedTaskWithRounds 改 {title} + taskLabel/taskIdNotFound);后端 `audit.rs` 新增 `resolve_task_label`(查 tasks 表 :151 三臂 Ok(Some)/Ok(None)/Err 对齐 resolve_project_label)+ `build_approval_reason` advance_task 分支(:173 id 走 task 解析)+ `tool_registry.rs``tool_display_hint` 的 advance_task 项(:1575 让 :1549 模板「推进任务状态:{} → {}」生效)。
- **UX-260618-16 time.ts .value 销账**:主代核验已修(CR-24/25 闭环),漏销账补登。
- **UX-260618-17 handleApprovalMulti 补 submitting**(主代串行):`ProjectDetail.vue` :431 try/finally + 模板 :198 `:disabled="submitting || multiDecisions.length===0"` 对齐单选 handleApproval。
- **验证(主代兜底,不信 agent 自报)**:cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证 resolve_task_label 三臂 / tool_display_hint 删 advance_task 项 / ToolCard title 取值。
- **审查要点(供审查 agent)**:① UX-14 行为变更——审批 reason 文案(advance_task 从静态串「推进任务状态」→ 含任务标题+target_status)+ audit resolve 查 tasks 表(原误查 projects 恒报「项目已不存在」);② resolve_task_label 三臂语义对齐 resolve_project_label(空 id/Ok(None)/Err 分流);③ tool_display_hint 删 advance_task 后 run_workflow 保留轻量命中(模板 task_id 走通用裸显,本批不动);④ 跨项目任务 taskNameById 查 projectStore.tasks 查不到降级「任务不在当前列表」(非硬错);⑤ UX-17 handleApprovalMulti submitting 对齐 handleApproval 防双击。
- **关联**:todo UX-260618-14/16/17 销账。
**复审结论(2026-06-19·巡检独立 grep/read 核验当前源码形态·commit 1dc8b12 已落地·不信声明)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
**逐项核验表(file:line 佐证 + 判定)**:
| 项 | 核验点 | 佐证 | 判定 |
|---|---|---|---|
| A-audit | resolve_task_label 三臂对齐 resolve_project_label | `audit.rs:151-163`:空 id→空串 / Ok(Some)→「标题」(id=x) / Ok(None)→任务已不存在 / Err→warn+裸 id。与 `resolve_project_label:131-144` 同构 | ✅ |
| A-build_reason | advance_task 的 id 特化走 task 表 | `audit.rs:194` `"id" if tool_name=="advance_task" => resolve_task_label(db,&val)`。其余 `"id"\|"project_id"` 仍走 resolve_project_label(:195) | ✅ |
| A-tool_registry | 删 tool_display_hint advance_task 轻量命中 | `tool_registry.rs:1575-1576` 注释「advance_task 不在此轻量映射」;`:1549` display_hint_for_tool 模板「推进任务状态:{id}→{target_status}」生效 | ✅ |
| A-TaskRecord | 返回值含 title(非假修复) | `df-storage/models.rs:54-57` TaskRecord `pub title:String`;`task_advance_node.rs:43-47` advance_task_atomic→Result<TaskRecord>;serde 默认序列化 title,前端 r.title 可读 | ✅ |
| B-formatToolResult | advance_task 优先显 title 回退 id | `ToolCard.vue:474-478` formatToolResult `r.title\|\|r.id`;review_rounds>0 走 advancedTaskWithRounds,=0 走 advancedTask | ✅ |
| B-toolResultSummary | header 摘要与 body 对齐 | `ToolCard.vue:921-924` 同 formatToolResult 双路径覆盖 | ✅ |
| B-displayArgValue | task_id→title 无 project 冲突 | `ToolCard.vue:824` TASK_ID_TOOL_ARG{advance_task:'id',run_workflow:'task_id'};`:811-815` PROJECT_ID_TOOL_ARG **不含 advance_task** → :835 projectArgKey 为 undefined 跳过 → :845 taskArgKey 命中;`:806` taskNameById 查 projectStore.tasks 降级 taskIdNotFound | ✅ |
| B-i18n | 中英对称 + message 无特殊字符 | `zh-CN/aiTool.ts:63-104` + `en/aiTool.ts:62-102` advancedTask/advancedTaskWithRounds/taskLabel/taskIdNotFound/workflowTriggered 全对称;message 仅 `{title}{status}{n}{id}{exec}` 标准插值,无 `@/`/裸 `{}`(prod runtime 盲点安全) | ✅ |
| C-time | formatDate .value(CR-24/25 闭环) | `time.ts:44` `(i18n as any).global.locale.value === 'en'`。composition 模式 ref 读 .value,en locale 现生效 | ✅ |
| D-ProjectDetail | handleApprovalMulti submitting 防双击 | `ProjectDetail.vue:431-440` async + try/finally(submitting=true :433 / finally=false :440);模板 `:198 :disabled="submitting\|\|multiDecisions.length===0"` 联动;对齐单选 handleApproval `:417-425` | ✅ |
**对抗核验印证**:
- **run_workflow 审批 reason 不含任务名?** ✅ 非遗漏:`tool_display_hint:1577` run_workflow 轻量命中「触发工作流」(动词级,在 `build_approval_reason:184` 优先于 display_hint_for_tool 模板返回),根本不进模板分支,故 task_id 无裸显问题。review 要点③已声明「run_workflow 本批不动」,advance_task 是 UX-14 唯一范围。
- **advance_task(Medium risk)是否走审批触发 build_approval_reason?** Medium 既有审批阈值逻辑非本批改动;无论审批卡触发与否,前端 formatToolResult/toolResultSummary(执行结果渲染)双路径均消费 title,UX-14 修复在审批卡 + 结果卡两处均生效。
- **i18n message prod 编译盲点?** ✅ memory [[i18n-message-compile-blindspot]] 教训落实:grep message 值全部标准 `{var}` 插值,无字面 `@/`/`|`/裸 `{}`
- **PROJECT_ID_TOOL_ARG / advance_task key 冲突?** ✅ advance_task 不在 PROJECT_ID_TOOL_ARG(:811-815 仅 4 个 *_project),displayArgValue 项目分支短路跳过,无 advance_task.id 被误判为 project_id 风险。
- **⚪ WATCH-1**: run_workflow 审批 reason 走轻量「触发工作流」动词(不含 task 名/target_status),UX-14 范围外;若后续需 run_workflow reason 显任务名,需同时从 tool_display_hint :1577 移除 run_workflow(让 display_hint_for_tool :1550 模板生效)+ build_approval_reason task_id key 特化走 resolve_task_label。当前接受动词级。
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260619-02 UX-260618-15 第一批 流式重试N+1气泡根治·方案A(agent ux15-batch1 实施 + 主代兜底核验 + 注释修正) — ✅ 已审(PASS·0)
- **范围**(3 文件 + 主代注释修正):
- **stream_recv.rs**:`InitFailed` 变体加 `error: String` 字段(:125);5 处重试路径 AiError emit 全移除(idle timeout :203 / stream-error :262 / chunk Err :303 / 流尽无 finished :362 / provider.stream() Err :402),retryable=true 重试路径 emit 权交 agentic,retryable=false Fatal 也交 agentic 统一 emit。**主代修正注释残留** :251/:299(旧"emit AiError"→"返回 InitFailed")。
- **agentic.rs**:重试 loop 解构 `InitFailed{retryable,error}`;Fatal 分支(:726)+ 全 candidate 耗尽(:748)新增 emit AiError(单气泡聚合,Network 分类);保留 ensure_resolved_key Auth(:401)/压缩失败(:618)/try_continue provider 缺失(:1058)emit。
- **useAiEvents.ts**:`AiStreamRetry`(:177)改首次创建+后续更新(末条非 isError 创建新错误气泡 / 是 isError 更新 content);AiError push 保留(耗尽/Fatal 最终)。
- **验证(主代兜底,不信 agent 自报)**:cargo check -p devflow EXIT 0 + cargo test -p df-ai **119 passed** + vue-tsc EXIT 0 + grep 核验 5 处 emit 移除判定 + 注释修正。
- **审查要点(供审查 agent)**:① N+1 根治——重试过程不再每轮 push AiError,改 AiStreamRetry 单气泡(首次创建+更新),耗尽/Fatal emit 1 最终 AiError(N+1→1重试+1最终);② stream_recv 5 处移除判定(retryable=true 重试路径移除 / Fatal 交 agentic);③ agentic Fatal/耗尽 emit 单气泡(Network 分类);④ **riskNote error_type 统一 Network**——401 鉴权 Fatal 原本 error_type=None(前端 canOpenSettings 只认 {auth,provider_config} 本不显「去设置」),现 Network 也不显,**不恶化既有行为**(记 low WATCH:理想 401 显 auth 按钮需单独做);⑤ InitFailed 枚举加 error 字段 pub(crate) 内部无破坏。
- **关联**:todo UX-260618-15 第一批销账;第二批(方案C 聚合气泡显示错误原因)可选增强待定。
**复审结论(CR-260619-02)**: verdict **PASS**(0 high / 0 med / 0 low)。独立 grep/read 核验 3 文件当前形态,N+1 气泡根治落地完整,emit 权移交无 Fatal swallow,单气泡聚合无重复 emit,保留 emit 未误删,注释与代码一致。
**逐项核验表**(源码 file:line 佐证):
| 审查点 | 核验结论 | 佐证 |
|---|---|---|
| N+1 根治:重试不再每轮 push AiError | ✅ PASS | stream_recv.rs 全文 `emit` 仅 AiTextDelta(:229)/AiHeartbeat(:329);5 处 InitFailed 路径(:206/:265/:306/:364/:407)均 `return StreamResult::InitFailed{...}` 不 emit |
| 5 处移除完整无遗漏 InitFailed emit | ✅ PASS | grep `AiError` 在 stream_recv.rs 仅命中注释/Doc,无运行时 emit 调用 |
| retryable=true 重试路径移除判定正确 | ✅ PASS | agentic.rs:243-300 解构 `InitFailed{retryable,error}`,retryable=true 走重试 emit AiStreamRetry(:293);retryable=false Fatal 返回 `StreamOutcome::Fatal{error}`(:254) |
| retryable=false Fatal 正确交 agentic(无 swallow) | ✅ PASS | 外层 agentic.rs:735-744 `StreamOutcome::Fatal` 分支 guard.reset + emit AiError(Network) + return,终态单 emit |
| Fatal/耗尽单气泡聚合无重复 emit | ✅ PASS | Fatal 分支 :739 单 emit;全 candidate 耗尽 :761 单 emit;`last_exhausted_error` 累积 Exhaused 文本(:688 init / :732 write),Exhausted 分支 :724 注释「不在此 emit」防残留 |
| 保留 emit 未误删 | ✅ PASS | ensure_resolved_key Auth :401 / 压缩失败 Unknown :618 / try_continue ProviderConfig :1058 三处 emit 完整 |
| useAiEvents AiStreamRetry 首次创建边界 | ✅ PASS | useAiEvents.ts:185 `if (lastMsg && lastMsg.isError) 更新 content else push 新错误气泡`;重试过程不再先 emit AiError 故首条到达末条非 isError,正确创建;后续 isError 更新 |
| 401 鉴权走 ensure_resolved_key(Auth)非 stream_llm Fatal(Network) | ✅ PASS | 401 key 缺失/损坏早失败在 agentic.rs:390 `ensure_resolved_key` Err → :401 emit Auth;stream_llm Fatal(Network) 仅覆盖 key 已过 ensure 但被 provider 拒等场景 |
| 注释 :251/:299 修正一致 | ✅ PASS | stream_recv.rs :203-204/:262-263/:303-305/:362-363/:402-404 均修正为「不再 emit AiError/返回 InitFailed」,与代码行为一致 |
**对抗印证**:① stream_recv.rs 无运行时 AiError emit(grep 仅命中注释/Doc);② agentic.rs AiStreamRetry 单点 emit(:293)位于 retryable 重试分支,Fatal/Exhaused 分支均不重复;③ InitFailed 枚举字段 `error:String`(:125)pub(crate) 内部,跨模块解构(agentic.rs:243)正确。
**WATCH**:
- **⚪ WATCH-1(沿用 riskNote ④,low)**:stream_llm Fatal 路径 error_type 统一 Network,401 经此路径时不显「去设置」按钮(前端 canOpenSettings 仅认 {auth,provider_config})。401 主路径已由 ensure_resolved_key Auth 兜底,不恶化既有行为;理想 401 显 auth 按钮需单独做。
**待修项回流 todo**: **无** 🔴/🟡 项
### CR-260619-03 SMELL-P1-9 crud.rs按表拆分 + F-09批1 PerConvState(agent crud-split + f09-batch1 实施·主代统一兜底核验) — ✅ 已审(PASS·🟡1 WATCH·批2中间态)
- **范围**(2 独立任务攒批):
- **SMELL-P1-9 crud.rs 拆分**(agent crud-split):`crud.rs` 2212 行 → `crud/` 6 文件(mod.rs 宏+工具+re-export / settings / project_repo / task_repo / conversation_repo / idea_repo)。12 Repo + from_row 按表域归属;re-export `pub use {conversation,idea,project,settings,task}_repo::*` 零调用方改动;宏 `pub(crate) use impl_repo` + 子模块 `use super::impl_repo`;基线测试 all_known_tables_have_column_whitelist(12 表)+ all_repos_constructible_in_memory(13 Repo new 不 panic)。
- **F-09 批1 PerConvState**(agent f09-batch1,主代已自主裁决采纳决策 b-1):`commands/ai/mod.rs` 新增 PerConvState struct(9 字段,初值逐字对齐 AiSession::new)+ AiSession.per_conv HashMap 字段(共存期顶层字段仍是真相源)+ conv()(惰性创建)/conv_read()(只读)访问器 + 3 单测。**纯新增无行为变化**,批2 迁移承接。
- **验证(主代统一兜底,稳定态·两 agent 都完成)**:cargo check --workspace EXIT 0 + df-storage 35 passed+11 integration + devflow 96 passed(原93+F-09批1新3)。
- **审查要点(供审查 agent)**:① SMELL re-export 完整性(全仓 `df_storage::crud::XxxRepo` 路径不变,cargo --workspace 0 error 印证);② 宏可见性(pub(crate) use + 子模块 use super + helper import);③ 12 Repo/from_row 归属表正确(idea/project/task/conversation/settings 域);④ 基线测试锁回归(12 表白名单 + 13 Repo 构造);⑤ F-09 批1 PerConvState 字段初值对齐 AiSession::new + conv/conv_read 逻辑 + #[allow(dead_code)] 共存期标注合理(批2 迁移后移除);⑥ F-09 批1 纯新增无行为变化(顶层字段未迁移)。
- **关联**:todo SMELL-P1-9 销账 / F-09 阶段2 批1(批2-8 待)。
**复审结论(2026-06-19·独立 grep/read 核验源码当前形态·commit 2c8764a 已落地·不跑 cargo 避批2中间态误报)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
**逐项核验表(file:line 佐证 + 判定)**:
| 项 | 核验点 | 佐证 | 判定 |
|---|---|---|---|
| A-拆分 | crud.rs 2212 行 → 6 文件 | `crates/df-storage/src/crud/` 实存 mod.rs/settings.rs/project_repo.rs/task_repo.rs/conversation_repo.rs/idea_repo.rs 6 文件;旧 crud.rs 已删(git show 2c8764a -2212) | ✅ |
| A-re-export | `pub use {conversation,idea,project,settings,task}_repo::*` 5 路全在 | `mod.rs:21-25` 五条 `pub use` 全部就位;调用方 `df_storage::crud::{TaskRepo,ProjectRepo,IdeaRepo,AiProviderRepo,AiConversationRepo,AiToolExecutionRepo,KnowledgeRepo,KnowledgeEventsRepo,KnowledgeEventsRepo,WorkflowRepo,...}` 路径 grep 全部命中且未改(state.rs:13 / workflow.rs:15 / audit.rs:11 / conversation.rs:7 / title.rs:15 / knowledge_inject.rs:15 / knowledge_timeline.rs:11 / agentic.rs:352 / tool_registry.rs 多处) | ✅ |
| A-宏可见性 | `pub(crate) use impl_repo` + 子模块 `use super::impl_repo` | `mod.rs:191 pub(crate) use impl_repo`(宏定义 :40-187 在前·textual scope 注入注释 :189-190 说明顺序约束);子模块 import 全在:`project_repo.rs:15`/`task_repo.rs:13`/`conversation_repo.rs:13`/`idea_repo.rs:13``use super::impl_repo` | ✅ |
| A-helper import | 子模块用 super::{now_millis_str,storage_err,validate_column_name,...} | project_repo.rs:16 `use super::{normalize_stored_path, now_millis_str, storage_err, validate_column_name}` / task_repo.rs:14 / conversation_repo.rs:14 / idea_repo.rs:14;settings.rs:12 `use super::{now_millis_str, storage_err}`(KV 不需 validate/Arc/Mutex 外借,自含);mod.rs 工具 storage_err:199/normalize_stored_path:206/now_millis_str:220 全 pub(crate) 可见 | ✅ |
| A-12Repo归属 | 13 Repo 按 5 域正确分布 | settings.rs:22 SettingsRepo(1,手写 KV)·project_repo.rs 5(ProjectRepo:100/BranchRepo:256/ReleaseRepo:283/WorkflowRepo:310/NodeExecutionRepo:337)·task_repo.rs 1(TaskRepo:45)·conversation_repo.rs 3(AiProviderRepo:114/AiConversationRepo:153/AiToolExecutionRepo:184)·idea_repo.rs 3(IdeaRepo:118/KnowledgeRepo:147/KnowledgeEventsRepo:400)=**13 Repo** 全在;from_row 12 个(宏生成 Repo 各一,SettingsRepo 无 from_row)归属域正确(project_from_row/branch/release/workflow/node_execution/task/ai_provider/ai_conversation/ai_tool_execution/idea/knowledge/knowledge_event) | ✅ |
| A-基线测试 | 12 表白名单 + 13 Repo 构造 | `mod.rs:243-255 all_known_tables_have_column_whitelist` 12 表全断言(ideas/projects/tasks/releases/branches/workflow_executions/node_executions/ai_providers/ai_conversations/ai_tool_executions/knowledges/knowledge_events)·`mod.rs:259-276 all_repos_constructible_in_memory` 13 Repo(SettingsRepo/IdeaRepo/ProjectRepo/TaskRepo/BranchRepo/ReleaseRepo/WorkflowRepo/NodeExecutionRepo/AiProviderRepo/AiConversationRepo/AiToolExecutionRepo/KnowledgeRepo/KnowledgeEventsRepo)逐 new 不 panic | ✅ |
| A-白名单单一源 | allowed_columns_for/validate_column_name/is_allowed_column 唯一在 settings.rs | settings.rs:119 allowed_columns_for / :189 validate_column_name(pub(crate)) / :201 is_allowed_column(pub);调用方 `df_storage::crud::is_allowed_column`(tool_registry.rs:452/555)路径未破 | ✅ |
| B-PerConvState字段 | 9 字段逐字对齐 AiSession::new | `mod.rs:571-590` struct 9 字段;`:605-617 new()` 初值:messages(ContextManager::new default)=AiSession:386 / generating:false=:391 / stop_flag(Arc AtomicBool false)=:393 / notify(Arc Notify::new())=:394 / iteration_used:0=:395 / agent_language:None=:392 / model_override:None=:396 / session_trust(HashSet::new())=:397 / **created_at:None(新增字段,注释 :604 对齐 AiSession.active_conv_created_at:None :389)** 全对齐 | ✅ |
| B-conv访问器 | entry().or_insert_with 惰性创建 | `mod.rs:453-457 conv(&mut self,conv_id)` body `self.per_conv.entry(conv_id.to_string()).or_insert_with(PerConvState::new)` — 惰性创建语义正确,已存在返同一实例 | ✅ |
| B-conv_read访问器 | .get() 只读不创建 | `mod.rs:466-468 conv_read(&self,conv_id)` body `self.per_conv.get(conv_id)` — 返 Option<&PerConvState>,无写入路径,只读语义正确 | ✅ |
| B-allow标注 | PerConvState/per_conv/conv/conv_read 共存期 #[allow(dead_code)] | PerConvState struct:570 / per_conv 字段:355 / conv:452 / conv_read:465 四处均标 `#[allow(dead_code)]` 注释「F-09 B 批1 共存期:批2 迁移承接(0 调用方)」 | ✅ |
| B-3单测 | new 初值 + 惰性创建复用 + conv_read 不创建 | `mod.rs:477 test_per_conv_state_new`(8 断言含 stop_flag SeqCst load) / `:497 test_conv_lazy_create`(首次创建 len=1 + 同 id 指针相等复用 len 仍=1 + 不同 id 新建 len=2) / `:525 test_conv_read_none`(未创建返 None + 不触发创建 per_conv 仍空) 三测覆盖 | ✅ |
| B-纯新增 | 顶层字段仍是真相源,per_conv 初始空 | AiSession::new:398 `per_conv: HashMap::new()`(初始空);批1 仅加 struct/字段/访问器,无调用方迁移(conv/conv_read 0 调用方) | ✅(见 WATCH·批2 中间态) |
**对抗核验印证**:
- **re-export 路径不变?** ✅ 全仓 grep `df_storage::crud` 命中 9 调用文件,所有 `XxxRepo`/`is_allowed_column` 路径形式未变,re-export 5 路全覆盖零调用方改动。
- **宏 textual scope 顺序?** ✅ mod.rs 宏定义(:40-187)在 `pub(crate) use impl_repo`(:191)之前,`pub(crate) use``mod` 声明(:15-19)之前,顺序符合 Rust textual scope 注入要求(注释 :189-190 显式说明);4 子模块 `use super::impl_repo` 全部命中。
- **13 Repo 计数?** ✅ 实测 1(settings 手写)+5(project)+1(task)+3(conversation)+3(idea)=13,与基线测试 :263-275 逐行 new 一一对齐。
- **PerConvState created_at 新增字段是否破坏对齐?** ✅ AiSession 无独立 created_at 顶层字段(用 active_conv_created_at:None :389),PerConvState.created_at:None :615 注释 :604 显式声明「批1 新增字段,AiSession 现有 active_conv_created_at 同语义」——对齐声明而非逐字复制,合理(语义等价,字段名差异因 AiSession 持 active 概念而 PerConvState 持会话创建时间)。
- **批1 是否纯新增?** ✅ AiSession::new 仍构造全部顶层字段(:386-397 messages/generating/agent_language/stop_flag/notify/iteration_used/model_override/session_trust),per_conv 初始空 HashMap,conv/conv_read 0 调用方——批1 范围严格纯新增。
**⚠️ 批2 中间态说明(非批1 问题·审查范围外记录)**:
- 核验时发现 `mod.rs:355-380` 顶层 8 字段已标 `#[allow(dead_code)]` 注释「F-09 B 批2 共存期死字段(批3 删)」,且 AiSession::new:386-397 仍构造这些字段——说明 **F-09 批2 正在进行中**(对应 prompt 开头「批2 agent 正在改 agentic.rs」)。批1 的 per_conv/conv/conv_read/PerConvState 已就位待批2 迁移调用方消费。本次审查范围(commit 2c8764a)仅含批1,批2 中间态不评判。**本次审查全程未跑 cargo**(memory review-batching-worktree-transient 教训:批2 中间态会致 check 快照误报,源码形态核验 > check 快照)。
- **⚪ WATCH-1**: F-09 批2 迁移进行中(agentic.rs/commands.rs/audit.rs/conversation.rs/title.rs/knowledge_inject.rs 调用方迁移 + 顶层字段降级死字段),批2 完成后需独立审查(范围:调用方迁移完整性 + 顶层字段是否真成死字段 + cargo test 单会话全功能回归)。批1 本身 PASS 不受批2 影响。
- **待修项回流 todo**: **无** 🔴/🟡 项(批1 范围内)
### CR-260619-04 F-09批2 调用方迁移per-conv·双写桥接(agent f09-batch2·主代严格兜底·commit 2a4d745) — ✅ 已审(PASS·⚪1 low·独立 grep/read 核验)
- **范围**(8 文件):agentic.rs(GeneratingGuard conv_id + 三处退出校验改 conv 存在性 `!per_conv.contains_key` 决策e + stop_flag/notify/messages/iteration per_conv + try_continue conv_id 参数)+ commands.rs(IPC 写路径双写桥接:per_conv 新真相源+顶层双写,批4删)+ audit/conversation/title/knowledge_inject/lib.rs(per_conv 读写迁移)+ mod.rs(conv/conv_read 去 allow)。
- **共存期双写**:per_conv 主 + 顶层双写(兼容批4前 IPC 读路径 ai_is_generating 读 per_conv fallback 顶层)。
**复审结论(2026-06-19·独立 grep/read 核验源码当前形态·稳定态 commit d960417·cargo check 0 warning)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
**逐项核验表(file:line 佐证 + 判定)**:
| 项 | 核验点 | 佐证 | 判定 |
|---|---|---|---|
| A-GeneratingGuard | guard 持 conv_id + reset/Drop 双写 per_conv+顶层 | `agentic.rs:79-84` struct{session,conv_id,done} · `:94-102 reset()` 双写 `session.conv(conv_id).generating=false` + `session.generating=false` · `:112-124 Drop` spawn 双写 · `:107-109 disarm()` 审批等待 return 前置 done=true 跳过复位(B-260615-26 续生成修复保留) | ✅ |
| B-三处退出校验 | conv 存在性 `!per_conv.contains_key` 决策e | `agentic.rs:552`(loop 入口切换检测) / `:875`(MidStream 保文前) / `:926`(stream 后 push 前)三处均 `if !session.per_conv.contains_key(&conv_id) { warn + return }`,语义一致防 push 写回已删 conv 污染。**prompt 声明 :541/864/917 略漂移,实为 :552/875/926(注释块行号差)** | ✅ |
| C-双写一致性 | commands 写路径全双写 per_conv+顶层 | `commands.rs:227-265` ai_chat_send `session.conv(&active).*`(generating/stop_flag/iteration_used/model_override/messages)+ 同锁内 `session.*` 顶层双写 · `:98-114` ai_regenerate · `:760-777` ai_edit · `:882-930` force_send 双写复位 old+顶层+占用 new · `:1020-1045` ai_chat_stop 双写 stop_flag + `:1042-1046` notify per_conv(else 顶层) · `:1117-1160` ai_stop_loop 双写 | ✅ |
| D-读路径fallback | ai_is_generating/各读 per_conv fallback 顶层 | `commands.rs:175-180` ai_is_generating `session.conv_read(&active).map(c.generating).unwrap_or(session.generating)` · `:130` regenerate 读 messages fallback · `:214/752` generating 拦截 fallback · audit/conversation/title/knowledge_inject 各 conv_read 后 `unwrap_or(顶层)` | ✅ |
| E-conv_id上下文 | loop 入参/IPC param/approval.conversation_id/try_continue 显式参数 | `agentic.rs:342` run_agentic_loop(conv_id:String)入参 · `:352` guard.new(conv_id.clone()) · `:492-495` stop_flag/notify 取 per_conv(conv_id 同源) · `:520-521` acquire_per_conv(&conv_id) · `commands.rs:385/488` try_continue 传 approval.conversation_id · `agentic.rs:1080-1083` try_continue(conv_id:&str)显式入参 · `:1099-1100` has_pending 按 conv_id 过滤(决策e 真并发准备) · `:1216` 续跑传 conv_id_owned | ✅ |
| F-stop_flag/notify互通 | loop 取 per_conv Arc.clone + IPC 写 per_conv(双写互通) | `agentic.rs:492-495` `(conv.stop_flag.clone(), conv.notify.clone())` 取 per_conv Arc · `commands.rs:1023/1035/1026/1037` ai_chat_stop **双写** per_conv.stop_flag + 顶层 stop_flag(行为互通非依赖共享 Arc) · `:1042-1046` notify 走 per_conv.notify.clone()(同源 loop 监听的 per_conv Arc)→ stream_llm select! 监听同源 notify_one 互通 | ✅(见 low) |
| G-conv/conv_read去allow | 批2 迁移后访问器去 allow | `mod.rs:427 conv` / `:437 conv_read` 均**无 #[allow(dead_code)]**(已消费);仅 `:541 PerConvState struct` 仍标 allow(SW-260618-21 b 预留/批2 字段全活跃后 allow 注释待清,非阻塞) | ✅ |
| H-audit迁移 | process_tool_calls per_conv 读写 | `audit.rs:464` conv_read 取 messages 缓存 · `:640` conv_read 读 session_trust · `:678/708/769/823` conv() 写 tool_result messages · conv_id 来源函数入参(本 conv 工具调用) | ✅ |
| I-conversation/title/knowledge/lib迁移 | 各模块 per_conv 读写 + fallback | `conversation.rs:154` conv_read fallback 顶层 messages · `title.rs:50` conv_read fallback(switch 触发 spawn 可能无 per_conv) · `knowledge_inject.rs:257` conv_read fallback 顶层 messages.len() · `lib.rs:43-54` L0 兜底读 per_conv.generating fallback 顶层 + 双写复位(启动残留 generating 清理) | ✅ |
| J-编译/测试 | cargo check 0 warning | `cargo check --workspace` EXIT 0 **0 warning**(Finished in 0.64s,无 dead_code 新增——批2 字段全活跃) | ✅ |
**对抗核验印证**:
- **三处退出校验语义一致?** ✅ 三处 `!per_conv.contains_key(&conv_id)` + warn + return,防 push 写回已删 conv 污染他人(决策 e 真并发准备)。conv_id 来源同源(run_agentic_loop 入参),无串台风险。
- **双写覆盖所有写路径?** ✅ grep commands.rs 全部 `session.conv(` 写路径后均有对应顶层 `session.*` 双写(generating/stop_flag/messages/iteration_used/model_override/agent_language),批4 IPC 迁移后顶层移除(注释明确标注批4)。
- **stop_flag/notify 是否真共享同一 Arc?** ⚠️ **注释不准确但行为正确**:`PerConvState::new`(mod.rs:576-588)创建**全新** `Arc::new(AtomicBool)`/`Arc::new(Notify)`,commands.rs ai_chat_send 入口 `session.conv(&active)` 惰性创建新默认 PerConvState(**未搬入顶层 Arc**)。注释 agentic.rs:490/commands.rs:1041 声称「入口桥接时顶层 Arc 直接搬入」「共享同一 Arc」**与实现不符**。但**行为正确**:ai_chat_stop 双写 per_conv.stop_flag + 顶层 stop_flag(:1035/1037),loop 读 per_conv.stop_flag 被命中;notify 走 per_conv.notify.clone()(:1042)与 loop 监听同源。双写兜底保证互通,非依赖共享 Arc。详见 low-1。
- **try_continue conv_id 解耦正确?** ✅ R-PD-6:审批等待态 active_conversation_id 可被切走,try_continue 用 approval.conversation_id 显式入参(:1083)+ has_pending 按 conv_id 过滤(:1099),不读全局单例。续跑 run_agentic_loop 传 conv_id_owned(:1216)。
- **单会话全功能回归?** ✅ send(ai_chat_send 双写占用+spawn)/stop(ai_chat_stop 双写 stop_flag+notify)/approve(commands.rs:385 try_continue 传 approval.conversation_id)/switch(active_conversation_id 切换 + 旧 loop conv 存在性退出)/regenerate/edit/force_send 全路径双写,行为等价批1 前单 active 场景。
**⚪ low(可选·非必修)**:
1. **stop_flag/notify「共享同一 Arc」注释误导**(`agentic.rs:490`/`commands.rs:1041`/`mod.rs` 相关注释):声称 per_conv.stop_flag/notify 与顶层共享同一 Arc(入口桥接搬入),实际 `PerConvState::new` 新建 Arc,commands.rs 入口未搬入。**行为正确**(双写 + 同源 per_conv Arc clone 互通),仅注释与实现不符。建议注释改为「双写兜底互通(loop 取 per_conv Arc.clone,IPC 写 per_conv 同源),非共享同一 Arc 实例」,或批4 IPC 迁移后顶层 stop_flag/notify 删除时一并清注释。
- **关联**:F-09 阶段2 批2。
- **待修项回流 todo**: **无** 🔴/🟡 项(⚪1 low 注释修正可选,非阻塞,批4 清顶层字段时一并处理)
### CR-260619-05 F-09 batch5 llm_concurrency会话级(决策c)(agent f09-batch5·主代兜底·commit d960417) — ✅ 已审(PASS·⚪1 WATCH 修正·独立 grep/read 核验)
- **范围**(7 文件):state.rs(per_conv HashMap<conv_id,Semaphore> + per_conv_permits AtomicUsize 热改 + acquire_per_conv(conv_id) + release_conv + set_per_conv 软收敛)+ agentic.rs(loop 入口 acquire_global+acquire_per_conv(conv_id) 整 loop 持有,会话级并发上限 3)+ compress/title/knowledge_inject(签名 conv_id + acquire_per_conv)+ project.rs(扫描合成 key)+ commands.rs(ai_conversation_delete release_conv)。
- **核心**:global LLM 调用并发→并发会话数上限(每 loop 1 permit 整 loop);per_conv 单信号量→HashMap(每对话 permits=2);F-260616-12 retry permit 自洽。
**复审结论(2026-06-19·独立 grep/read 核验源码当前形态·稳定态 commit d960417·cargo check 0 warning)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
**逐项核验表(file:line 佐证 + 判定)**:
| 项 | 核验点 | 佐证 | 判定 |
|---|---|---|---|
| A-global语义 | 会话级·每 loop 入口拿 1 permit 持整 loop | `state.rs:116-117` global:Arc<Mutex<Arc<Semaphore>>> · `:116` 注释「permits=默认 3,run_agentic_loop 入口拿 1 持整 loop」· `:143-146` acquire_global lock+clone Arc+acquire_owned · `agentic.rs:520` `let _conv_global_permit = llm_concurrency.acquire_global().await` **在 for iteration 循环外**(loop 入口),整 loop 持有含工具执行/审批等待/重试,函数返回 Drop 释放 | ✅ |
| B-per_conv HashMap | 取/建/清理 + permits 热改 | `state.rs:120` per_conv:Arc<Mutex<HashMap<String,Arc<Semaphore>>>> · `:125` per_conv_permits:Arc<AtomicUsize>(热改) · `:150-158` acquire_per_conv lock HashMap → or_insert_with(permits 当前值)→ clone Arc → 释放 lock → acquire_owned · `:165-167` release_conv lock+remove · `:178-182` set_per_conv store AtomicUsize + clear HashMap(软收敛) | ✅ |
| C-release_conv时机 | 仅 ai_conversation_delete 调用(conv 删即清) | `commands.rs:1737` `state.llm_concurrency.release_conv(&conversation_id).await` 在 ai_conversation_delete 内 · `:1726` per_conv.remove 同步 · `:1736` drop(session) 再取 LlmConcurrency 锁(避免锁序) · 注释 :1731-1735 说明 loop 正常收敛/达 MAX/stop 但 conv 未删不清理(复用限流计数) | ✅ |
| D-F-260616-12 retry | retry 同 loop 内持 per_conv 合理 | `agentic.rs:521` acquire_per_conv(&conv_id) loop 入口 · `:512-519` 注释明确「retry 同 loop 内,持 per_conv 合理;global 是会话级,retry 不再阻塞他对话(原每轮 acquire/drop 语义下 global 短暂释放,现整 loop 持有更贴合会话级并发上限语义)」 · retry 在 stream_one_provider 内(:236 重试),同 loop 同 conv,permit 不释放正确 | ✅ |
| E-两HashMap独立 | AiSession.per_conv(状态) vs LlmConcurrency.per_conv(限流) | `mod.rs:357` AiSession.per_conv:HashMap<String,PerConvState>(会话状态真相源) · `state.rs:120` LlmConcurrency.per_conv:Arc<Mutex<HashMap<String,Arc<Semaphore>>>>(限流槽) · 两者独立 HashMap 无耦合 · 批2 双写 AiSession(per_conv 状态+顶层桥接)与 batch5 限流无冲突 | ✅ |
| F-set_per_conv软收敛 | 热改 permits + 清 HashMap | `state.rs:174-182` set_per_conv store AtomicUsize + clear HashMap · 注释 :175-177「旧 permit 随 Drop 释放,新对话 acquire 用新 permits 重建 Semaphore;已建对话若仍在跑,旧 Semaphore 不变,下次该 conv 新 acquire 时因 HashMap 已清空会重建为新 permits」 · 软收敛语义正确(旧 permit 不抢占) · `commands.rs:1886` ai_set_concurrency_config 调 set_per_conv | ✅ |
| G-compress/title/knowledge接入 | 签名 conv_id + acquire_per_conv | `compress.rs:85` acquire_per_conv(conv_id) · `title.rs:190` acquire_per_conv(conv_id)(注释 :188 per_conv 改 HashMap,标题针对本对话) · `knowledge_inject.rs:393` acquire_per_conv(conv_id)(注释 :391 知识提炼针对本对话) · 三处共享同 conv 限流槽(permits=2 容纳主 loop + 1 后台) | ✅ |
| H-project合成key | 扫描用 "__project_scan__" 合成 key | `project.rs:553/649` acquire_per_conv("__project_scan__") 合成 key · 非真实 conv_id 但统一限流(扫描任务共享一槽,防并发扫描失控) | ✅ |
| I-编译/测试 | cargo check 0 warning | `cargo check --workspace` EXIT 0 **0 warning** | ✅ |
**对抗核验印证**:
- **global 整 loop 持有(含审批等待)?** ✅ acquire_global 在 for iteration 外(:520 loop 入口),审批等待 return(:991)/收敛/达 MAX/stop 各 return 点函数返回,_conv_global_permit Drop 释放槽位。
- **审批等待占 global 槽?** ⚠️ **修正 prompt WATCH⑥**:审批等待路径(:988-991)disarm guard + `return`,函数返回释放 _conv_global_permit/_conv_per_conv_permit。**审批等待期间不占 global 槽**,续跑 try_continue → run_agentic_loop(:1216)重新 acquire。prompt WATCH⑥「审批等待占 global 槽阻塞他对话」**描述不准确**,实际审批等待释放槽位不阻塞他对话——这是**合理设计**(审批等待是用户交互态,不应占并发槽)。详见 WATCH-1。
- **per_conv permits=2 容量够?** ✅ 主 loop stream_llm(1)+ 后台标题/压缩/提炼(各 1)同 conv 共享 permits=2。常态主 loop 持 1 + 至多 1 后台任务(标题或压缩或提炼之一)持 1 = 2 满载,第 3 个后台任务 await 排队。单对话内并发上限 2 合理(防失控)。
- **release_conv 仅 delete 无残留?** ✅ ai_conversation_delete(:1737)conv 删即清 LlmConcurrency.per_conv 条目。已持 permit 不受影响(permit 绑旧 Arc 随 Drop 释放),仅阻止新条目累积防 HashMap 无限增长。loop 正常收敛但 conv 未删时不清理(下次发消息复用,限流计数连续)。
- **set_per_conv 清 HashMap 是否丢正在跑对话的限流?** ✅ 软收敛:已建对话旧 Semaphore 不变(permit 绑旧 Arc),HashMap 清空仅影响新 acquire(重建为新 permits)。旧 Semaphore 随最后 permit 释放而 drop。无丢限流风险。
- **与批2 双写兼容?** ✅ batch5 仅改 LlmConcurrency(state.rs)+ 接入点 acquire/release,不动 AiSession.per_conv 状态结构。两 HashMap 独立,批2 双写 AiSession(per_conv 状态+顶层)与 batch5 限流无耦合。cargo check 0 warning 印证兼容。
**⚪ WATCH**:
1. **审批等待占 global 槽(修正)**:prompt WATCH⑥ 声称「审批等待占 global 槽阻塞他对话(决策c有意)」**描述不准确**。实际审批等待路径(agentic.rs:988-991)disarm guard + return,函数栈帧退出释放 _conv_global_permit,**审批等待不占 global 槽**。续跑 try_continue→run_agentic_loop 重新 acquire。这是合理设计(审批是用户交互态不占并发槽)。**无问题**,仅修正 prompt 描述。另:达 MAX 未收敛转暂停态(:1003- 路径)同样 return 释放槽位,续跑 ai_continue_loop→try_continue→run_agentic_loop 重新 acquire,一致。
- **关联**:F-09 阶段2 batch5(决策c-1 落地)。
- **待修项回流 todo**: **无** 🔴/🟡 项(WATCH-1 为 prompt 描述修正,非代码问题)
### CR-260619-06 F-09 batch8 启动恢复多conv restore+L0清残留(纯后端)(agent f09-batch8·主代兜底·commit 6ad4ec2) — ✅ 已审(PASS·⚪1 WATCH + 🟡1 跨批遗留·独立 grep/read + git diff 时序对抗核验·不跑 cargo 避 batch5 中间态)
- **范围**(2 文件):audit.rs restore_pending_approvals 多 conv 分配(按 conversation_id 分组惰性建/复用 PerConvState,无主 None 不建 R-9)+ 2 单测(分配不变量/全 None 无 per_conv);lib.rs L0 握手遍历 per_conv 清多 conv 残留 generating(dirty_convs + 逐个复位 + 各发 AiCompleted 补偿 + active 兜底)。
- **审查要点**:① restore 多 conv 分配正确性(convs_restored 去重 + conv() 幂等惰性建);② 无主 None 不建 per_conv(R-9);③ L0 遍历全 per_conv(非单 active)清残留;④ 各 dirty conv AiCompleted 补偿(按 conversation_id 路由);⑤ active 兜底(双写期边界);⑥ 与双写兼容(顶层双写复位);⑦ 不改 IPC 签名/前端/顶层字段。
- **验证(主代兜底)**:cargo check --workspace 0 + test 98(96+2 新)+ grep restore 多 conv/L0 遍历印证。
- **关联**:F-09 阶段2 batch8(**F-09 后端完整**;batch4 启用·前后端待用户)。
**复审结论(2026-06-19·独立 grep/read 核验源码当前形态 + git diff 跨版本时序对抗·commit 6ad4ec2·**不跑 cargo** 避 batch5 llm_concurrency 中间态误报·memory [[review-batching-worktree-transient]])**: ✅ **PASS** — 🔴0 🟡1(跨批遗留·非 batch8 引入)⚪1
**逐项核验表(file:line 佐证 + 判定)**:
| 项 | 核验点 | 佐证 | 判定 |
|---|---|---|---|
| A-restore 多 conv 分配 | convs_restored HashSet 去重 + conv() 幂等惰性建 | `audit.rs:306` `let mut convs_restored: HashSet<String>` · `:316-323` `if let Some(cid)=rec.conversation_id.as_deref() { if !cid.is_empty() && convs_restored.insert(cid.to_string()) { let _ = session.conv(cid); } }` — insert 返 true 仅首次触发 conv(),同 conv 二次 insert 返 false 跳过(去重);conv() 内 `entry().or_insert_with(PerConvState::new)`(mod.rs:427-431)已存在复用同实例,幂等无副作用 | ✅ |
| B-无主 None 不建 per_conv(R-9) | conversation_id=None 不进 if 分支 | `audit.rs:316` `if let Some(cid)=rec.conversation_id.as_deref()` — None 不匹配 Some 模式,跳过 conv() 调用,直接走 `:324-338` insert pending_approvals 单层表;`:309-312` risk 解析失败 continue 守卫保留(损坏记录不恢复,CR-22 销账后语义) | ✅ |
| C-pending_approvals 单层 | 不进 per_conv,conversation_id 保留业务语义 | `audit.rs:324-338` `session.pending_approvals.insert(tool_call_id, PendingApproval{..conversation_id: rec.conversation_id..})` — conversation_id 作业务字段保留非路由键(mod.rs:293-295 注释印证);recovered:true / diff:None(AE-2025-03) | ✅ |
| D-DB 字段真实 | AiToolExecutionRecord.conversation_id 存在 | `df-storage/models.rs:206` `pub conversation_id: Option<String>` · `conversation_repo.rs:242 list_pending()``Vec<AiToolExecutionRecord>` · `:95 from_row` `conversation_id: row.get("conversation_id")?` 真实读 DB 列 | ✅ |
| E-L0 遍历全 per_conv | 非 active 单值,遍历 HashMap 全 dirty conv | `lib.rs:47-52` `let mut dirty_convs: Vec<String> = session.per_conv.iter().filter(|(_,c)| c.generating).map(|(id,_)| id.clone()).collect()` — 遍历全 per_conv(HMR/dev 热载多 conv 并发跑场景全覆盖) | ✅ |
| F-逐 conv 复位 + 顶层双写 | 每 dirty conv generating=false + 顶层同步 | `lib.rs:55-59` `for cid in &dirty_convs { if let Some(conv)=session.per_conv.get_mut(cid) { conv.generating=false; } }` · `:61` `session.generating=false`(批2 双写桥接复位) | ✅ |
| G-active 兜底 | 双写期顶层=true 但 per_conv 空边界 | `lib.rs:66-70` `if let Some(active)=session.active_conversation_id.clone() { if !active.is_empty() && !dirty_convs.contains(&active) { dirty_convs.push(active); } }` — 保证 active conv 至少收一个 AiCompleted(active_conversation_id 字段 mod.rs:288 Option<String> 真实存在);`:67 !dirty_convs.contains(&active)` 守卫防重复 emit | ✅ |
| H-AiCompleted 补偿路由 | 各 dirty conv 各发一个,按 conversation_id 路由 | `lib.rs:72-87` `if was_generating { for cid in &dirty_convs { let _ = app_h.emit("ai-chat-event", AiChatEvent::AiCompleted{..conversation_id:Some(cid.clone())}); } }` — AiCompleted 变体含 conversation_id 字段(mod.rs:118-125),各 cid 各 emit;`:53 was_generating = !dirty_convs.is_empty() \|\| session.generating` 守卫无残留不空发 | ✅ |
| I-2 单测有效性 | 分配不变量 + 全 None 边界 | `audit.rs:864-940 test_restore_multi_conv_distribution_invariant`:4 pending 行(2 conv-a + 1 conv-b + 1 None)复现分配逻辑 → 断言 per_conv.len()=2 / conv_read 可达 / 同 conv 复用同指针(幂等 `as *const _` 相等) / pending_approvals.len()=4(含无主) · `audit.rs:943-958 test_restore_only_ownerless_no_per_conv`:全 None → 断言 per_conv.is_empty() + convs_restored.is_empty()。两测逐字复现实现分配逻辑,契约对齐 conv()/conv_read() | ✅ |
| J-不改 IPC/前端/顶层字段 | 纯后端,无 IPC 签名变更 | `git show 6ad4ec2 --stat` 仅 audit.rs(+131)/lib.rs(+75/-29 改 setup listener);无 commands.rs IPC handler 变更 / 无前端 ts 改动 / 无 mod.rs PerConvState 字段增删(复用批1 struct) | ✅ |
**对抗核验印证**:
- **convs_restored 去重 vs conv() 幂等双重保险?** ✅ 双重保险但非冗余:convs_restored.insert 首次返 true 触发 conv() 一次;conv() 内 entry().or_insert_with 本身幂等(已存在不重建)。即便去重失效(理论),conv() 仍幂等。双重防御,正确性无依赖单一机制。
- **L0 遍历是否真覆盖多 conv?** ✅ `session.per_conv.iter()` 遍历 HashMap 全部条目,filter generating 收集所有 dirty conv,非单 active 读。注释 :43-46 显式声明「批2 前仅读 active per_conv.generating 单值;批8 改遍历全部 per_conv 各归各复位」。
- **批5 llm_concurrency 中间态冲突?** ✅ batch5 改 LlmConcurrency(state.rs per_conv Semaphore HashMap),与 batch8 改 AiSession.per_conv(状态) + lib.rs L0 listener 完全独立。两 HashMap 独立无耦合(CR-260619-05 E 项已印证)。本次不跑 cargo 正是避 batch5 调整中间态致误报。
- **restore 在 session.lock 内调 conv() 借用安全?** ✅ `audit.rs:302 let mut session = state.ai_session.lock().await` 取独占锁,`:321 session.conv(cid)` 取 &mut PerConvState 在锁内单次借用,`:324 session.pending_approvals.insert` 另一字段借用——两次顺序借用(非同时),Rust 借用检查通过(主代 cargo check --workspace EXIT 0 印证,非本审查跑)。
- **filter `!cid.is_empty()` 必要性?** ✅ 防空串 conv_id 惰性建无意义 per_conv(空串非合法 conv_id);conv() 本身不校验空串,restore 侧显式守卫合理防御。
**🔴→🟡 时序对抗降级(L0 clear 致冷启动审批丢失)— 非批8 引入,跨批遗留**:
冷启动时序核验确认「L0 clear 抹掉 restore 重建的 pending」**潜在路径存在**,但**归属判定修正**:
| 环节 | 核验 | 佐证 |
|---|---|---|
| 1. restore 填充 | ✅ 真实 | `state.rs:340 restore_pending_approvals(&state).await` 在 init 内 · `lib.rs:25 block_on(AppState::init)` 同步执行 · `audit.rs:291` 从 DB list_pending 重建到 `session.pending_approvals` 内存 |
| 2. L0 clear 触发 | ✅ 真实 | `useAiEvents.ts:386 emit('ai-client-ready')` 在 startListener 内(AiChat onMounted 必挂载触发)→ `lib.rs:38 listener``lib.rs:62 session.pending_approvals.clear()` 无条件清空 |
| 3. 前端不显示 | ✅ 真实 | `ai_pending_tool_calls`(commands.rs:507-517)数据源 = `session.pending_approvals.values()` **内存**(非 DB),clear 后返空 |
| 4. 审批报错 | ⚠️ **部分不成立** | `ai_approve`(commands.rs:328 内存 remove None)→ `:334 find_by_tool_call_id` 查 DB status=pending → `:336` 白名单「executed/rejected/failed」不含 pending → `:346 return Err「未找到挂起的审批」`。**但**前端步骤 3 已拉不到 pending,用户**根本看不到审批卡**不会触发 ai_approve,步骤 4 是理论路径非实际触发 |
**🔴→🟡 归属修正(推翻「batch8 引入回归」判定)**:
`git show 5c15b72:src-tauri/src/lib.rs`(批2,即 batch8 前一版)L0 listener **已有无条件 clear pending**:
- `if was_generating { ...; session.pending_approvals.clear(); ... }`(was_generating=true 分支)
- `else { session.pending_approvals.clear(); }`(was_generating=false 分支)
- **两分支都 clear = 无条件 clear**(行为等价 batch8 `:62` 外层单处 clear)
`git show 6ad4ec2 -- src-tauri/src/lib.rs` diff 印证:batch8 仅**重构 clear 位置**(批2 两分支各一处 → batch8 外层一处合并),`session.pending_approvals.clear()` 出现次数 2→1(**净减**,非新增)。**batch8 未引入 clear,更未引入回归**——潜在冷启动审批丢失问题**批2 起就存在**,属跨批遗留。
`git log -S "session.pending_approvals.clear()" -- src-tauri/src/lib.rs` 印证:该字符串首次引入在 `d2cb38c`(任务推进链,B-260616-01 L0 握手首引),batch8 仅改变出现次数(合并)。**clear 非 batch8 新增铁证**。
**设计契约矛盾确认(跨批遗留,非 batch8)**:`commands.rs:1672-1675` switchConversation `retain(|_, a| a.conversation_id.as_deref() != Some(&conversation_id))` 注释明说「防 init 重建的内存 HashMap 被清空,重启恢复链路:restore → switchConversation(此处不清目标)→ ai_pending_tool_calls → ai_approve」——该恢复链前提是 restore 重建的 pending 在 switchConversation 时仍在内存,但 L0 clear(批2 起)在冷启动 AiChat onMounted 时就触发(早于用户手动 switchConversation)抹掉 restore 成果。**矛盾真实存在,但归属批2+,非 batch8**。
**为何另一位审查 agent 误判 batch8 引入**:`git show 6ad4ec2 -- lib.rs` 只看 batch8 diff 内的 `+ session.pending_approvals.clear()`(那是合并后上提的外层一处),**未对比批2 版本(5c15b72)已有两处 clear**,误判为新增。印证 memory [[code-review-anti-contamination]]:对抗核验须跨版本 git diff,不能只看单 commit diff 的 +/- 行。
**⚪ WATCH-1(AiCompleted 补偿前端「各归各复位」描述精度,low)**:lib.rs:74 注释 + prompt 声称「前端 useAiEvents.ts:133-140 按 conversation_id 路由各归各复位」,实际 useAiEvents.ts:133-140 `isCurrent=false`(非当前展示 conv)收到 AiCompleted 时清的是**全局单值 `state.generatingConvId=null`**(:136),非按 conv 各清。因 devflow 前端 generatingConvId 是单值(同时刻只展示一对话),多 conv 后台跑 loop 切走后完成时清全局 null 无副作用。**补偿事件设计意图(防 HMR 残留 generating 卡死)达成**,描述略宽泛。建议注释改为「前端按 conversation_id 过滤,非当前 conv 收到 AiCompleted 清全局 generatingConvId(单值)」。非阻塞。
**🟡 WATCH-2(L0 clear 致冷启动审批丢失·跨批遗留·非 batch8)**:L0 `lib.rs:62 session.pending_approvals.clear()` 无条件清空,抹掉 `state.rs:340 restore_pending_approvals` 重建的 pending → 冷启动后 ai_pending_tool_calls(内存数据源)返空 → 待审批 toolCard 不显示。**归属批2+(5c15b72 起两分支无条件 clear),非 batch8 引入**。batch8 仅合并 clear 位置(行为等价)。修复方向(回填 todo,供主代/用户评估,**不属 batch8 审查范围必修**):
- **方案 A(推荐)**:`lib.rs:62 clear()``retain(|_, a| !a.recovered)`。仅清非 recovered(本次会话产生的死 pending / HMR 残留),保留 restore 重建(`PendingApproval.recovered=true`,audit.rs:331)。对齐 switchConversation 保护意图,HMR 清死 pending 与冷启动保 restore 两全。
- **方案 B**:删 `lib.rs:62`。pending 清理由 switchConversation retain / delete_conversation retain / ai_chat_clear 各路径精确管理。
- **判定**:此 🟡 是**跨批遗留问题曝光**(批2 起就存在),batch8 未恶化(行为等价)。是否修属产品决策(冷启动审批恢复是否是承诺功能),非 batch8 审查必修项。
**⚠️ batch5 中间态说明(审查范围外记录)**:
- 本次审查**全程未跑 cargo**(memory [[review-batching-worktree-transient]]:F-09 batch5 agent 正在改 agentic.rs/state.rs,跑 cargo check 抓中间态致误报)。源码形态核验 + git diff 跨版本时序对抗 > check 快照。主代兜底 cargo check --workspace EXIT 0 + test 98 passed 为稳定态报告(本审查采信但不复跑)。
- **待修项回流 todo**: **无** 🔴 项(batch8 本身 PASS 无回归) · 🟡 **1 项跨批遗留**(BUG-260619-06-cross-batch L0 clear 致冷启动审批丢失·**归属批2+ 非 batch8**·方案 A `retain(!recovered)` 推荐·供主代/用户评估是否修,非 batch8 审查必修)
### CR-260619-07 F-09 batch4 启用多会话上线(决策e真并发·前后端·agent f09-batch4·commit d899c58) — ✅ 已审(PASS·⚪2·巡检 2026-06-19 独立 grep/read 核验)
- **范围**(前后端大改,F-09 上线):IPC 签名加 conv_id(ai_is_generating/ai_chat_send/ai_chat_force_send/ai_chat_stop + 内部移除 active 一致性校验)+ 删 switch readonly(决策 e)+ 删 ai_conversation_create 强制结束旧 loop + 删双写桥接/顶层字段(messages/generating/stop_flag/notify/iteration_used/agent_language/model_override/session_trust 全删,per_conv 唯一真相源)+ 删双写代码(全模块)+ pending_approvals retain 目标 conv + finalize_pending_placeholders 按 conv_id + 前端(api/ai.ts+useAiSend/useAiWindow)传 conv_id。保留 session_state(SW 预留标 allow)+ readonly(前端不读)。
- **审查要点**:① IPC 签名 conv_id 前端传参(api/ai.ts/useAiSend/useAiWindow);② 删 readonly 后 switch 生成中可切(决策 e)+ 后台 conv 跳过 reload;③ 顶层字段全删(per_conv 唽一)+ 双写清理完整(grep 顶层引用 0);④ conv_id 上下文一致(前端 activeConversationId);⑤ pending_approvals retain 目标 conv(不误杀他 conv);⑥ switch reload 边界;⑦ 保留项(session_state/readonly)合理(零调用方预留按用户指导保留)。
- **验证(主代兜底)**:cargo check --workspace 0 + vue-tsc 0 + test 98 + grep 顶层删除/per_conv 唯一印证。
- **⚠️ 双会话回归留用户验收**:开 A 跑→切 B 发→A 后台不退出不污染 B;审批/max 续跑跨会话。后端 per_conv 隔离 + 事件 conversation_id 路由保障。
- **关联**:F-09 完整(batch1-8+batch4 上线)。
**复审结论(2026-06-19·巡检独立 grep/read 核验源码当前形态 + 决策e 真并发对抗分析·commit d899c58)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
**7 审查要点逐项核验(file:line 佐证)**:
| # | 要点 | 佐证 | 判定 |
|---|---|---|---|
| ① | IPC conv_id 前端传参 | `api/ai.ts`:sendMessage/forceSend/regenerate/edit/continue/stop/ai_is_generating 全加 conversationId;`useAiSend.ts`:doSend :125/127 传 `state.activeConversationId`、regenerate :174-183/edit :244-252 有 `if(!convId)` null 保护 | ✅ |
| ② | 删 readonly 后台 conv 可切 | `agentic.rs:546` loop 退出判据 `!per_conv.contains_key(&conv_id)`(conv 存在性,非 active 一致性);:539-541 注释「旧 loop 跑自己 conv 不污染他人,active 切换不让退出」 | ✅ |
| ③ | 顶层字段全删 + 双写清理 | grep `session.generating/messages/stop_flag/notify/iteration_used/agent_language/model_override/session_trust` 全命中**注释**(agentic.rs:400/993、mod.rs:474 等),无实际顶层读写代码(cargo check 0 印证) | ✅ |
| ④ | conv_id 上下文一致 | `run_agentic_loop:338` conv_id 入参快照,全程 `session.conv(&conv_id)`;`try_continue_agent_loop:1075` 显式 conv_id 入参(:1214 注释「active_conversation_id 字段移除,改入参 conv_id」);loop 内 grep active_conversation_id **无实际读写** | ✅ |
| ⑤ | pending_approvals retain 目标 conv | switchConversation(`mod.rs:482`)/deleteConversation(:526)/ai_chat_clear(`chat.rs:536`)/ai_chat_stop(:895/1015)全 `retain(... != target)`,不误杀他 conv,保护 restore 重建链路(注释 :480-481) | ✅ |
| ⑥ | conv_id=null fallback | `ai_is_generating:178-182`(null→fallback active→空返 false)/`ai_chat_send:218-233`(null→active→懒创建 new_id)/`ai_chat_stop:994-998`(空返 Ok) | ✅ |
| ⑦ | ai_continue/stop_loop 移除 active 校验 | `chat.rs:1094/1129` 实际只 `conv_read(&conv_id).generating` 校验,**无 active 一致性**(决策e 落地) | ✅ |
**对抗核验印证(防污染)**:
- **CR-06 修复延续 ✅(关键)**:`lib.rs:62 session.pending_approvals.retain(|_,a| !a.recovered)` 在 batch4 后**仍保留**(:59-62 注释「BUG-260619-06 修复」)。batch4 删顶层 generating 双写复位(:45)+ 删 active 兜底(顶层退役后无意义),但 retain 修复完整保留 → 上轮审 CR-06 的 🔴 未被新批破坏。
- **决策e 真并发核心 ✅**:loop 退出判据 conv 存在性(:546)+ loop conv_id 快照不读 active(:348/368)+ guard/stop_flag/notify/messages 全 per_conv(:97/1100/1135)+ 事件 emit 全带 conversation_id → A 后台 loop 切 B 不退出不污染,设计契约与 switchConversation retain 一致。
- 主代自审 PASS + cargo check 0 + test 98,独立核验源码形态印证(非信声明)。决策e 真并发时序保障经多维度交叉(前端传参/后端 fallback/loop 快照/退出判据/retain 路由)成立。
**⚪ low(2)**:
1. **注释过时(6 处,功能正确但误导维护)**:`agentic.rs:400`(model_override「入参←session」→ 实为函数入参)/`:993`(session.iteration_used→ 实为 per_conv)/`chat.rs:1079`(session.iteration_used→per_conv)/`:1082-1083`&`:1117-1118`(ai_continue/stop_loop 文档注释「校验 active 一致性」→ batch4 已移除)/`mod.rs:474`(批2 迁移说明残留)。建议批量更新对齐 batch4 per_conv 化 + 移除 active 校验。
2. **双会话实机验收(CR-07 :883 已声明)**:开 A 跑→切 B 发→A 后台不退出不污染 B;审批/max 续跑跨会话。后端 per_conv 隔离 + 事件 conversation_id 路由 + loop 退出判据 conv 存在性已核验保障,真并发时序(两 conv 同时 loop + provider pool/llm_concurrency 竞争)需实机验收。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260619-08 df-ai 会话意图识别层 intent.rs 新建(纯函数模块·不接入 loop·commit 7724cb7) — ✅ 已审(PASS·⚪1 low·独立 grep/read 核验)
- **结论(2026-06-19·独立 grep/read 核验 commit 7724cb7)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **范围**:`crates/df-ai/src/intent.rs`(新建 682 行)+ `crates/df-ai/src/lib.rs`(`pub mod intent;` 注册 4 行)。
**6 维度逐项核验(file:line 佐证 + 判定)**:
| 维度 | 核验点 | 佐证 | 判定 |
|---|---|---|---|
| ① 识别规则 | 优先级取舍 + 置信度封顶 + 求和 | `recognize:289-301` 依次 best_in_group(SPECIFIC→ENTITY→GENERIC) 命中即 return 不向下累积 · `best_in_group:305-329` 组内 score=Σ命中权重 `:315` + `.min(1.0):322` 封顶 · 平局 `b>=conf 保留旧:324`(SPECIFIC_GROUP 数组 Code 首位 :200 同分 Code 胜) | ✅ |
| ② 工具名对齐 | ToolDomain 29 工具名逐条 vs tool_registry.rs | 自动化 perl 比对:registry 29 == intent 29,**in registry but NOT in intent: (none) / in intent but NOT in registry: (none)** · Data 18(list_projects..get_task_count)+File 10(read_file..rename_file)+Http 1(http_request)与 `tool_registry.rs:1838-1852` 基线测试 expected 完全一致 | ✅ |
| ③ ModelTier None 预留 | 恒 None,未误接 provider/model | `suggested_model_tier:376-379` body 仅 `None` + TODO 注释 · ModelTier 枚举 :80-87 仅 Fast/Standard/Heavy 定义,无 provider/model 关联 · 单测 :653-671 遍历 11 意图全断言 None | ✅ |
| ④ 独立性 | 不接入 loop/不读 registry/不碰 src-tauri | `git show --stat 7724cb7` 仅 2 文件(intent.rs 新增 + lib.rs +4)·lib.rs:12 仅 `pub mod intent;` · intent.rs grep 无 `use crate::`/`commands`/`tool_registry`/`agentic` 运行期依赖(仅文档注释 :6/:15-16/:222-223 提及)·无 src-tauri 改动 | ✅ |
| ⑤ 测试覆盖 | 36 单测覆盖关键路径 | `cargo test -p df-ai --lib intent::` **36 passed 0 failed**(EXIT 0 复跑印证无中间态漂移)·覆盖:枚举 as_str / recognize 中英文 / 优先级 SPECIFIC>ENTITY>GENERIC / 边界空串+空白+无关键词 / 置信度封顶 / tool_subset 各 domain + fallback 空 / 跨 domain 去重 / ModelTier None / Default 构造 | ✅ |
| ⑥ 设计文档对齐 | 方式 A 规则识别 | `意图识别层论证-2026-06-19.md` 第 11 条「方式 A 规则(零延迟零成本)落 intent.rs + tool domain 标签」+ 第 58 条「子集扩充非裁剪 / None fallback 全量零回归」+ 第 13 条「loop 入口生效一次不进 loop 体」全对齐实现 · 文档预估 ~200 行,实现纯逻辑约 380 行(682 含 36 单测 ~300 行)量级合理 | ✅ |
**对抗核验印证**:
- **维度① 平局处理**:recognize_debug_zh 测试输入「bug 复现 调试 排查」(bug→Code=1.0 / 复现0.9+调试1.0+排查0.9=2.8→1.0→Debug)同分 1.0,Code 在 SPECIFIC_GROUP 首位先遍历,`b>=conf`(1.0>=1.0)保留 Code → 返 Code ✅ 测试 :420 断言 Code 正确反映此行为
- **维度② 维度声明文档笔误**:登记项写「Data 18/File 10/Http 1」(暗示 28)但实际 18+10+1=**29**。tool_registry.rs:1830 基线测试断言 `29(18 data + 10 file + 1 http)`。intent.rs 正确对齐 29,非 28。**声明笔误不影响代码正确性**,仅文档表述
- **关键词歧义点(规则识别固有局限,非 bug)**:"修改"(File 0.7)+ "项目"(Project 1.0)同命中 ENTITY 组,File 在 ENTITY_GROUP 首位先遍历 → "修改项目名称"会识别为 File 非 Project。方式 A 准确率 70%+(设计文档第 27 条声明),子集扩充非裁剪 + None fallback 零回归兜底,非 high/med
- **测试用例歧义容忍**:recognize_search_zh :454-461 输入「搜索 代码 grep」(Search 搜索1.0+grep1.0=2.0→1.0 vs Code 代码1.0→1.0 同分,Code 首位胜 → 返 Code),用 `matches!(Search|Code):460` 容忍已知歧义 ✅ 测试合理(见 low-1)
**⚪ low(可选·非必修)**:
1. **recognize_search_zh 测试注释与用例自相矛盾**(`intent.rs:457-459`):注释说「调整用例避免歧义」但用例本身仍触发歧义(仅靠 `matches!(Search|Code)` 容忍)。建议要么改输入为纯 Search 关键词(如 "grep 查找" 已有 recognize_search_pure :464 覆盖,本用例冗余),要么删注释「调整用例」表述保留 `matches!` 容忍说明。纯测试可读性,非功能。
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260619-09 F-260619-03 文件访问权限模型 Phase B+C + anthropic_compat 连续user合并(后端 state/audit/chat/conversation/mod/lib + 前端 DirAuthDialog 等 15 文件·commit bddbfd4 + 7c98134) — ✅ 已审(PASS·🟡1⚪2)
- **结论(2026-06-19·独立 grep/read 核验 commit bddbfd4+7c98134)**: ✅ **PASS** — 🔴0 🟡1 ⚪2
- **验证**: cargo test -p devflow --lib state:: **15 passed 0 failed**(Phase A/B/C 全覆盖) / cargo check -p devflow **EXIT 0 无 warning** / vue-tsc --noEmit **EXIT 0**。独立 grep 核验 6 维度源码当前形态,不信 agent 自报。
**6 维度逐项核验**:
| # | 维度 | 判定 |
|---|------|------|
| 1 | 权限模型正确性 | ✅ session 进程级全局(`Arc<RwLock<AllowedDirs>>` state.rs:305),clear 时机完整(conversation.rs create:74/switch:151/delete:230);NeedsAuth 挂起恢复链路完整(pending_count+=1 audit/mod.rs:283 + 占位 tool_result :299 + emit AiDirAuthRequired :300 + ai_authorize_dir→try_continue chat.rs:628) |
| 2 | 黑名单完整性 | ✅ Win(System32/SysWOW64/System/Program Files×2) + Unix(/etc /usr /bin /sbin /boot /dev /proc /sys)覆盖合理;分段精确匹配防误伤(test_blacklist_no_false_positive 印证 "my program files backup" 不拒);黑名单优先于白名单双判(is_authorized state.rs:353 + check_path_authorization :441 独立判);**write_file 新建路径三层防护**:预校验 check_file_tool_auth(audit/mod.rs:253) + handler 词法层 is_authorized(tool_registry.rs:326) + handler canonicalize 层 is_authorized(:334),不存在路径也判黑名单,不绕过 |
| 3 | 写约束覆盖 | ✅ tool_registry.rs 逐条核验:delete_file=High(:1438) / write_file=Medium(:1061) / patch_file=Medium(:1175) / append_file=Medium(:1401) / rename_file=Medium(:1507);读类 read_file/list_directory/file_info/search_files=Low。路径授权放行后**仍走 RiskLevel 审批**(正交性:check_file_tool_auth 在 risk_level 分类**前**调 audit/mod.rs:233-259,Authorized drafts 才进下方 Low/Med/High 循环 :325) |
| 4 | 挂起恢复链路 | ✅ ai_authorize_dir(chat.rs:520)三分支完整:deny→Err+恢复 loop(:542) / once→add_session_allowed_dir(:575) / always→add_persistent_allowed_dir(:580,失败降级 session);复用 ai_approve 执行链(run_workflow 特殊处理 :589);**path_auth 守卫双向严密**:ai_approve :362 拦 path_auth 挂起回滚 pending+Err,ai_authorize_dir :534 拦普通审批 ok_or_else Err |
| 5 | 前端弹窗交互 | ✅ DirAuthDialog.vue 三选项(once/always/deny)+ 双重守卫(pendingDirAuth + isViewGenerating :55)+ dirAuthActing 防重入(:54);useAiEvents.ts pendingDirAuth 置位(:231)+ 三处清空(AiApprovalResult:335/AiCompleted:357/AiError:407);NO_RESET_WATCHDOG 含 AiDirAuthRequired(:47)不触发整流超时 |
| 6 | 跨会话隔离 | ✅ clear_session_allowed_dirs(state.rs:673)仅清 allowed_dirs.session,不动 per_conv/pending_approvals;conversation.rs create/switch/delete 的 retain 仅清目标 conv 的 pending;**session 全局单例不构成 F-09 回归**(Phase B 沿用 active 单全局模型,agent 风险点5 已承认,真多会话独立临时授权需迁 PerConvState 属后续工作) |
**附带 7c98134 merge_consecutive_users 核验**: anthropic_compat.rs:265 合并相邻 user 块为一条 user 含 [tool_result..., text] blocks 数组,String/Array content 双形态规范化(:282-294),while 循环不增 i 续合并多连续 user(:297),逻辑正确,打破 GLM 1214 连续 user 恶性循环。
**🟡 MED-1**: `state.rs:673` + `audit/mod.rs:245` + `tool_registry.rs:1065` — 预校验(process_tool_calls)与 handler 闭包两端**独立 read lock** 取 allowed_dirs.session 快照,并发 clear_session(create/switch/delete)下存在"预校验放行→handler 拒绝"窄窗口不一致。mod.rs:251 注释声称"两端授权判定一致"未标注并发限制。后果仅工具返 Err(LLM 收错误自行调整,非数据破坏),触发条件极窄(用户在工具执行瞬间切会话+该会话有 session 临时授权目录)。建议:注释补充"并发 clear_session 下两端快照可能不一致,后果为工具 Err 非数据破坏"说明,或后续 session 字段迁 PerConvState 时顺带消除。
**⚪ LOW-1**: `state.rs:412` is_in_system_blacklist Unix 分支未覆盖 `/var`(日志/spool/cron)和 `/root`(root 家目录)。影响有限:黑名单是用户误授权兜底,/var/root 非系统核心不可替换目录,且需用户手动授权才触达。后续可按需补充。
**⚪ LOW-2**: `state.rs:327` AllowedDirs.session 进程级全局单例,F-09 多会话并发下各会话无法独立临时授权(切走即清,切回需重新授权)。agent 实施报告风险点5 已明确承认此限制,Phase B 对齐 active 单全局模型,非新回归。真多会话独立临时授权需迁 PerConvState.allowed_dirs,属后续工作。
- **待修项回流 todo**: **无** 🔴/🟡 项(MED-1 为观察级注释补充,非阻塞性代码修复;LOW 两项为已知限制/可选扩展)
---
### CR-260619-10 消息级溯源 P0 地基(ChatMessage.id + ai_messages 拆表 + V21 迁移 + AiMessageRepo + audit message_id 列·commit e981c14) — ✅ 已审(PASS)
- **结论(2026-06-19·独立 grep/read 核验 commit e981c14 + cargo test 复跑)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: `cargo test -p df-storage` 45 单测 + 11 集成全 passed(V21 5 测:新库空跑/老库迁移/坏数据/幂等重跑/列补建 + message_repo 5 测全绿,无漂移)。git show e981c14 10 文件 diff 逐文件核验。
**5 维度逐项核验**:
| # | 维度 | 判定 |
|---|------|------|
| 1 | 迁移正确性(migrate_v21 migrations.rs:374) | ✅ 幂等三重防护(COUNT 探测 :400-407 / INSERT OR IGNORE :472/:489/:505 / schema_version PK IGNORE)+ 坏数据 warn+continue :432-435 + 全量逐条 :438-483 + 新库空跑(V9_SQL :727-744 同步建 ai_messages 表 + message_id 列)+ msg_migrated_{conv_id}_{seq} :442 填 ai_messages.id 正确(V21 测 v21_legacy_db_migrates_messages 覆盖老库迁移 + 字段提取 + seq 递增) |
| 2 | id 格式(new_message_id types.rs:134) | ✅ AtomicU64 COUNTER + ts 并发安全 + 时钟回拨单调(计数器兜底)+ serde `#[serde(default)]` 老 JSON → None round-trip + 6 构造器(provider.rs:45 system/48 user/51 assistant/54 assistant_with_tools/57 tool_result/63 user_parts)全覆盖 `id: Some(new_message_id())` |
| 3 | AiMessageRepo(message_repo.rs) | ✅ 5 方法 SQL/参数/返回正确:insert_batch INSERT OR IGNORE 幂等 :80 / list_by_conversation ORDER BY seq :115 / delete_range 左闭右开 + None 末尾 :145-153 / update_status :179 / update_content_by_tool_call_id 定点 :207。baseline_tests(mod.rs:263)断言 ai_messages 白名单 None 合理(专用 Repo 不进通用 query 路径) |
| 4 | P0 边界(未误接 P1/P2) | ✅ AiMessageRepo 仅 df-storage 自身注册 + test,**0 外部调用方**(grep crates/src-tauri 业务侧 0 命中)/ ContextManager 读写路径未改 / save_conversation 未改双写 / audit message_id None 占位仅 finalize.rs:40 单点(P1 接真值) |
| 5 | 迁移耦合点(types + migrations) | ✅ types.rs:75-77 ChatMessage 定义注释「改字段名必须同步更新 migrate_v21」+ migrations.rs:370-373 反向标注「同步标注已在 types.rs 加注释」,双向闭环 |
**主代核查·审查防污染印证**:
- 迁移数据完整性逻辑独立走查:messages JSON → Vec<serde_json::Value> 逐条 :430,seq `messages.iter().enumerate()` :438(0 起递增),status 归一化 None/空→"active" :458-461(列 NOT NULL DEFAULT 'active' 对齐),created_at timestamp 优先 fallback conv_created_at :466-469。
- audit 14 处 message_id 说法:范围描述措辞,实际 grep src-tauri 仅 finalize.rs:40 单点构造 AiToolExecutionRecord(AiToolExecutionRecord { 仅 1 处),14 处指未来调用覆盖面。单点 None 占位符合 P0 边界(列已建、值待 P1),非缺陷。
- workspace 自验 EXIT 0(commit message 自报)+ 本会话 cargo test 45+11 复跑印证无漂移(memory [[review-batching-worktree-transient]] P0 与 MCP 并行警惕快照漂移,以实际磁盘内容为准已落实)。
- **⚪ WATCH-1**: V21_SQL(migrations.rs:498)与 V9_SQL(migrations.rs:727)两处 ai_messages 建表 SQL 镜像,改动须两边同步——已在两处加注释 :496-497 / :724-726 标注,后续加列/改约束需双向维护(低风险,有注释护栏)
- **待修项回流 todo**: **无** 🔴/🟡 项
### CR-260619-11 MCP Server(df-mcp crate + stdio 协议 + CLI 子命令·commit f217e39) — ✅ 已审(PASS·⚪2)
- **结论(2026-06-19·独立 grep/read 核验 commit f217e39 + cargo test df-mcp 8 passed 复跑)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: cargo test -p df-mcp 8 passed 0 failed 复跑确认(initialize/tools_list 高风险过滤/read_only/路由/High 拒/create+list 往返/visible 谓词全过)。HEAD vs f217e39 在 df-mcp 范围零 diff,源码形态即 commit 形态。19 工具逐条核验 vs tool_registry + df-storage Repo 方法全部存在。
**6 维度逐一核验**:
| # | 维度 | 判定 |
|---|------|------|
| 1 | JSON-RPC 协议正确性 | ✅ protocol.rs:109-127 路由 initialize/initialized/tools/list/tools/call/ping 5 method + 错误码常量 -32700/-32600/-32601/-32602/-32603 齐全。server.rs:60-106 stdin 逐行 read_line + write_response 单行 \n framing 正确。Notification(id=None)不回(:92-102)。initialize 返 serverInfo+capabilities.tools(:117-130)。tools/list 返 `{tools:[{name,description,inputSchema}]}` 符合 MCP Tool schema(:136-143)。tools/call 返 `content:[{type:"text"}]` + isError 区分业务错/协议错(protocol.rs:185-201) |
| 2 | **安全降级三层严密性(本 CR 重点)** | ✅ **严密无漏** — 层①tools/list visible 谓词(server.rs:183-189:`risk!=High` 非ro / `risk==Low` ro)过滤 4 个 High(delete_project/delete_task/delete_idea/run_workflow)永不暴露。层②dispatch 兜底(server.rs:163 `if spec.risk==RiskLevel::High` 拒)+ read-only 下 Medium 也拒(:155 `spec.risk!=RiskLevel::Low`)。层③handler 二次拒(tools.rs:305/489/590/651 四个 High handler 体内无条件 return error,连 ctx/args 都不读)。**对抗核验**:purge_project df-mcp 未注册(物理删比软删更危险,数据层 MCP 故意只暴露 delete_project 软删,设计正确非遗漏);方法路由仅 5 个无其他 method 触发工具;文件工具(read/write/patch/list/search/command)0 命中。测试 tools_call_high_risk_is_rejected + tools_call_read_only_rejects_medium + tools_list_excludes_high_risk_by_default 全覆盖 |
| 3 | 不暴露文件系统 | ✅ grep `read_file\|write_file\|patch_file\|list_directory\|search_files\|run_command\|delete_file\|rename_file\|append_file\|http_request` 全 df-mcp/src **0 命中**。19 工具全为数据层(项目/任务/灵感/工作流/回收站),防绕过应用内路径校验 |
| 4 | 工具复用正确性 | ✅ 19 工具逐条核验:list_projects/get_project/create_project/update_project/delete_project/bind_directory/list_tasks/create_task/update_task/advance_task/delete_task/list_ideas/create_idea/update_idea/delete_idea/evaluate_idea/run_workflow/list_trash/restore_project。复用 df-storage Repo(ProjectRepo/TaskRepo/IdeaRepo)零重复 CRUD。advance_task 走 advance_status_atomic CAS(task_repo.rs:151 WHERE id AND status=expected + deleted_at IS NULL 兜底)。bind_directory 走 find_path_conflict(project_repo.rs:190)+ df-mcp 自实现 normalize_path(tools.rs:695 与 df-project scan.rs:24 逐字一致)。evaluate_idea 本地启发式(tools.rs:623 纯函数,不调 LLM) |
| 5 | CLI 子命令 | ✅ main.rs:16 首参 == "mcp-server" 路由 + run_mcp_server(:28)解析 --db/--read-only/--print-config/-h/--help。default_db_path(:112)Windows %APPDATA%/devflow/devflow[-dev].db(cfg!(debug_assertions)→devflow-dev.db,release→devflow.db)与 GUI 同库。--print-config 输出 `{"mcpServers":{"devflow":{"command":"devflow","args":["mcp-server"[,"--read-only"]]}}}` (:66-70)。windows_subsystem="windows" 保留(main.rs:2) |
| 6 | 并发安全 | ✅ db.rs:22 `PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;` 确认启用(run_server server.rs:38 复用 Database::open)。仅 stdio/本地无认证,依赖本地隔离(Tauri 桌面应用非网络服务,门槛可接受,与 tool_registry Path Traversal 同评估口径 CR-260618-11 #8) |
**⚪ 可选优化 (2)**:
| # | file:line | 问题 | 建议 |
|---|-----------|------|------|
| 1 | `tools.rs:664-665` | `list_trash``unwrap_or_default()` 吞 DB 错误返空列表。MCP 对外暴露,DB 故障时客户端收 `{projects:[],tasks:[],...count:0}` 误判「回收站为空」(与 CR-260618-13 SMELL-P0-1 同款降级模式,但 MCP 对外场景误导性更高) | 改 `match { Ok(v)→v, Err(e)→return err_str(e) }` 让客户端感知 DB 故障 |
| 2 | `tools.rs:616` | `evaluate_idea` `let _ = repo.update_full(&rec).await;` 丢弃写回结果。scores 写回失败时客户端仍收旧 idea + 新 scores(响应用写回前的 idea clone :617)轻微数据不一致 | 写回失败时 err_str 返回,或响应标注「scores 未持久化」(只读评估场景影响小,可接受现状) |
**主代核验印证(防污染)**:
- agent 自报 8 passed,主代独立 cargo test -p df-mcp 复跑 8 passed 0 failed 一致 ✅
- 登记项维度2 把 purge_project 列为 High 风险工具之一,独立 grep 核验 df-mcp **未注册 purge_project**(仅 delete_project 软删),描述偏差非 df-mcp 遗漏 — df-mcp 设计正确(数据层不暴露物理删,tool_registry.rs 有 purge_project 但 MCP 不暴露)
- 三层安全降级源码佐证齐:visible 谓词 server.rs:183 / dispatch 兜底 server.rs:163 / handler 二次拒 tools.rs:305+489+590+651,四 High 工具三层全覆盖
- **待修项回流 todo**: **无** 🔴/🟡 项(⚪2 可选:list_trash 吞 DB 错误返空列表对外误导 / evaluate_idea 丢弃写回结果,非阻塞)
---
### CR-260619-12 消息级溯源 P1 + 流式失败重试修复(context/audit/知识/knowledge_timeline/chat/useAiEvents·commit d3e7640) — ✅ 已审(PASS·🟡1⚪1)
- **结论(2026-06-19·独立 grep/read 核验 commit d3e7640 源码当前形态 + cargo test --workspace 复跑)**: ✅ **PASS** — 🔴0 🟡1 ⚪1
- **验证**: 独立 grep/read 核验 7 文件源码当前形态(diff 快照对比) / `cargo test -p df-ai context::tests::last_` 4 测全绿(last_assistant_message_id_returns_latest / last_user_message_id_returns_latest / last_message_id_none_when_no_such_role / last_message_id_none_for_legacy_no_id) / `cargo test --workspace` 全 11 crate 0 failed(df-ai 139 passed 含 4 P1 新测 / df-workflow 23 / 其余全 ok)。
**5 维度逐项核验**(源码当前形态为准,非 diff 快照):
| # | 维度 | 判定 |
|---|------|------|
| 1 | P1 message_id 传递完整性 | ✅ audit/mod.rs 6 处 audit_tool_call 全传 current_message_id(:283 rejected / :319 pending / :396 auto_dedup / :429 pending / :483 auto_trust / :536 auto,独立 grep 确认无遗漏·cache.rs:106 仅注释引用非调用) / process_tool_calls :224-227 取 `session.conv_read(conv_id).messages.last_assistant_message_id()`,agentic/mod.rs:882 assistant_with_tools 已 push 后 :913 才调 process_tool_calls,取到的就是本轮 assistant id / 知识提炼 knowledge_inject.rs:355-361 反向扫描 messages 取末条 assistant id / 4 处 build_knowledge_context(chat.rs:151/317/951/1115)全传 user_message_id.as_deref() / 对抗性核验:新构造 ChatMessage(provider.rs:47/50/53 user/assistant/assistant_with_tools 全 `id: Some(new_message_id())`)非 None,**无 None 泄漏到已处理 assistant** ✅ |
| 2 | 老数据兼容 | ✅ ChatMessage.id=None(老反序列化)→ last_assistant_msg_id/last_user_message_id 返 None(context.rs:483 `and_then(\|t\| t.message.id.clone())` 直接透传 None)→ source_ref 降级 conv:{conv_id}(knowledge_inject.rs:455-458 match / knowledge_timeline.rs:91-94/108-111 match None 分支),**无 conv_msg:None 无效值** ✅ |
| 3 | 症状1 修复(useAiEvents.ts) | ✅ AiError :401 flushCurrentText 在 :404 `state.currentText=''` 前(顺序正确) / flushCurrentText :134 `last.role==='assistant' && !last.isError` 跳错误气泡(AiStreamRetry :199 push 的 isError 气泡) / AiError 错误气泡 :417 在 flush 之后 push(不冲突) / **对抗性场景核验**:AiStreamRetry→AiError 链路下末条是 isError 气泡,flush 跳过不覆盖错误提示 ✅ / AiAgentRound/AiCompleted 正常路径末条非 isError,`!last.isError` 仍 true 正常回填,无副作用 ✅ |
| 4 | 症状2 修复(ai_regenerate) | ✅ chat.rs:109-124 `pop_last_assistant_round` 返 false 时取 `all_messages_clone().last()``MessageRole::User`,last_is_user=true 不报错直接走重跑路径(末尾 user 等价重发) / false 仅在空对话/末尾非 user 才 `conv.generating=false; return Err("没有可重新生成的回复")` ✅ |
| 5 | 重试不丢参数 | ✅ ai_regenerate :172 spawn `run_agentic_loop(...)` 入口读 `session.conv(&conv_id).messages` 完整历史(agentic/mod.rs:570/668 全量 messages 迭代),不丢历史参数 ✅ |
**🟡 MED-1**(`src/composables/ai/useAiEvents.ts:401`): AiStreamRetry→AiError 失败重试链路下部分回复仍可能丢。场景:流式失败→AiStreamRetry push/更新 isError 气泡(末条变 isError)→重试耗尽→AiError flushCurrentText 取末条(isError 气泡)被 `!last.isError` 跳过,**部分回复不会回填到占位 assistant 气泡**(占位气泡 content 仍为空),随后 :404 `state.currentText=''` 清空,本轮部分回复丢失。纯 AiError(无 AiStreamRetry 前导)路径完全有效,仅重试耗尽链路存在覆盖盲区。建议:flushCurrentText 跳过 isError 时改为回填到「isError 气泡之前的最近非 isError assistant 气泡」,或 AiError 收尾前显式查找占位气泡回填。
**⚪ LOW-1**(`src-tauri/src/commands/ai/knowledge_inject.rs:355`): 知识提炼 source_ref 取「对话最新 assistant」(反向扫描全量 messages,非提炼窗口 recent 6 条内的最新)。OnComplete 异步 spawn 时序下,若 OnComplete 触发后用户又发新消息(虽 OnComplete 通常即时触发),last_assistant_msg_id 可能指向非真正产出该条知识的 assistant。精度边界非正确性问题,source_ref 仍指向对话内合法 assistant,展示侧兼容。注释已说明设计选择(「确保取到对话最新 assistant,即便它不在提炼窗口内也属于本轮 AI 产出」)。
**审查防污染印证**:
- audit 6 处调用方计数:独立 grep audit_tool_call 命中 finalize.rs:22 定义 + mod.rs 6 调用(:283/319/396/429/483/536)+ cache.rs 注释引用。**与登记项「6 处」一致**,无遗漏无多算。
- chat.rs build_knowledge_context 调用方计数:独立 grep 命中 4 调用(151/317/951/1115)+ 1 定义(knowledge_inject.rs:200)。**与登记项「4 处」一致**。
- assistant push 时序:注释声称「agentic.rs:882 已 push」,独立 Read agentic/mod.rs 确认 :882 `session.conv(&conv_id).messages.push(msg)` 在 :908 has_tool_calls 判断 + :913 process_tool_calls 之前,**取到的 last_assistant_message_id 就是刚 push 的本轮 assistant**(provider.rs:53 assistant_with_tools 必生成 id 非 None),对抗性核验成立,无 None 泄漏。
- cargo test --workspace 独立复跑 EXIT 0(非主代自报),11 crate 全 ok,df-ai 139 passed 含 4 P1 新测,印证 memory [[review-batching-worktree-transient]] 教训(源码形态 > check 快照)。
- **待修项回流 todo**: **无** 🔴/🟡 项(🟡 MED-1 主代已修:flushCurrentText 改从末尾向前找非 isError 占位 assistant 写入,AiStreamRetry→AiError 链路部分回复不再丢)
### CR-260619-13 任务查询按状态过滤(list_tasks status·commit 2f8b13f) — ✅ 已审(PASS·⚪1)
- **结论(2026-06-19·独立 grep/read 核验 commit 2f8b13f)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **核验**:`cargo check -p df-mcp -p devflow --manifest-path src-tauri/Cargo.toml` EXIT 0。逐维度核验源码当前形态(commit 后):
- **维度1 status 过滤正确性** ✅:tool_registry.rs:620-622 `args.get("status").and_then(|v| v.as_str())``tasks.retain(|t| t.status == status)`。TaskRecord.status: String(models.rs:59),`String == &str` 通过 PartialEq 成立;status 不传时跳过 retain(可选)语义正确。df-mcp tools.rs:357-359 `list.retain(|t| t.status == *st)` 同理。
- **维度2 7 态完整** ✅:task_advance_node.rs:147 spec enum `["todo","in_progress","in_review","testing","done","blocked","cancelled"]` 与 commit 文案 7 态逐字一致。filter 纯 String 相等不校验合法性(未知 status 返空集,无 crash/panic),行为可接受。
- **维度3 df-mcp 一致** ⚪:handler 功能就绪(tools.rs:349,357-359),**但 spec(tools.rs:108)只声明 `project_id`,未声明 `status`** —— MCP 客户端无法从 schema 发现该参数。commit message 已声明「spec 声明待补(中文 Edit 编码限制)」,已知延后。记 ⚪ WATCH-1。
- **维度4 不破坏现有** ✅:tool_registry project_id 分支(:614-618)+ offset/limit/has_more(:624-627)路径不变;df-mcp project_id_filter + list_active 不变,新增 status 仅追加 retain。注意 df-mcp 原代码 `if let Some(pid) = project_id_filter` 改为 `&project_id_filter`+`*pid`(借用重构),行为等价(project_id_filter 仅借用未移动)。
- **⚪ WATCH-1**:df-mcp list_tasks spec(tools.rs:108)未声明 status 参数,MCP 客户端 schema 不可见。commit 已声明延后补(中文 Edit 编码限制),后续补 schema 时一并补 status field。
- **状态**:✅ 已审(PASS)
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260619-14 多会话并发前端 UI 阶段3/4(generatingConvs:Set + 侧栏并行 + 独立窗口 per-conv·commit 7d54029) — ✅ 已审(PASS)
- **结论(2026-06-19·独立 grep/read 核验 commit 7d54029 当前形态,HEAD=7d54029 无后续代码漂移)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: `npx vue-tsc --noEmit` EXIT 0 独立复跑 ✅ / `grep -rn generatingConvId src/` 仅 2 处注释命中(stores/ai.ts:196/220),零代码读写残留 ✅ / 逐维度源码核验(非 diff 快照)。
**6 维度逐项核验**(源码当前形态为准):
| # | 维度 | 判定 |
|---|------|------|
| 1 | Set 响应式 | ✅ stores/ai.ts:70 `generatingConvs: new Set<string>()``reactive({})` 内;:204-206 `isGenerating``state.generatingConvs.has(convId)`。Vue 3 reactive 对 Set 的 has/add/delete 提供 collection proxy 跟踪,computed/template 内调用建立响应式依赖,Set 变更触发更新。AiChat(:283)/MessageList(:197/354)/DirAuthDialog(:51)/MaxRoundsCard(:43) 四处 computed 调 `store.isGenerating(activeConversationId)` 均建立依赖,UI 自动刷新 |
| 2 | 5 处迁移完整性 | ✅ useAiEvents.ts:362(Completed) `state.generatingConvs.delete(event.conversation_id \|\| '')` / :415(AiError) 同 delete / :465(路由非当前 conv) `state.generatingConvs.delete(convId \|\| '')` / :472(路由标记) `state.generatingConvs.add(convId)`。外加 useAiWindow.ts:65/196(detach delete + restore add)+ useAiStream.ts:30(clear)+ useAiConversations.ts:78(newConversation clear)。grep 零代码赋值残留 |
| 3 | AiError 精确清 | ✅ useAiEvents.ts:415 `state.generatingConvs.delete(event.conversation_id \|\| '')` — 仅清出错会话,不再 `= null` 全局清空(原单值语义会误清其他并发会话)。多会话并发下 A 报错不影响 B 生成态 |
| 4 | 侧栏并行指示 | ✅ ConversationSidebar.vue 三处独立脉冲点:`store.isGenerating(conv.id)` 在 :50(搜索结果)/:132(活跃分组,带 F-09 注释)/:177(归档分组)。各会话独立判定,多会话同时生成时各自项独立显示 `.ai-conv-gen-dot`。i18n key `aiChat.generating` 中英对称(zh:7 '生成中…' / en:7 'Generating…') |
| 5 | 独立窗口 per-conv | ✅ useAiWindow.ts:28-40 前缀常量 `ai-detached-`/`df-ai-gen-`/`df-ai-text-` + detachedLabel/genKey/textKey 生成器。:46 detachPanel 接受 convId → label per-conv;:54-57 写 per-conv 快照;:101-108 reattachPanel 关闭所有 `ai-detached-*` 前缀窗口(含旧 'ai-detached');:117-128 cleanupAllDetachedSnapshots 清所有 per-conv + 旧单 key |
| 6 | 向后兼容 | ✅ (a) 单会话场景:Set 0/1 元素行为等价(isGenerating has 单元素等单值 null/非null)。(b) :161/179/186/187/219/222 多处读路径 `localStorage.getItem(per-conv) \|\| localStorage.getItem('df-ai-gen')` 双兜底,旧版本写入的单 key 快照仍可读。(c) streaming 单值保留(stores/ai.ts:63)粗粒度"任一会话流式中",供 AiChat 等场景判断当前视图流式态 |
**审查防污染印证**:
- HEAD = 7d54029,`git diff 7d54029 --stat` 仅 docs/待审查.md +16(本次回填),12 源文件零后续漂移,源码形态可信(memory [[review-batching-worktree-transient]] 教训印证)。
- AiError 修复为本批最高价值项:原单值 `generatingConvId = null` 在多会话并发下会误清其他会话生成态(侧栏脉冲错灭/ghost),改 `delete(event.conversation_id)` 精确清是核心正确性修复。
- useAiStream.ts:30 `state.generatingConvs.clear()` 全清(非精确)是合理的兜底设计:整流超时无法确定具体 conv(无 event 携带 convId),保守全清避免幽灵,与 AiCompleted/AiError 精确 delete 互补,非缺陷。
- **⚪ WATCH-1**: useAiConversations.ts:78 `newConversation` `generatingConvs.clear()` 全清 — 多会话并发下新建会话会清掉其他后台生成态的侧栏脉冲指示(但不影响后端真实生成,仅前端视觉)。当前设计合理(新建会话视作用户关注转移),若后续报告后台会话脉冲丢失可改精确清。
- **⚪ WATCH-2**: streaming 单值(stores/ai.ts:63)在多会话并发下语义为"任一会话流式中",各组件用它判当前视图流式态时需配合 `isGenerating(activeConversationId)` 联合判定(4 组件已对齐)。若未来仅读 streaming 单值可能误判其他会话流式影响当前视图,当前 4 处 computed 均联合判定无隐患。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260619-15 F-260619-01 任务关联灵感(tasks.idea_id 1对1 单向·V20 迁移·commit 4a87c55) — ✅ 已审(PASS·⚪2)
- **结论(2026-06-19·独立 grep/read 核验 commit 4a87c55 + 复跑 cargo test df-storage 47 passed + vue-tsc EXIT 0)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **7 维度逐项核验**(独立 grep 源码当前形态,不信 commit message):
| # | 维度 | file:line | 判定 |
|---|------|-----------|------|
| 1 | V20 迁移正确性 | `migrations.rs:361-369`(migrate_v20)+ `:575-580`(V9_SQL 列)+ `:47`(steps 含 (20,migrate_v20))+ `:26`(注释从"预留给 F-260619-01 跳过"更新为"V20 = F-260619-01") | ✅ column_exists 探测跳过 ALTER + ALTER 兜底老库 + V9_SQL 新库直接 `idea_id TEXT REFERENCES ideas(id)` + 写版本 20;steps 数组 20 项 + 注释同步更新。schema_version INSERT 普通(非 IGNORE)与 v1-v19 全一致模式(grep 20 处全 `INSERT INTO schema_version`),run() 正常流程 current_version<20 只调一次无 PK 冲突 |
| 2 | TaskRecord idea_id | `models.rs:78-82`(`pub idea_id: Option<String>` + `#[serde(default)]`) + `task_repo.rs:34`(task_from_row `idea_id: row.get("idea_id")?`) | ✅ Option + serde default 兼容老前端 JSON + from_row 读 |
| 3 | task_repo SQL 完整 | `task_repo.rs:52-58`(insert 加列+占位 ?13)+ `:62-65`(update 加 idea_id=?12)+ 3 处显式 SELECT: `:83`(list_active)/`:184`(advance_status_atomic)/`:205`(list_deleted)全加 idea_id;`impl_repo!` 宏(mod.rs:81/98/119)用 `SELECT *` 自动带列 | ✅ grep `SELECT.*FROM tasks` 仅 3 处全含 idea_id 无遗漏 |
| 4 | 白名单 | `crud/settings.rs:145-147`(`"idea_id"` 加入 tasks allowed_columns + 注释"非状态机收口字段,合法可写") | ✅ update_task 工具经白名单自动支持改 idea_id |
| 5 | AI 工具 + IPC | `tool_registry.rs:633-634`(create_task schema 加 `("idea_id","string",false)`)+ `:650-651`(handler `args.get("idea_id").and_then(as_str).filter(!is_empty).map(String)` 空串/null 视为不关联);`commands/task.rs:24-27`(CreateTaskInput `pub idea_id: Option<String>`)+ `:85`(handler `input.idea_id.filter(!is_empty)`) | ✅ AI 工具 + IPC 双层一致空串语义 |
| 6 | 前端展示 | `types.ts:120-121`(TaskRecord `idea_id?:string`)+ `:133-135`(CreateTaskInput 同)+ `TaskDetail.vue:107-115`(router-link `:to="/ideas/${task.idea_id}"` 显 ideaTitle 非 id)+ `:222-227`(ideaTitle computed find idea by id 回退 id)+ `:402-409`(load Promise.all 加 ideaApi.list)+ `:443`(df-data-changed 监听 entity==='idea') | ✅ router-link 友好 + 回退 id + 数据变更联动 |
| 7 | 向后兼容 | `models.rs:80-81`(#[serde(default)] 老任务 None)+ 单向 1对1(grep 全 crates 无 `list_tasks_by_idea` 反向查询)+ 6 处 TaskRecord 构造点全补 idea_id(project_soft_delete.rs/ai_node.rs/task_advance_node.rs/df-mcp tools.rs create+update×2) | ✅ 无反向查询符合 1对1 起步约束 + 编译无遗漏 |
- **复跑验证**(防 check 假绿):
- `cargo test -p df-storage --lib`: **47 passed 0 failed**(含 `migrations::tests::v20_legacy_db_adds_idea_id_column` + `v20_column_exists_skips_alter` 两测)✅
- `vue-tsc --noEmit`: **EXIT 0**
- **复用印证**:tasks.idea_id (`migrations.rs:579`) 与 projects.idea_id (`:562`) 同 `TEXT REFERENCES ideas(id)` 模式,FK 引用语义一致。
- **⚪ WATCH-1**: `migrate_v20` schema_version 用普通 `INSERT`(非 `INSERT OR IGNORE`),重跑同版本会 PK 冲突报错——这是 v1-v19 全部迁移的统一模式(run() 流程 current_version<20 才调,正常无重跑),非本次引入。测试注释 `migrations.rs:1072-1074` 已诚实标注此语义。
- **⚪ WATCH-2**: V20 测试用 mock legacy schema 直接调 migrate_v20(非完整 run() 链),新库真路径依赖 V9_SQL 列存在性(已 grep 核验 :579)。同 V21 测试模式(:844-862),非本次短板。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260619-16 REFACTOR-260619-04 ToolCard.vue 拆分(1167→374 行·commit 74f3860) — ✅ 已审(PASS·⚪1)
- **结论(2026-06-19·独立 grep/read 核验 commit 74f3860)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: vue-tsc --noEmit EXIT 0 / git show 74f3860^:ToolCard.vue(拆分前 1167 行)与 ToolResultBody.vue 8 分支逐节点对比 / CSS class 集合 diff(原 1167 行 ToolCard 全部 .ai-tool-* 类 0 丢失)。
**6 维度逐项核验**:
| # | 维度 | 判定 |
|---|------|------|
| 1 | 逻辑等价 | ✅ isFailed 在 ToolCard(卡片级 border/dot/sub)与 ToolResultBody(结果级 banner/result-failed)各持 computed,源逻辑一字相同(status!=='completed'→false / run_command exit_code / raw 正则 /^(执行失败\|Error:\|Failed:)/m);status watch 拆分等价——ToolResultBody 仅在 status==='completed' 挂载(:74 v-if),其 immediate watch(:206-211)初始化 cmdOutputExpanded/httpBodyExpanded=isToolFailure(tc) 语义与原合并 watch(completed 分支)等价;ToolCard watch(:194-199)仅留 approving 复位,immediate 初始非 pending_approval 时 approving=false/timer=null 无副作用 |
| 2 | 8 渲染分支完整 | ✅ ToolResultBody v-if/v-else-if 链顺序与原逐字对齐:read_file(6)/list_directory(21)/search_files(44)/write_file(61)/patch_file(71)/run_command(85)/http_request(103)/generic(125)。**关键**:run_command 条件无 `&& parsed`(原 :152 同无,因用 argString 取 command 不依赖 parsed)与其他 7 分支带 parsed 形成与原一致的条件错位;各分支内部文案/SVG/class 绑定逐节点等价 |
| 3 | 接口契约不变 | ✅ ToolCard props(tc/isExpanded/isContentExpanded)+ emits(toggle/expand-content/approve)逐字保留;唯一调用方 ToolCardList.vue:33-43 绑定零改动;MessageList/AiChat 经 ToolCardList 间接消费不直接引 ToolCard(grep 印证仅 ToolCardList:51 import);expand-content 冒泡链 ToolResultBody→ToolCard(:77 `(id)=>emit('expand-content',id)`)→ToolCardList→父级等价 |
| 4 | composable 抽离 | ✅ parseDiffLines(useToolCardRender:32-45)纯函数复现原内联逻辑(空行过滤 + +/-/ctx 分类 + maxLines 截断追加「…」ctx 占位);useToolCardHeader 6 函数(projectNameById/taskNameById/displayArgValue/toolDisplayName/argsEntries/httpHost)+ 2 常量表(PROJECT_ID_TOOL_ARG/TASK_ID_TOOL_ARG)与原 :441-557 逐字等价(getTc() 闭包替代 props.tc,taskNameById 形参 t→tk 纯命名差异);6 个 import 全用 |
| 5 | ToolCard 瘦身 | ✅ 374 行(<400 达标)。保留:容器+头部(:10-22)+Running 骨架(:28-32)+审批区(参数/diff 预览/按钮 :35-63)+rejected(:66-69)+ConfirmDialog(:82-87)+超时 toast(:90-92)+状态机(approving timer + onApprove + HIGH_RISK 白名单 + watch 复位 + onBeforeUnmount) |
| 6 | 行为等价 | ✅ diffLines(ToolCard:230)用 parseDiffLines(props.tc.diff) 无 maxLines(write_file 审批 diff,与原 :400-410 等价);resultDiffLines(ToolResultBody:202)用 parseDiffLines(parsed.value?.diff, 120)(patch_file 结果 diff,与原 :418-433 MAX_DIFF_LINES=120 等价)——两路径数据源/截断互不干扰;scoped style 迁移:原 .ai-tool-* 全部类集合 diff **0 丢失** |
**主代核查·审查防污染印证**:
- 独立 git show 74f3860^ 取拆分前 ToolCard.vue 全文逐节点对比,不信 commit message 声明。
- CSS class 集合 diff:原 ToolCard.vue 全部 .ai-tool-* 类经 sort -u 后与 [新 ToolCard 新 ToolResultBody] 集合做 comm -23 → **空集**(0 丢失)。
- run_command 条件无 `&& parsed` 这处「条件错位」最易在拆分时被误改为统一加 parsed,独立核验 :85-86 与原 :151-152 逐字一致未动。
- vue-tsc EXIT 0 独立复跑通过(主代已核,本次复验一致)。
- **⚪ WATCH-1**: 7 个 diff 相关 class(approval-diff/approval-reason/diff-line/diff-line--add/del/ctx/diff-pre)在 ToolCard 与 ToolResultBody **重复定义**(因 scoped 样式不跨组件,两组件各自渲染 diff 预览需各持一份)。定义内容逐字等价(仅格式差异),非冲突非回归,但后续若调 diff 视觉需同步两处(可考虑抽全局非 scoped 样式 DRY,低优先)。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260619-17 REFACTOR-260619-06 ai_node.rs 拆分(836→423,抽 AiSelfReviewNode·commit c61a222) — ✅ 已审(PASS)
- **结论(2026-06-19·独立 grep/read 核验 commit c61a222 + cargo test 复跑)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: cargo test -p df-nodes 82 passed 0 failed 1 ignored(复跑通过,含 9 自审测试) / cargo check --workspace EXIT 0(零 warning) / 逐行 git show c61a222^ vs c61a222 对比 impl 体。
**6 维度逐项核验**:
| # | 维度 | 判定 |
|---|------|------|
| 1 | 逻辑等价 | ✅ AiSelfReviewNode struct + `impl Node`(execute/schema/node_type)+ build_review_prompt 逐字一致(逐行 diff 核验,无字符级改动);Node trait 签名 `df_workflow::node::Node` 不变 |
| 2 | AiSelfReviewNode 抽出完整 | ✅ ai_self_review_node.rs(447)含 struct:39 + new:45 + build_review_prompt:50 + impl Node:73-262,符合一节点一文件约定(human_node/script_node/task_advance_node) |
| 3 | 测试共置 | ✅ 9 自审测试全迁入 ai_self_review_node::tests(update_field_writes_output_json / parse_review_json_valid_passes_through / parse_review_json_invalid_falls_back_unknown / parse_review_json_strips_markdown_code_fence / truncate_for_summary_long_text / build_review_prompt_contains_inputs / gate_disabled_never_blocks_any_verdict / gate_enabled_blocks_only_on_fail / gate_unknown_verdict_does_not_block);ai_node.rs 测试模块止于 glm_live_complete:387,无自审测试残留 |
| 4 | state.rs 路径引用 | ✅ grep 全 src-tauri+crates 确认 `ai_node::AiSelfReviewNode` **0 残留**;state.rs:714 已改 `df_nodes::ai_self_review_node::AiSelfReviewNode`(注释同步 :708) |
| 5 | 注册键/node_type 不变 | ✅ state.rs:713 `register("ai_self_review", ...)` 字符串键未动(git diff 仅 :708 注释 + :714 路径两行变更);node_type() 返 "ai_self_review" 不变(ai_self_review_node.rs:260);task_workflow_templates.rs 本 commit 未改(字符串引用 :66/67/80/137 基于字符串非 Rust 路径,不受影响) |
| 6 | params/self_review 未重复抽 | ✅ ai_node_helpers.rs 本 commit 零改动(git show 空 diff);8 helper 全 pub(crate):resolve_and_parse/provider_from_params/resolve_provider/parse_params/REVIEW_SYSTEM_PROMPT/parse_review_json/truncate_for_summary/gate_should_block;ai_self_review_node.rs:17-22 仅 import 未重定义;build_review_prompt 留 ai_self_review_node(AiSelfReviewNode 专属,非共享 helper) |
**审查防污染印证**:
- 逐字搬运独立核验:对 ai_self_review_node.rs:75-262(execute impl 体)与 c61a222^:ai_node.rs:197-323 逐行比对,execute/schema/node_type 体字符级一致(含 review JSON 合并逻辑 / gate 闸门 return Err / schema params required 字段 / 注释文案),仅文件归属变更。
- imports 精简合理:ai_self_review_node.rs 删原 glob `#[allow(unused_imports)]`(改显式列 5 个 helper),ai_node.rs 主代码 import 收敛到 2 个(execute 唯二用)+ tests 模块独立 import 3 个(parse_params/resolve_provider/ResolvedProvider),无 unused warning(cargo check 零 warning 印证)。
- 行数账对:836(旧)≈ 423(ai_node.rs)+ 447(ai_self_review_node.rs)- 重复(imports/文档块重写),符合纯拆分预期。
- task_workflow_templates.rs 字符串引用经核基于 "ai_self_review" 字面量(dag.add_node:66/edge:80/test:137),非 Rust 路径,模块拆分对其零影响——印证「注册键字符串不变」论断正确。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260619-18 对话列表脉冲点样式修复(vertical-align/margin-left 冲突·commit 5c395f4) — ✅ 已审(PASS)
- **结论(2026-06-20·独立 grep/read 核验 commit 5c395f4 + vue-tsc EXIT 0)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
**3 维度独立核验(commit 5c395f4 diff 1 文件 +1/-2,vue-tsc EXIT 0)**:
| 维度 | file:line | 核验 | 判定 |
|------|-----------|------|------|
| 1 根因修复 | `AiChat.vue:813-821` | `.ai-conv-gen-dot` 当前无 `vertical-align`/`margin-left`;父 `.ai-conv-item-title:779-781` `display:inline-flex; align-items:center; gap:3px` 承载对齐与间距,无 4px 叠加(原 7px→现 3px 统一) | ✅ |
| 2 防挤出 | `AiChat.vue:782` + `:819` | `.ai-conv-item-title``min-width:0`(允许 inline-flex 收缩)+ 脉冲点 `flex-shrink:0`(不被 ellipsis 挤出),`text-overflow:ellipsis:777` 截标题不截脉冲点 | ✅ |
| 3 不破坏 | `ConversationSidebar.vue:50/132/177` | 3 处列表引用同 `.ai-conv-gen-dot``.ai-conv-item-title` 子元素,布局一致;其他属性保留(`display:inline-block/width:6px/height:6px/border-radius:50%/background/animation:820`),`@keyframes ai-conv-gen-pulse:822-825` 未动 | ✅ |
- **验证**: `git show 5c395f4 --numstat` = `1 2 src/components/AiChat.vue`(仅 1 文件 +1/-2);Read `AiChat.vue:772-825` 当前形态逐字核验;vue-tsc --noEmit EXIT 0。
- **⚪ WATCH-1**: `src/styles/global.css:234` 存在 `.ai-conv-gen-dot { outline:1px solid #0aa!important; background:#0aa!important; width:10px!important; height:10px!important }` TEMP DEBUG 参考线(强制 10px+青色),未提交非本 CR;调试用 `!important` 会覆盖生产 6px,上线前需移除(本次审查仅标注,不动手)。
- **待修项回流 todo**: **无** 🔴/🟡 项
---
### CR-260619-19 AiChat 样式同步到子组件 scoped + 复用抽取(God 拆分遗漏修复·commit 4f8f1cf) — 🟡 待审
- **范围**(4 文件 +596/-742):AiChat.vue(scoped 886→151,瘦身 83%)+ ConversationSidebar.vue(新建 <style scoped> +47 选择器)+ ChatInput.vue(新建 <style scoped> +40)+ components.css(+复用 .ai-btn-icon* 6)
- **根因**:God 拆分时样式没移子组件,留 AiChat `<style scoped>`。Vue scoped 不匹配子组件内部元素(子元素无父 data-v)→ 对话列表/输入框样式丢失 → 布局塌陷(主导航被覆盖点击无反应 + 样式乱)
- **维度**:
1. **纯样式移动**:选择器/属性/值一字不变(只改归属文件),无逻辑变更
2. **分类完整**:AiChat scoped CLEAN(0 子组件类 .ai-conv-/.ai-input-wrap/.ai-skill- 等)+ 子组件 scoped 匹配各自元素
3. **复用抽取**:.ai-btn-icon* → components.css 全局(TopBar+ConversationSidebar 共用)
4. **容器保留**:.ai-panel/.ai-chat-area/.ai-queue-*/.ai-toast* 留 AiChat scoped
5. **TopBar 重复块清理**:AiChat 漏删死代码(零视觉变更,TopBar scoped 接管)
6. **编译**:vite build ✓ + vue-tsc EXIT 0
- **验证方法**:独立 grep/read 核验 commit 4f8f1cf + vite build + AiChat scoped CLEAN(grep 0 子组件类)+ 子组件 scoped 匹配各自元素。**不信 agent 自报**。
- **关联**:God 拆分(ConversationSidebar/ChatInput/MessageList 抽出)遗漏 + CR-18 脉冲点(5c395f4)。
- **状态**:🟡 待审
---
## 已审归档
> 已审 CR 段迁独立文件: [待审查归档/2026-06.md](./07-项目管理/待审查归档/2026-06.md)