新增: 知识图谱Phase2事件流(父⑥) — project_events统一事件流+埋点+timeline

父⑥ Phase 2(对标设计 §2.4):
- V30 迁移:project_events 追加型审计表(id/project_id/event_type/entity_type/entity_id/from_state/to_state/context_json/source/conversation_id/created_at)+ 双索引(project,time / entity)
- ProjectEventRecord + ProjectEventRepo(insert 追加型/get_by_project/get_by_entity/list_recent)
- 事件埋点(best-effort,hook/after,失败 warn 不阻断主操作):
  - task.rs emit_event 辅助 + create_task/advance_task/move_task_queue 埋点
  - idea.rs promote_idea 埋点(idea_promoted,project_id=新项目)
  - project.rs create/delete 埋点
  - 注:idea_created 暂不埋点(idea 无 project_id,待 Inbox 项目落地,NOT NULL+FK 约束)
- get_project_timeline IPC(events 模块)+ lib.rs 注册 + AI 工具(基线 +N)
- source 语义:IPC 标 human,AI 工具路径标 ai

注:workflow 脚本中文变量名 const 致验证 agent 未跑,主控独立 cargo check EXIT 0 + grep 落地齐全核验。
This commit is contained in:
2026-06-27 00:23:17 +08:00
parent 744b68da5b
commit 5a893680b7
12 changed files with 853 additions and 20 deletions

View File

@@ -490,7 +490,7 @@ fn register_http_tools(registry: &mut AiToolRegistry) {
);
}
/// 数据层 AI 工具注册(24 个持 db 的 CRUD/状态机/工作流/知识图谱工具)——从 build_ai_tool_registry 抽出。
/// 数据层 AI 工具注册(25 个持 db 的 CRUD/状态机/工作流/知识图谱工具)——从 build_ai_tool_registry 抽出。
///
/// 工具闭包捕获 `db: &Arc<Database>` Arc 重建 Repo(列表/创建/更新/删除/状态推进/工作流/任务关联)。
/// 例外:run_workflow handler 防御返回 Err(CR-52),不持 db 不 clone,真正执行经
@@ -498,6 +498,8 @@ fn register_http_tools(registry: &mut AiToolRegistry) {
///
/// 知识图谱 Phase 1(2026-06-26):新增 register_task_graph_tools 子函数(6 工具:task_link CRUD
/// + 父子树 + 跨池移动 + content 更新),data 层 18→24。
/// 知识图谱 Phase 2(2026-06-26):register_task_graph_tools 增 get_project_timeline(项目事件流查询),
/// data 层 24→25。
///
/// SMELL-P0-2:抽自原 build_ai_tool_registry 1091 行单函数(数据+文件混合)。
/// 18 个 register 调用【原样移入】,零行为变更,仅机械搬运。
@@ -510,8 +512,10 @@ fn register_http_tools(registry: &mut AiToolRegistry) {
/// - register_trash_tools(1):list_trash
/// 知识图谱 Phase 1(2026-06-26)新增第 6 子函数:
/// - register_task_graph_tools(6):task_link CRUD + 父子树 + 跨池移动 + content 更新
/// AiToolRegistry 底层 HashMap(注册顺序无关),拆分后工具集合与原一致 + 新增 6,
/// 行为零变更(create_task 扩展参数 + 6 新工具,基线测试 test_build_ai_tool_registry_baseline_tool_count 守护)
/// 知识图谱 Phase 2(2026-06-26):register_task_graph_tools 增 get_project_timeline(项目事件流,只读),
/// 该子函数 6→7 工具,data 层 24→25
/// AiToolRegistry 底层 HashMap(注册顺序无关),拆分后工具集合与原一致 + 新增,
/// 行为零变更(create_task 扩展参数 + 7 新工具,基线测试 test_build_ai_tool_registry_baseline_tool_count 守护)。
fn register_data_tools(registry: &mut AiToolRegistry, db: &Arc<Database>) {
register_project_tools(registry, db);
register_task_tools(registry, db);
@@ -881,8 +885,8 @@ fn register_task_tools(registry: &mut AiToolRegistry, db: &Arc<Database>) {
}
/// 工作流类 AI 工具注册(1 个:run_workflow)——不持 db(handler 防御兜底,详见注释)。
/// 知识图谱 Phase 1 AI 工具注册(6 个:task_link CRUD + 父子树 + 跨池移动 + content 更新)
/// —— 持 db:Arc<Database>,对标设计 §五 Phase1 AI 工具表 + §2.1/§2.2。
/// 知识图谱 Phase 1+2 AI 工具注册(7 个:task_link CRUD + 父子树 + 跨池移动 + content 更新
/// + 项目事件流查询) —— 持 db:Arc<Database>,对标设计 §五 Phase1/Phase2 AI 工具表 + §2.1/§2.2/§2.4
///
/// 工具直接调 df_storage::crud Repo(与 register_task_tools 同源模式,经 db Arc 重建 Repo),
/// 复用 IPC 层(commands::task)的底层 Repo 方法,不重复实现业务校验:
@@ -1094,6 +1098,56 @@ fn register_task_graph_tools(registry: &mut AiToolRegistry, db: &Arc<Database>)
})
})},
);
// ── get_project_timeline (Low 只读,设计 §五 + §2.4) ──
// 查询项目事件流(project_events 统一事件流)。AI 精准检索基础:
// 回答"上周做了什么 / 这个任务为何 blocked / 决策何时做出"。
// 参数 project_id(必填) + event_type(可选过滤) + limit(默认 50,上限 200)。
// source 标 "ai"(AI 工具路径,区别于 IPC 层的 "human")。
registry.register(
"get_project_timeline", "查询项目事件流(AI 精准检索基础,回答\"上周做了什么/这个任务为何 blocked/决策何时做出\")。参数 project_id(必填,项目 ID)、event_type(可选,按事件类型精确过滤,如 task_created/task_advanced/idea_promoted/idea_evaluated/decision_made 等,空=不过滤)、limit(可选,返回条数上限,默认 50,内部钳制 ≤ 200)。返回 { items: 事件列表(时间倒序), total, project_id }。事件含 event_type/entity_type/entity_id/from_state/to_state/source/created_at",
df_ai::ai_tools::object_schema(vec![
("project_id", "string", true),
("event_type", "string", false),
("limit", "integer", false),
]),
RiskLevel::Low,
{ let db = db.clone(); Box::new(move |args: serde_json::Value| {
let db = db.clone();
Box::pin(async move {
let project_id = args["project_id"].as_str()
.ok_or_else(|| anyhow::anyhow!("缺少 project_id"))?
.trim()
.to_string();
if project_id.is_empty() {
anyhow::bail!("project_id 不能为空");
}
let event_filter = args.get("event_type")
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|s| s.to_string());
// limit 默认 50(对齐 commands::events::default_limit);负数/异常值兜底 50。
let limit = args.get("limit")
.and_then(|v| v.as_u64())
.map(|n| n as u32)
.unwrap_or(50);
let repo = df_storage::crud::ProjectEventRepo::new(&db);
let mut events = repo.get_by_project(&project_id, limit).await?;
// 应用层按 event_type 过滤(单用户事件量小,保持 Repo 方法精简)。
if let Some(et) = &event_filter {
events.retain(|e| e.event_type == *et);
}
let total = events.len();
Ok(serde_json::json!({
"items": events,
"total": total,
"project_id": project_id,
}))
})
})},
);
}
/// 抽自 register_data_tools(SMELL-P0-2 续拆),【原样移入】,零行为变更。
@@ -2772,11 +2826,11 @@ mod tests {
// 工具注册基线测试SMELL-P0-2 拆分防护)
//
// 防未来 register_data_tools / register_file_tools 拆分或重构时静默丢工具。
// build_ai_tool_registry 经两层 register_* 组装:data(24 持 db) + file(13 不持 db) + http(1) = 38
// build_ai_tool_registry 经两层 register_* 组装:data(25 持 db) + file(13 不持 db) + http(1) = 39
// 任一层漏移 register 调用,此测试立即红。工具名集合也断言,防 rename 致 LLM tool 突变。
// ============================================================
/// build_ai_tool_registry 应注册恰好 38 个工具(24 data + 13 file + 1 http),且工具名集合稳定。
/// build_ai_tool_registry 应注册恰好 39 个工具(25 data + 13 file + 1 http),且工具名集合稳定。
///
/// 用 in-memory SQLite(Database::open_in_memory 自跑迁移),构造零外部依赖的 db,
// 不实际执行任何 handler——仅断言注册阶段的定义完整性,故无需真实数据。
@@ -2789,24 +2843,26 @@ mod tests {
let allowed_dirs = Arc::new(RwLock::new(AllowedDirs::default_with_root()));
let registry = build_ai_tool_registry(&db, &allowed_dirs);
// 总量基线:38(24 data + 13 file + 1 http)。拆分前后必须一致。
// 总量基线:39(25 data + 13 file + 1 http)。拆分前后必须一致。
// F-260621: file 层 10→11(新增 grep 跨文件内容搜索工具)。
// L1 环境感知(设计 §2.1): file 层 11→12(新增 detect_environment 环境探测工具)。
// AST 代码智能(7c2e3b2): file 层 12→13(新增 read_symbol 符号解析,治 read_file 全文回灌 prompt 爆)。
// 知识图谱 Phase 1(2026-06-26): data 层 18→24(新增 register_task_graph_tools 6 工具:
// create_task_link/remove_task_link/list_task_links/get_task_tree/move_task_queue/update_content)。
// 知识图谱 Phase 2(2026-06-26): data 层 24→25(新增 get_project_timeline 项目事件流查询,
// register_task_graph_tools 6→7)。
assert_eq!(
registry.len(),
38,
"工具总数应为 38(24 data + 13 file + 1 http),实际 {}", registry.len()
39,
"工具总数应为 39(25 data + 13 file + 1 http),实际 {}", registry.len()
);
// 工具名集合基线:防 rename / 漏注册 / 误删除。
// data 层 24 个(持 db):CRUD/状态机/工作流/知识图谱任务关联
// data 层 25 个(持 db):CRUD/状态机/工作流/知识图谱任务关联 + 项目事件流
// file 层 13 个(不持 db):命令/读/列/写/改/元/追加/删/移/搜/grep/环境探测/符号解析
// http 层 1 个(不持 db):http_request
let mut expected: Vec<&str> = vec![
// ── data 层 (24) ──
// ── data 层 (25) ──
"list_projects", "list_tasks", "list_ideas",
"update_project", "create_project", "bind_directory",
"create_task", "update_task", "advance_task",
@@ -2816,6 +2872,8 @@ mod tests {
// 知识图谱 Phase 1 任务关联工具(register_task_graph_tools 6 个)
"create_task_link", "remove_task_link", "list_task_links",
"get_task_tree", "move_task_queue", "update_content",
// 知识图谱 Phase 2 项目事件流(register_task_graph_tools 7 个,本工具为第 7)
"get_project_timeline",
// ── file 层 (13) ──run_command 注册顺序已移至末位降低 LLM 偏好,
// 集合断言经 sort 后与顺序无关,仅守护工具名不漂移。grep 新增 F-260621;
// detect_environment 新增 L1 环境感知 设计 §2.1;read_symbol 新增 AST 代码智能)