From 4dc5219a3c5d7e1d5ca2c544e188e64886b0f542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Tue, 16 Jun 2026 03:12:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20CR-01=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E5=BE=85=E4=BF=AE=E9=A1=B9=E2=91=A0=E7=B1=BB7=E9=A1=B9(CSS?= =?UTF-8?q?=E8=A1=A5testing/blocked=E5=BE=BD=E7=AB=A0+CAS=E9=98=B2?= =?UTF-8?q?=E5=9B=9E=E6=94=B6=E7=AB=99=E6=8E=A8=E8=BF=9B+DRY+=E6=B3=A8?= =?UTF-8?q?=E9=87=8A+=E6=B5=8B=E8=AF=95=E6=94=B9=E5=90=8D+advancing?= =?UTF-8?q?=E6=8E=A5=E6=96=87=E6=A1=88)+=E6=96=B0=E5=A2=9E=E5=BE=85?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5.md=E5=AE=A1=E6=9F=A5=E9=98=9F=E5=88=97(?= =?UTF-8?q?=E8=81=8C=E8=B4=A3=E5=88=86=E7=A6=BB:=E4=B8=BB=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E5=86=99/=E5=AE=A1=E6=9F=A5agent=E8=AF=BB)+todo?= =?UTF-8?q?=E9=94=80=E8=B4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/df-nodes/src/task_advance_node.rs | 17 +++--- crates/df-storage/src/crud.rs | 10 +++- docs/todo.md | 21 +++++++- docs/待审查.md | 69 ++++++++++++++++++++++++ src-tauri/src/state.rs | 6 +++ src/views/TaskDetail.vue | 5 +- 6 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 docs/待审查.md diff --git a/crates/df-nodes/src/task_advance_node.rs b/crates/df-nodes/src/task_advance_node.rs index 34d469c..921c9a5 100644 --- a/crates/df-nodes/src/task_advance_node.rs +++ b/crates/df-nodes/src/task_advance_node.rs @@ -20,7 +20,7 @@ use df_storage::db::Database; use df_storage::models::TaskRecord; use df_workflow::node::{Node, NodeContext, NodeOutput, NodeResult, NodeSchema}; -use crate::task_state_machine::{can_transition, is_regression, is_valid_state}; +use crate::task_state_machine::{can_transition, is_regression, is_valid_state, ALL_STATES}; /// 推进任务到目标状态(核心逻辑,DAG 节点与 IPC 入口共用)。 /// @@ -45,8 +45,9 @@ pub async fn advance_task_atomic( // 1. target 合法性(7 态之一)。先于读库校验:即便任务不存在,也先拒绝非法状态值。 if !is_valid_state(target_status) { return Err(df_core::error::Error::Validation(format!( - "非法 target_status {:?},合法值: todo/in_progress/in_review/testing/done/blocked/cancelled", - target_status + "非法 target_status {:?},合法值: {}", + target_status, + ALL_STATES.join("/") ))); } @@ -295,15 +296,15 @@ mod tests { } #[tokio::test] - async fn concurrent_cas_change_detected() { - // 模拟 TOCTOU:推进前已被旁路改 status。CAS 的 expected 与库内不符 → None → 报错。 + async fn advance_succeeds_after_prior_status_change() { + // 前序旁路改 status 后,推进链基于「当前」status 判定合法转换并正常写入。 + // 注:这并非 CAS 并发失败场景(真 CAS 失败由 cas_returns_none_when_status_mismatch 覆盖), + // 而是验证读后改路径在 from=当前库内 status 时正常推进。 let repo = setup().await; repo.insert(rec("t1", "todo")).await.unwrap(); // 旁路把 status 改成 in_progress(模拟另一路并发推进) repo.update_field("t1", "status", "in_progress").await.unwrap(); - // 此时读出来是 in_progress,推进到 in_review 是合法的——这测的不是 CAS 失败, - // 而是验证「读后改」路径在 status 一致时正常。CAS 失败路径靠 advance_status_atomic - // 自身的 None 返回覆盖(下方 cas_returns_none_when_status_mismatch 单测)。 + // 读出来是 in_progress,推进到 in_review 合法 → 正常成功 let r = advance_task_atomic(&repo, "t1", "in_review").await.unwrap(); assert_eq!(r.status, "in_review"); } diff --git a/crates/df-storage/src/crud.rs b/crates/df-storage/src/crud.rs index 067a996..37dcd79 100644 --- a/crates/df-storage/src/crud.rs +++ b/crates/df-storage/src/crud.rs @@ -325,6 +325,10 @@ pub fn allowed_columns_for(table: &str) -> Option<&'static [&'static str]> { "tasks" => &[ // id/created_at 不列入:主键与创建时间不可通过通用 update_field 改写 // (防篡改主键/伪造创建时间/跨项目移动)。 + // review_rounds 不列入:它仅 advance_status_atomic 退回转换 + // (in_review→in_progress / testing→in_review)时原子 +1(收口:仅该专用路径可改, + // update_task/update_field 白名单均不含)。后人勿把 review_rounds 补进白名单, + // 否则破坏「review_rounds 唯一写入路径」收口、引入旁路写导致计数错乱。 "project_id", "title", "description", "status", "priority", "branch_name", "assignee", "workflow_def_id", "base_branch", "updated_at", // TODO(B-260616-16): project_id 跨表存在性校验待 commands/task.rs 层补。 @@ -846,12 +850,14 @@ impl TaskRepo { tokio::task::spawn_blocking(move || { let guard = conn.blocking_lock(); // CAS:WHERE id AND status=expected 锁定当前态;affected==0 即并发已改动。 + // deleted_at IS NULL:回收站任务(soft_delete 设了 deleted_at)CAS 必败→affected=0, + // 返回 None,杜绝回收站任务被推进(D-02 软删语义收口,一处关闭)。 let sql = if bump_rounds { "UPDATE tasks SET status = ?1, review_rounds = review_rounds + 1, updated_at = ?2 \ - WHERE id = ?3 AND status = ?4" + WHERE id = ?3 AND status = ?4 AND deleted_at IS NULL" } else { "UPDATE tasks SET status = ?1, updated_at = ?2 \ - WHERE id = ?3 AND status = ?4" + WHERE id = ?3 AND status = ?4 AND deleted_at IS NULL" }; let affected = guard .execute(sql, params![new_status, now, id, expected]) diff --git a/docs/todo.md b/docs/todo.md index ad080ea..4c57335 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -56,6 +56,12 @@ - [ ] B-260616-11 [P2] — **AI 消息 `-`/`+` 列表/diff 无视觉效果**。marked 实测两个出口:①裸 `-`/`+` 混用 → GFM 要求同列表标记一致,marked 拆成**两个独立 `
` **无 hljs 高亮**(ai-md.css 未集成 highlight.js),`-`/`+` 字面纯文本无红绿。根因层 marked 行为合规,缺的是 **diff 高亮集成 + 裸 diff 文本识别**。改动(待定):①集成 hljs 覆盖 ```diff(关联已知待办「aichat交互体验改进方案-2026-06-14」2.2 代码块高亮)②裸 `-`/`+` diff 文本预处理识别。— src/composables/useMarkdown.ts + src/styles/ai-md.css。**②子项已修(第㉖批 2026-06-16·主代理直修)**:useMarkdown.ts L40-41 import diff + L63 registerLanguage('diff', diff)(16→17 语言),```diff 代码块经 highlightCode getLanguage('diff') 命中 → hljs-addition/deletion token class → github-dark.css 红绿。**①子项仍②类待复现**:裸 -/+ 文本预处理识别(需确认 AI 实际输出格式)。vue-tsc exit 0。
- [x] ✅(第㉗批 2026-06-16·workflow) B-260616-19 [P2] — **知识库详情窄屏标题挤压成竖线(一行一字)**。三因子叠加(Knowledge.vue):①`.kn-layout` grid `grid-template-columns: 360px 1fr`(L567)——左列表死钉 360px、右详情 `1fr` 拿剩余,**无响应式断点**;窗口窄(如总宽 500px,减 padding 40+gap)详情列塌缩到 ~80px 甚至更小。②`.detail-title`(L625)`font-size:18px` + `word-break:break-word` —— 窄列里中文每字宽度 > 列宽,逐字换行 → 竖线化。③`.detail-actions`(L626)`flex-shrink:0` —— 编辑/发布/拒绝 3 按钮不缩,优先占位,把 `.detail-title-row`(`flex:1`) 挤到零头。`.kn-detail-panel`(L570-576) 无 `min-width` 兜底。改动(待定):①grid 列宽改 `minmax(240px,360px) 1fr` + `@media (max-width:760px)` 切单列(`grid-template-columns:1fr`,列表详情上下堆叠);②详情面板加 `min-width:0` 配合标题行 `min-width` 防塌缩;③可选标题字号响应式降级(18px→15px)。— src/views/Knowledge.vue。**低风险纯 CSS**,无逻辑/i18n/数据变动。
+### 💡 2026-06-16 新需求(已分析·待实施)
+
+> 功能需求,已走查定位链路 + 方案记录,待用户确认后实施。
+
+- [ ] F-260616-01 [P2] — **aichat Agentic 最大循环轮次支持配置(Settings)**。现状:`MAX_AGENT_ITERATIONS=10` 硬编码常量(`agentic.rs:33`),复杂 agentic 任务未收敛即被截断,用户无法调。**黄金参考:`llm_concurrency`**——项目唯一成熟的「前端数字配置→AppState 字段→热改 command→透传 loop 形参」范式,本需求 1:1 复刻。改动 8 处:①`state.rs` AppState 加 `agent_max_iterations: Arc`(默认 10) ②`agentic.rs` 常量改名 `DEFAULT_MAX_AGENT_ITERATIONS` + `run_agentic_loop` 增形参 `max_iterations` + 循环(L156)/超限消息(L321-332)用形参 + `try_continue_agent_loop`(L478) 补传 ③`commands.rs` `ai_chat_send`(L114) 补传 + 新 command `ai_set_agent_max_iterations`(clamp 1-50 store) ④`lib.rs` invoke_handler(L125 区)注册 ⑤`api/ai.ts` 新 `setAgentMaxIterations`(仿 L73 `setConcurrencyConfig`) ⑥`Settings.vue` reactive(L615 区)+UI 数字 input(min1 max50,紧挨并发控制 L268)+watch 持久化+`syncAgentMaxIterations`+onMounted 推后端 ⑦i18n 补 zh/en 键。**生效时机**:loop 入口 `load` 取快照传值,当前 loop 锁定边界,热改下次发消息生效(防边界飘移;与 `llm_concurrency` 传 Arc 实时反映的区别)。key `df-ai-agent-max-iterations`,范围 1-50 双 clamp。`agentic.rs:27-32` 注释已预留此扩展点。**低风险,复刻现成范式无新机制**。— 详案见本地 plan 文件 `quizzical-prancing-hennessy.md`。
+
### 🔧 任务模块全栈核对(2026-06-16 · Explore 代理并行取证)
> 来源:[任务模块问题分析-2026-06-16.md](./05-代码审查/任务模块问题分析-2026-06-16.md)。原清单 18 项核对:真 bug 7 项 + #8 路由漏注册去重(B-260616-09 已存在)、增强 5 项、假/部分假 3 项、去重 3 项。详见文档速览矩阵。
@@ -85,7 +91,20 @@
- [ ] F-260616-06 阶段2 工作流联动(task_id+回调+DAG模板)
- [ ] F-260616-07 阶段3 AI 执行闭环(advance_task/run_workflow 工具+AiNode+自审)+ **F-03收口三件**(advance_task注册AI工具+update_task描述/schema移status+df-storage tasks白名单移status,同步落地防AI工具行为不一致)
- [ ] F-260616-08 阶段4 Git 集成(kind+git闸门+worktree)
-- [ ] **CR-260616-01 代码审查 — 阶段1推进链(batch㉘)** 范围:df-nodes/{task_state_machine.rs,task_advance_node.rs,lib.rs,Cargo.toml}+crud.rs(advance_status_atomic)+models.rs+migrations.rs(V15)+task.rs(advance_task IPC)+lib.rs注册+TaskDetail.vue(ADVANCE_MAP)+api/task.ts(advance)+i18n zh/en taskDetail.ts+tool_registry.rs/tests project_soft_delete.rs/types.ts(补review_rounds字段)。内容:①状态机转换矩阵正确性(对照报告§8,退回/终态/非法跳过)②advance_task CAS原子写`WHERE status=expected`防TOCTOU ③advance_status_atomic是否唯一status/review_rounds写入路径(防旁路写)④review_rounds退回递增逻辑+V15迁移幂等(PRAGMA column_exists)⑤F-03停下决策正确性(AI update_task依赖status,收口延后阶段3三件)⑥前端ADVANCE_MAP与后端can_transition一致性(勿前端臆断合法性)⑦指派外补字段(tool_registry create_task/tests fixture/types.ts)最小性非超范围 ⑧TaskAdvanceNode Node trait实现正确性(阶段2 DAG预留,config读task_id/target_status)
+- [x] ✅ **CR-260616-01 代码审查完成** → 审查登记已迁 [待审查.md](./待审查.md)(职责分离:审查队列独立,不进 todo)。结论 🔴0 🟡6 ⚪4 质量优,8 维度全过。**待修项 CR-01-A~I 见下方推进区**。
+
+ > **审查发现待修项(2026-06-16 · CR-260616-01)**:
+ - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-A** [P1] TaskDetail.vue 补 `.status-testing`(橙)/`.status-blocked`(红 danger 边框)CSS;Tasks.vue 已有/Dashboard 不用此体系无需补
+ - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-B** [P1] crud.rs advance_status_atomic 两分支 CAS SQL 加 `AND deleted_at IS NULL`,回收站任务 CAS 必败返 None(D-02 软删收口)
+ - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-C** [P2] task_advance_node.rs:50 错误信息改 `ALL_STATES.join("/")` DRY(运行时文案不变)
+ - [ ] **CR-01-D** [P2] task_advance_node.rs:62 同态拒绝(from==to)/非法转换(can_transition false)错误区分(现合并 `InvalidState`,前端无法分辨)— **②类留**(需改错误枚举判断)
+ - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-E** [P2] src-tauri/src/state.rs:240 build_registry 补注释「TaskAdvanceNode 未注册(阶段2联动时加),勿删」
+ - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-F** [P2] crud.rs 白名单注释补 review_rounds 专用路径(仅 advance_status_atomic,防误补破坏收口)
+ - [ ] **CR-01-G** [P3] TaskDetail.vue:218 advance 失败错误走 i18n 分级(`e.toString()` 非空直显后端中文,en locale 也中文)— **②类留**(需 i18n 分级改造)
+ - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-H** [P3] task_advance_node.rs 测试改名 `concurrent_cas_change_detected` → `advance_succeeds_after_prior_status_change`(反映实际测正常推进)
+ - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-I** [P3] i18n `taskDetail.advancing` 接文案(推进按钮 advancing 时显示「推进中...」,冗余 key 转实引用)
+ >
+ > **修复核查(主代理独立·2026-06-16)**:7 项真实落地(Read+git diff+cargo check --workspace exit 0.83s+vue-tsc 0 核实)。**注意:验证 agent CR-01-E 证据张冠李戴**(报 df-workflow/state.rs,实改 src-tauri/state.rs),改动正确但证据幻觉——验证 agent 可信度打折,主代理核查兜底。D/G ②类留。
### ⏸️ 待用户授权/确认(巡检悬挂项·2026-06-16)
diff --git a/docs/待审查.md b/docs/待审查.md
new file mode 100644
index 0000000..0e10b77
--- /dev/null
+++ b/docs/待审查.md
@@ -0,0 +1,69 @@
+# 待审查队列
+
+> **职责分离**:
+> - **主会话**(编码合并后 / 巡检)→ 【**写入**】"当前队列"区,登记待审查项(范围 + 维度 + 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-260616-01-fixes 审查待修项修复批次 — 🟡 待审(低优先,主代理已核查)
+
+- **状态**: 🟡 待审(低优先 — 小修+主代理已独立核查双绿,独立审查 agent 可选核)
+- **来源**: CR-260616-01 审查待修项①类7项(cr01-fixes workflow `wu4r8ejcp`)
+- **改动文件**:
+ - `src/views/TaskDetail.vue`(.status-testing/.status-blocked CSS + advancing 接文案)
+ - `crates/df-storage/src/crud.rs`(CAS SQL 加 `AND deleted_at IS NULL` 两分支 + 白名单注释)
+ - `crates/df-nodes/src/task_advance_node.rs`(ALL_STATES.join DRY + 测试改名)
+ - `src-tauri/src/state.rs`(build_registry 补 TaskAdvanceNode 未注册注释)
+- **主代理核查**: cargo check --workspace exit 0(仅 pre-existing warning)+ vue-tsc exit 0 + git diff 边界合规(4 文件符合 agent 领地)
+- **审查关注点**:①CR-01-B CAS 补 deleted_at 语义正确性(回收站任务必败)②CR-01-A CSS 配色跨视图一致 ③CR-01-C DRY 无运行时文案变更
+- **未做**: CR-01-D(错误枚举区分)/CR-01-G(i18n 分级)②类留 todo
+
+---
+
+## 已审归档
+
+### CR-260616-01 任务推进链(batch28) — ✅ 已审
+
+- **commit**: `d2cb38c` 新增: 任务推进链(7态状态机+advance_task CAS原子写)+软删除+前后端7态对齐
+- **审查时间**: 2026-06-16(5 路并行多角度·防代入独立核验)
+- **决策依据**: D-260616-01(前端7态对齐) / D-02(任务软删除) / D-03(advance_task 走 df-nodes Node trait) / D-04(阶段1先行)
+- **总体结论**: 🔴0 🟡6 ⚪4 — **质量优**
+- **8 点 checklist 全过**:
+ - 状态机矩阵 ✅(7态15转换,前后端单一真相源)
+ - CAS 原子 ✅(`WHERE id AND status=expected` + affected=0 报错)
+ - 退回累加原子 ✅(同 UPDATE)
+ - V15 迁移幂等 ✅(版本门控 + column_exists 双层,八版零事故)
+ - IPC 真 thin ✅(D-03 守住,task.rs 仅 3 行转发)
+ - i18n 成对零遗漏 ✅(28 key)
+ - create_task 不暴露 rounds ✅
+ - 测试 25 个 ✅
+- **F-03 status 双写 = 已知延后**(阶段3 三件同落地,非新缺陷)
+- **范围外澄清**: df-ai/lib.rs(`mod retry`) + script_node.rs(`shell_type`) = 同 commit 无关连带改,零风险误报
+- **待修项回流 todo**(CR-01-A~I,见 [todo.md](./todo.md) "审查发现待修项"区):
+ - [P1] **CR-01-A** TaskDetail.vue scoped style 补 `.status-testing`/`.status-blocked`(D-01 前端7态遗漏,testing/blocked 徽章裸渲染丢样式;同查 Tasks.vue/Dashboard)
+ - [P1] **CR-01-B** crud.rs advance_status_atomic CAS SQL 加 `AND deleted_at IS NULL`(get_by_id 宏无软删过滤→回收站任务可被 advance,D-02 软删对齐遗漏,语义错乱)
+ - [P2] **CR-01-C** task_advance_node.rs:46 错误7态硬编码改 `ALL_STATES.join("/")`(DRY 单一事实源)
+ - [P2] **CR-01-D** task_advance_node.rs:62 同态拒绝(from==to)/非法转换区分(现合并 InvalidState,前端无法分辨)
+ - [P2] **CR-01-E** state.rs build_registry 注释补「task_advance 未注册(阶段2联动时加)」
+ - [P2] **CR-01-F** crud.rs:325 白名单注释补 review_rounds 专用路径
+ - [P3] **CR-01-G** TaskDetail.vue:218 advance 失败错误走 i18n 分级(en locale 也中文)
+ - [P3] **CR-01-H** task_advance_node.rs:297 测试改名(实际未测 CAS 失败)
+ - [P3] **CR-01-I** i18n `taskDetail.advancing` 冗余 key 处理
diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs
index 0cc3839..037967c 100644
--- a/src-tauri/src/state.rs
+++ b/src-tauri/src/state.rs
@@ -237,6 +237,12 @@ fn build_registry() -> NodeRegistry {
registry.register("ai", |_config| {
Box::new(df_nodes::ai_node::AiNode)
});
+ // 未在此注册的已实现节点:
+ // - TaskAdvanceNode(df_nodes::task_advance_node, impl Node trait 已就绪):
+ // 推进链 F-01~04 当前手动推进(IPC 直驱 advance_task),DAG 形态为阶段 2
+ // 工作流联动(run_workflow task_id + 完成回调 advance_task + DAG 模板)预留。
+ // 届时在此 register("task_advance", ...) 注入 Arc。
+ // 非遗漏,勿删 task_advance_node.rs。
registry
}
diff --git a/src/views/TaskDetail.vue b/src/views/TaskDetail.vue
index ee7d7f5..570b2c7 100644
--- a/src/views/TaskDetail.vue
+++ b/src/views/TaskDetail.vue
@@ -60,7 +60,7 @@
:class="act.variant"
:disabled="advancing"
@click="handleAdvance(act.target)"
- >{{ $t(act.label) }}
+ >{{ advancing ? $t('taskDetail.advancing') : $t(act.label) }}
@@ -340,6 +340,9 @@ onBeforeUnmount(() => {
.status-review { background: rgba(255,217,61,0.2); color: var(--df-warning); }
.status-done { background: rgba(100,255,218,0.15); color: var(--df-success); }
.status-abandoned { background: rgba(255,107,107,0.2); color: var(--df-danger); }
+/* D-260616-01:7 态新增 — testing(橙警告,近 review 区分)/blocked(红 danger,与 cancelled 区分用实心边框)。配色与 Tasks.vue 一致保持跨视图统一 */
+.status-testing { background: rgba(255,152,0,0.2); color: #ff9800; }
+.status-blocked { background: rgba(255,107,107,0.12); color: var(--df-danger); border: 0.5px solid var(--df-danger); }
.priority-badge {
font-size: 10px; font-weight: 500;