优化: miniapp渲染(图片/checkbox/代码高亮) + MCP规则共享下沉 + 文档重写 + 节点风格对齐
This commit is contained in:
+42
-29
@@ -110,13 +110,13 @@ pub fn all_tools() -> &'static Vec<&'static ToolSpec> {
|
||||
spec("bind_directory", "为项目绑定本地代码目录(会做路径冲突检测,Medium 风险+审计日志)", object_schema(json!({"id": str_field("项目 ID"), "path": str_field("本地目录绝对路径")}), &["id", "path"]), Medium, bind_directory),
|
||||
// ─── 任务 ───
|
||||
spec("list_tasks", "列出所有未删除任务(可按 project_id/status 过滤;分页 offset/limit,默认 limit=50 上限 100)", object_schema(json!({"project_id": opt_str_field("按项目过滤(可空)"), "status": opt_str_field("按状态过滤(todo/in_progress/in_review/testing/blocked/done/cancelled,可空)"), "offset": int_field("偏移量(可空,默认 0)"), "limit": int_field("返回上限(可空,默认 50,上限 100)")}), &[]), Low, list_tasks),
|
||||
spec("create_task", "创建任务(Medium 风险,默认允许+审计日志;可选 parent_id 父任务 ID,限 1 级嵌套,父任务自身不能是子任务)", object_schema(json!({"project_id": str_field("项目 ID"), "title": str_field("标题"), "description": str_field("描述"), "priority": int_field("优先级(可空,默认 0)"), "parent_id": opt_str_field("父任务 ID(可空)")}), &["project_id", "title"]), Medium, create_task),
|
||||
spec("create_task", "创建任务(Medium 风险,默认允许+审计日志;可选 idea_id 关联灵感、queue 管理池(默认 todo,新建仅 backlog/todo/decision)、parent_id 父任务 ID(限 1 级嵌套,父任务自身不能是子任务)、content_json 结构化需求规格(须合法 JSON))", object_schema(json!({"project_id": str_field("项目 ID"), "title": str_field("标题"), "description": str_field("描述"), "priority": int_field("优先级(可空,默认 2=medium,值域 0..=3)"), "idea_id": opt_str_field("关联灵感 ID(可空)"), "queue": opt_str_field("管理池(可空,默认 todo,新建仅 backlog/todo/decision)"), "parent_id": opt_str_field("父任务 ID(可空,限 1 级嵌套)"), "content_json": opt_str_field("结构化需求规格 JSON(可空,须合法 JSON)")}), &["project_id", "title"]), Medium, create_task),
|
||||
spec("update_task", "更新任务(部分更新:仅传需要改的字段,未传字段保留原值;状态须走 advance_task)", object_schema(json!({"id": str_field("任务 ID"), "project_id": opt_str_field("项目 ID(可空=保留原值)"), "title": opt_str_field("标题(可空=保留原值)"), "description": opt_str_field("描述(可空=保留原值)"), "expected_updated_at": int_field("乐观锁版本(可空):上次读取到的 updated_at 毫秒时间戳,不一致则拒绝写入")}), &["id"]), Medium, update_task),
|
||||
spec("advance_task", "推进任务状态(传目标 status,内部读当前态+状态机校验,Medium 风险+审计日志)", object_schema(json!({"id": str_field("任务 ID"), "to": str_field("目标 status(todo/in_progress/in_review/testing/blocked/done/cancelled)")}), &["id", "to"]), Medium, advance_task),
|
||||
spec("delete_task", "软删任务(进回收站)——High 风险,默认拒绝,请在 DevFlow 应用内执行", object_schema(json!({"id": str_field("任务 ID")}), &["id"]), High, delete_task),
|
||||
// ─── 灵感 ───
|
||||
spec("list_ideas", "列出所有想法/灵感(分页:offset/limit,默认 limit=50 上限 100)", object_schema(json!({"offset": int_field("偏移量(可空,默认 0)"), "limit": int_field("返回上限(可空,默认 50,上限 100)")}), &[]), Low, list_ideas),
|
||||
spec("create_idea", "创建想法(Medium 风险,默认允许+审计日志)", object_schema(json!({"title": str_field("标题"), "description": str_field("描述"), "priority": int_field("优先级(可空,默认 0)")}), &["title"]), Medium, create_idea),
|
||||
spec("create_idea", "创建想法(Medium 风险,默认允许+审计日志)", object_schema(json!({"title": str_field("标题"), "description": str_field("描述"), "priority": int_field("优先级(可空,默认 1)")}), &["title"]), Medium, create_idea),
|
||||
spec("update_idea", "更新想法(部分更新:仅传需要改的字段,未传字段保留原值)", object_schema(json!({"id": str_field("想法 ID"), "title": opt_str_field("标题(可空=保留原值)"), "description": opt_str_field("描述(可空=保留原值)"), "expected_updated_at": int_field("乐观锁版本(可空):上次读取到的 updated_at 毫秒时间戳,不一致则拒绝写入")}), &["id"]), Medium, update_idea),
|
||||
spec("delete_idea", "软删想法——High 风险,默认拒绝,请在 DevFlow 应用内执行", object_schema(json!({"id": str_field("想法 ID")}), &["id"]), High, delete_idea),
|
||||
spec("evaluate_idea", "对想法做启发式评估(只读:只返分数不写库,基于 description/title 计算 feasibility/impact/urgency/overall)", object_schema(json!({"id": str_field("想法 ID")}), &["id"]), Low, evaluate_idea),
|
||||
@@ -572,31 +572,45 @@ fn create_task(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> {
|
||||
Err(r) => return Box::pin(std::future::ready(r)),
|
||||
};
|
||||
let description = arg_str_or(&args, "description", "");
|
||||
let priority = arg_int_or(&args, "priority", 0);
|
||||
medium_audit("create_task", &format!("{project_id}/{title}"));
|
||||
Box::pin(async move {
|
||||
// parent_id 可选(arg_str_or 给 "" 哨兵,空串视为 None)。非空时校验 1 级嵌套铁律:
|
||||
// 父任务存在 + 父任务自身无 parent_id(防孙任务),违反返回明确错误(与 IPC create_task 同规则)。
|
||||
let parent_id_raw = arg_str_or(&args, "parent_id", "");
|
||||
let parent_id = if parent_id_raw.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
let pid = parent_id_raw.trim();
|
||||
let repo = TaskRepo::new(&db);
|
||||
match repo.get_by_id(pid).await {
|
||||
Ok(Some(parent)) => {
|
||||
if parent.parent_id.is_some() {
|
||||
return CallToolResult::error(format!(
|
||||
"父任务不能是子任务(1 级嵌套限制): {pid} 自身有 parent_id={:?}",
|
||||
parent.parent_id
|
||||
));
|
||||
}
|
||||
Some(pid.to_string())
|
||||
}
|
||||
Ok(None) => return CallToolResult::error(format!("父任务不存在: {pid}")),
|
||||
Err(e) => return err_str(e),
|
||||
}
|
||||
let repo = TaskRepo::new(&db);
|
||||
|
||||
// 共享校验(与 GUI/IPC 同源,一处修改全局生效):queue / parent_id / content_json / priority 值域
|
||||
let queue = match df_storage::crud::normalize_queue_for_create(
|
||||
args.get("queue").and_then(|v| v.as_str()),
|
||||
) {
|
||||
Ok(q) => q,
|
||||
Err(e) => return CallToolResult::error(e),
|
||||
};
|
||||
let parent_id = match df_storage::crud::validate_parent_id(
|
||||
&repo,
|
||||
args.get("parent_id").and_then(|v| v.as_str()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(pid) => pid,
|
||||
Err(e) => return CallToolResult::error(e),
|
||||
};
|
||||
let content_json = match df_storage::crud::validate_content_json(
|
||||
args.get("content_json").and_then(|v| v.as_str()),
|
||||
) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return CallToolResult::error(e),
|
||||
};
|
||||
let priority = match df_storage::crud::normalize_priority(
|
||||
args.get("priority").and_then(|v| v.as_i64()),
|
||||
) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return CallToolResult::error(e),
|
||||
};
|
||||
// 可选关联灵感(空串/缺省视为不关联,与 GUI create_task 同语义)
|
||||
let idea_id = args
|
||||
.get("idea_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(String::from);
|
||||
|
||||
let now = now_millis();
|
||||
let rec = TaskRecord {
|
||||
id: new_id(),
|
||||
@@ -611,15 +625,14 @@ fn create_task(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> {
|
||||
base_branch: None,
|
||||
review_rounds: 0,
|
||||
output_json: None,
|
||||
idea_id: None,
|
||||
idea_id,
|
||||
module_id: None,
|
||||
queue: "todo".to_string(),
|
||||
queue,
|
||||
parent_id,
|
||||
content_json: None,
|
||||
content_json,
|
||||
created_at: now.clone(),
|
||||
updated_at: now,
|
||||
};
|
||||
let repo = TaskRepo::new(&db);
|
||||
match repo.insert(rec).await {
|
||||
Ok(id) => {
|
||||
let created = repo.get_by_id(&id).await.ok().flatten();
|
||||
@@ -766,7 +779,7 @@ fn create_idea(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> {
|
||||
Err(r) => return Box::pin(std::future::ready(r)),
|
||||
};
|
||||
let description = arg_str_or(&args, "description", "");
|
||||
let priority = arg_int_or(&args, "priority", 0);
|
||||
let priority = arg_int_or(&args, "priority", 1);
|
||||
medium_audit("create_idea", &title);
|
||||
Box::pin(async move {
|
||||
let now = now_millis();
|
||||
|
||||
Reference in New Issue
Block a user