From ec7fdb0f0de5f217522c34fcda74f3e40bf2f0ff Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 27 Jul 2026 21:27:44 +0800 Subject: [PATCH] fix: harden dws contract edge cases --- internal/cli/schema_agent_metadata/doc.json | 138 +++++-- internal/cli/schema_agent_metadata/drive.json | 28 +- internal/cli/schema_agent_metadata/index.json | 2 +- internal/cli/schema_agent_metadata/todo.json | 53 ++- internal/cli/schema_agent_metadata_audit.json | 2 +- internal/cli/schema_catalog.json | 362 +++++++++++------- internal/cli/schema_hints/metadata/doc.json | 11 +- internal/cli/schema_hints/metadata/drive.json | 9 +- internal/cli/schema_hints/selection/doc.json | 7 +- .../cli/schema_hints/selection/drive.json | 4 +- internal/cli/schema_hints/selection/todo.json | 17 +- internal/helpers/common.go | 26 ++ internal/helpers/contract_defects_test.go | 206 ++++++++++ internal/helpers/doc.go | 88 ++++- internal/helpers/drive.go | 7 +- internal/helpers/oa.go | 2 +- internal/helpers/todo.go | 14 +- internal/shortcut/smart/remind.go | 12 +- .../shortcut/smart/remind_contract_test.go | 20 + skills/mono/references/intent-guide.md | 4 +- skills/mono/references/products/todo.md | 5 +- skills/multi/dingtalk-todo/SKILL.md | 2 +- skills/multi/dingtalk-todo/references/todo.md | 5 +- 23 files changed, 758 insertions(+), 266 deletions(-) create mode 100644 internal/helpers/contract_defects_test.go create mode 100644 internal/shortcut/smart/remind_contract_test.go diff --git a/internal/cli/schema_agent_metadata/doc.json b/internal/cli/schema_agent_metadata/doc.json index ec6cfd6e..b6ab7735 100644 --- a/internal/cli/schema_agent_metadata/doc.json +++ b/internal/cli/schema_agent_metadata/doc.json @@ -9377,7 +9377,7 @@ ] }, "doc info": { - "agent_summary": "获取文档元信息(标题/类型/创建者/权限等)", + "agent_summary": "获取文档元信息(标题/类型/创建者/权限等,并从钉盘补齐缺失的文件大小)", "agent_summary_source": "dws-agent-selection/doc", "availability": "available", "avoid_when": [ @@ -9386,21 +9386,21 @@ ], "confirmation": "not_required", "effect": "read", - "effect_source": "command-verb", + "effect_source": "agent-hint", "examples": [ "dws doc info --node \u003cDOC_ID\u003e --format json", "dws doc info --node \"https://alidocs.dingtalk.com/i/nodes/\u003cDOC_UUID\u003e\" --format json" ], "field_provenance": { "agent_summary": { - "value": "获取文档元信息(标题/类型/创建者/权限等)", + "value": "获取文档元信息(标题/类型/创建者/权限等,并从钉盘补齐缺失的文件大小)", "source": "internal/cli/schema_hints/selection/doc.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", "candidates": [ { - "value": "获取文档元信息(标题/类型/创建者/权限等)", + "value": "获取文档元信息(标题/类型/创建者/权限等,并从钉盘补齐缺失的文件大小)", "source": "internal/cli/schema_hints/selection/doc.json", "precedence": "reviewed_explicit", "selected": true, @@ -9413,14 +9413,14 @@ "source": "internal/cli/schema_hints/metadata/doc.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "candidates": [ { "value": "available", "source": "internal/cli/schema_hints/metadata/doc.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields." }, { "value": "available", @@ -9454,29 +9454,45 @@ }, "confirmation": { "value": "not_required", - "source": "risk-default", - "precedence": "inference_or_default", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", "resolution": "highest_precedence", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields." + }, { "value": "not_required", "source": "risk-default", "precedence": "inference_or_default", - "selected": true + "selected": false } ] }, "effect": { "value": "read", - "source": "command-verb", - "precedence": "inference_or_default", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", "resolution": "highest_precedence", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields." + }, { "value": "read", "source": "command-verb", "precedence": "inference_or_default", - "selected": true + "selected": false } ] }, @@ -9504,31 +9520,39 @@ }, "idempotency": { "value": "idempotent", - "source": "effect-default", - "precedence": "inference_or_default", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", "resolution": "highest_precedence", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields." + }, { "value": "idempotent", "source": "effect-default", "precedence": "inference_or_default", - "selected": true + "selected": false } ] }, "interface_mode": { - "value": "mcp", + "value": "composite", "source": "internal/cli/schema_hints/metadata/doc.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "candidates": [ { - "value": "mcp", + "value": "composite", "source": "internal/cli/schema_hints/metadata/doc.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields." }, { "value": "mcp", @@ -9538,17 +9562,43 @@ } ] }, - "interface_ref": { - "value": "doc.get_document_info", - "source": "internal/cli/schema_hints/imported/wukong.json", - "precedence": "imported", + "interface_reason": { + "value": "The CLI uses doc/get_document_info as the authoritative response and best-effort enriches a missing fileSize from drive/get_file_info, so no single MCP interface represents the delivered result.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", "resolution": "highest_precedence", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "candidates": [ + { + "value": "The CLI uses doc/get_document_info as the authoritative response and best-effort enriches a missing fileSize from drive/get_file_info, so no single MCP interface represents the delivered result.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields." + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ], + "overridden_candidates": [ { "value": "doc.get_document_info", "source": "internal/cli/schema_hints/imported/wukong.json", "precedence": "imported", - "selected": true + "selected": false }, { "value": "doc.get_document_info", @@ -9563,14 +9613,14 @@ "source": "internal/cli/schema_hints/metadata/doc.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "candidates": [ { "value": true, "source": "internal/cli/schema_hints/metadata/doc.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed" + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields." }, { "value": true, @@ -9583,22 +9633,31 @@ }, "risk": { "value": "low", - "source": "effect-default", - "precedence": "inference_or_default", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", "resolution": "highest_precedence", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/doc.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields." + }, { "value": "low", "source": "effect-default", "precedence": "inference_or_default", - "selected": true + "selected": false } ] }, "use_when": { "value": [ "用户要查看文档/节点元信息(标题、类型、创建者、权限)时", - "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" + "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时", + "文档接口本身缺少 fileSize,但用户需要读取上传文件大小时" ], "source": "internal/cli/schema_hints/selection/doc.json", "precedence": "reviewed_explicit", @@ -9608,7 +9667,8 @@ { "value": [ "用户要查看文档/节点元信息(标题、类型、创建者、权限)时", - "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" + "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时", + "文档接口本身缺少 fileSize,但用户需要读取上传文件大小时" ], "source": "internal/cli/schema_hints/selection/doc.json", "precedence": "reviewed_explicit", @@ -9619,11 +9679,8 @@ } }, "idempotency": "idempotent", - "interface_mode": "mcp", - "interface_ref": { - "product_id": "doc", - "rpc_name": "get_document_info" - }, + "interface_mode": "composite", + "interface_reason": "The CLI uses doc/get_document_info as the authoritative response and best-effort enriches a missing fileSize from drive/get_file_info, so no single MCP interface represents the delivered result.", "reviewed": true, "risk": "low", "source_refs": [ @@ -9645,7 +9702,8 @@ ], "use_when": [ "用户要查看文档/节点元信息(标题、类型、创建者、权限)时", - "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" + "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时", + "文档接口本身缺少 fileSize,但用户需要读取上传文件大小时" ] }, "doc list": { @@ -12274,7 +12332,7 @@ }, "use_when": { "value": [ - "用户要改文档/文件在列表与链接中展示的名称时" + "用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端" ], "source": "internal/cli/schema_hints/selection/doc.json", "precedence": "reviewed_explicit", @@ -12283,7 +12341,7 @@ "candidates": [ { "value": [ - "用户要改文档/文件在列表与链接中展示的名称时" + "用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端" ], "source": "internal/cli/schema_hints/selection/doc.json", "precedence": "reviewed_explicit", @@ -12315,7 +12373,7 @@ "structured-hint:internal/cli/schema_hints/selection-review.json#doc.rename_document" ], "use_when": [ - "用户要改文档/文件在列表与链接中展示的名称时" + "用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端" ] }, "doc search": { diff --git a/internal/cli/schema_agent_metadata/drive.json b/internal/cli/schema_agent_metadata/drive.json index aa0edebb..aa7b5111 100644 --- a/internal/cli/schema_agent_metadata/drive.json +++ b/internal/cli/schema_agent_metadata/drive.json @@ -7083,7 +7083,7 @@ ] }, "drive rename": { - "agent_summary": "修改文档空间中文档或文件的名称", + "agent_summary": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名", "agent_summary_source": "dws-agent-selection/drive", "availability": "available", "avoid_when": [ @@ -7098,14 +7098,14 @@ ], "field_provenance": { "agent_summary": { - "value": "修改文档空间中文档或文件的名称", + "value": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名", "source": "internal/cli/schema_hints/selection/drive.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", "candidates": [ { - "value": "修改文档空间中文档或文件的名称", + "value": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名", "source": "internal/cli/schema_hints/selection/drive.json", "precedence": "reviewed_explicit", "selected": true, @@ -7118,14 +7118,14 @@ "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "candidates": [ { "value": "available", "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension." }, { "value": "available", @@ -7224,14 +7224,14 @@ "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "candidates": [ { "value": "mcp", "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension." }, { "value": "mcp", @@ -7246,14 +7246,14 @@ "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "candidates": [ { "value": "doc.rename_document", "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension." }, { "value": "doc.rename_document", @@ -7268,14 +7268,14 @@ "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "candidates": [ { "value": true, "source": "internal/cli/schema_hints/metadata/drive.json", "precedence": "reviewed_explicit", "selected": true, - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed" + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension." }, { "value": true, @@ -7302,7 +7302,7 @@ }, "use_when": { "value": [ - "用户要重命名钉盘/文档空间中的文件或文档显示名称时" + "用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层" ], "source": "internal/cli/schema_hints/selection/drive.json", "precedence": "reviewed_explicit", @@ -7311,7 +7311,7 @@ "candidates": [ { "value": [ - "用户要重命名钉盘/文档空间中的文件或文档显示名称时" + "用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层" ], "source": "internal/cli/schema_hints/selection/drive.json", "precedence": "reviewed_explicit", @@ -7342,7 +7342,7 @@ "structured-hint:internal/cli/schema_hints/selection-review.json#drive.rename_document" ], "use_when": [ - "用户要重命名钉盘/文档空间中的文件或文档显示名称时" + "用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层" ] }, "drive search": { diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index 6195ecce..29ef0e70 100644 --- a/internal/cli/schema_agent_metadata/index.json +++ b/internal/cli/schema_agent_metadata/index.json @@ -1,6 +1,6 @@ { "version": 1, - "source_hash": "sha256:557f4019cc83df15cbfe6d1611774331b0bdde87c0b5748c4d37d778b35abade", + "source_hash": "sha256:920c33b1853777cdb21a14d6e44ba75d3e0497f3470b3f04f10fa0a3f8715cb5", "surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed", "coverage": { "surface_products": 26, diff --git a/internal/cli/schema_agent_metadata/todo.json b/internal/cli/schema_agent_metadata/todo.json index af17dc32..604c46b1 100644 --- a/internal/cli/schema_agent_metadata/todo.json +++ b/internal/cli/schema_agent_metadata/todo.json @@ -5829,12 +5829,13 @@ ] }, "todo task add-reminder": { - "agent_summary": "添加待办提醒", + "agent_summary": "写入一条待办提醒规则(上游不支持规则读回)", "agent_summary_source": "dws-agent-selection/todo", "availability": "available", "avoid_when": [ "要重置/清除提醒规则时改用 dws todo task reset-reminder", - "提醒时间与目标待办未确认时不要添加" + "提醒时间与目标待办未确认时不要添加", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "confirmation": "not_required", "effect": "write", @@ -5845,14 +5846,14 @@ ], "field_provenance": { "agent_summary": { - "value": "添加待办提醒", + "value": "写入一条待办提醒规则(上游不支持规则读回)", "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "结合本机 dws schema 实时描述、Skill 与 Cobra 手写选型;pinned schema_mcp_metadata 仅对照。", "candidates": [ { - "value": "添加待办提醒", + "value": "写入一条待办提醒规则(上游不支持规则读回)", "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", "selected": true, @@ -5885,7 +5886,8 @@ "avoid_when": { "value": [ "要重置/清除提醒规则时改用 dws todo task reset-reminder", - "提醒时间与目标待办未确认时不要添加" + "提醒时间与目标待办未确认时不要添加", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -5895,7 +5897,8 @@ { "value": [ "要重置/清除提醒规则时改用 dws todo task reset-reminder", - "提醒时间与目标待办未确认时不要添加" + "提醒时间与目标待办未确认时不要添加", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -6043,7 +6046,7 @@ }, "use_when": { "value": [ - "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间)" + "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间);成功响应仅作为写入回执" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -6052,7 +6055,7 @@ "candidates": [ { "value": [ - "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间)" + "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间);成功响应仅作为写入回执" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -6078,11 +6081,12 @@ "internal/cli/schema_hints/selection/todo.json", "internal/cli/schema_mcp_metadata.json#tools.todo.add_todo_reminder", "live-dws-schema:todo.add_todo_reminder", + "skills/mono/references/intent-guide.md", "skills/mono/references/products/todo.md", "structured-hint:internal/cli/schema_hints/selection-review.json#todo.add_todo_reminder" ], "use_when": [ - "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间)" + "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间);成功响应仅作为写入回执" ] }, "todo task create": { @@ -6343,6 +6347,7 @@ "internal/cli/schema_hints/selection/todo.json", "internal/cli/schema_mcp_metadata.json#tools.todo.create_personal_todo", "live-dws-schema:todo.create_personal_todo", + "skills/mono/references/intent-guide.md", "skills/mono/references/products/todo.md", "structured-hint:internal/cli/schema_hints/imported/wukong.json#todo.create_personal_todo", "structured-hint:internal/cli/schema_hints/selection-review.json#todo.create_personal_todo" @@ -7183,7 +7188,8 @@ "availability": "available", "avoid_when": [ "需要按条件查多个待办时改用 dws todo task list", - "需要修改待办时改用 update/done/delete 等写命令" + "需要修改待办时改用 update/done/delete 等写命令", + "需要读取或验证提醒规则时不要使用;当前详情接口不返回 reminderRules" ], "confirmation": "not_required", "effect": "read", @@ -7234,7 +7240,8 @@ "avoid_when": { "value": [ "需要按条件查多个待办时改用 dws todo task list", - "需要修改待办时改用 update/done/delete 等写命令" + "需要修改待办时改用 update/done/delete 等写命令", + "需要读取或验证提醒规则时不要使用;当前详情接口不返回 reminderRules" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -7244,7 +7251,8 @@ { "value": [ "需要按条件查多个待办时改用 dws todo task list", - "需要修改待办时改用 update/done/delete 等写命令" + "需要修改待办时改用 update/done/delete 等写命令", + "需要读取或验证提醒规则时不要使用;当前详情接口不返回 reminderRules" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -8559,12 +8567,13 @@ ] }, "todo task reset-reminder": { - "agent_summary": "重置待办提醒", + "agent_summary": "整体替换或清除待办提醒规则(上游不支持规则读回)", "agent_summary_source": "dws-agent-selection/todo", "availability": "available", "avoid_when": [ "只需追加一条提醒时改用 dws todo task add-reminder", - "新规则与目标待办未确认时不要重置" + "新规则与目标待办未确认时不要重置", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "confirmation": "not_required", "effect": "write", @@ -8575,14 +8584,14 @@ ], "field_provenance": { "agent_summary": { - "value": "重置待办提醒", + "value": "整体替换或清除待办提醒规则(上游不支持规则读回)", "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "结合本机 dws schema 实时描述、Skill 与 Cobra 手写选型;pinned schema_mcp_metadata 仅对照。", "candidates": [ { - "value": "重置待办提醒", + "value": "整体替换或清除待办提醒规则(上游不支持规则读回)", "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", "selected": true, @@ -8615,7 +8624,8 @@ "avoid_when": { "value": [ "只需追加一条提醒时改用 dws todo task add-reminder", - "新规则与目标待办未确认时不要重置" + "新规则与目标待办未确认时不要重置", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -8625,7 +8635,8 @@ { "value": [ "只需追加一条提醒时改用 dws todo task add-reminder", - "新规则与目标待办未确认时不要重置" + "新规则与目标待办未确认时不要重置", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -8773,7 +8784,7 @@ }, "use_when": { "value": [ - "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除)" + "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -8782,7 +8793,7 @@ "candidates": [ { "value": [ - "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除)" + "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执" ], "source": "internal/cli/schema_hints/selection/todo.json", "precedence": "reviewed_explicit", @@ -8812,7 +8823,7 @@ "structured-hint:internal/cli/schema_hints/selection-review.json#todo.reset_todo_reminder" ], "use_when": [ - "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除)" + "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执" ] }, "todo task update": { diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index ad43c166..24a9d094 100644 --- a/internal/cli/schema_agent_metadata_audit.json +++ b/internal/cli/schema_agent_metadata_audit.json @@ -1,6 +1,6 @@ { "version": 1, - "source_hash": "sha256:557f4019cc83df15cbfe6d1611774331b0bdde87c0b5748c4d37d778b35abade", + "source_hash": "sha256:920c33b1853777cdb21a14d6e44ba75d3e0497f3470b3f04f10fa0a3f8715cb5", "surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed", "source_files": 160, "hint_files": 54, diff --git a/internal/cli/schema_catalog.json b/internal/cli/schema_catalog.json index c5928cb7..71030934 100644 --- a/internal/cli/schema_catalog.json +++ b/internal/cli/schema_catalog.json @@ -1,12 +1,12 @@ { "version": 1, - "source_hash": "sha256:56946c5c8d966b0f0803e575dd2f86e2733c2fc6697bdc6e799aae23a363981c", + "source_hash": "sha256:47cc0ab0b5a981af7d3ef4e87674cc4c06c8281bed85abff3d894754816a3d34", "surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed", "catalog": { "agent_metadata": { "products_with_metadata": 26, "source": "embedded-skill-metadata", - "source_hash": "sha256:557f4019cc83df15cbfe6d1611774331b0bdde87c0b5748c4d37d778b35abade", + "source_hash": "sha256:920c33b1853777cdb21a14d6e44ba75d3e0497f3470b3f04f10fa0a3f8715cb5", "surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed", "surface_products": 26, "surface_tools": 813, @@ -14656,7 +14656,7 @@ }, { "agent_metadata_source": "embedded-skill-metadata", - "agent_summary": "获取文档元信息(标题/类型/创建者/权限等)", + "agent_summary": "获取文档元信息(标题/类型/创建者/权限等,并从钉盘补齐缺失的文件大小)", "agent_summary_source": "dws-agent-selection/doc", "availability": "available", "avoid_when": [ @@ -14667,14 +14667,11 @@ "cli_name": "info", "cli_path": "doc info", "confirmation": "not_required", - "description": "获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。", + "description": "获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。\n文档接口未返回 fileSize 时,会从钉盘元数据补齐文件大小。", "effect": "read", "idempotency": "idempotent", - "interface_mode": "mcp", - "interface_ref": { - "product_id": "doc", - "rpc_name": "get_document_info" - }, + "interface_mode": "composite", + "interface_reason": "The CLI uses doc/get_document_info as the authoritative response and best-effort enriches a missing fileSize from drive/get_file_info, so no single MCP interface represents the delivered result.", "metadata_source": "embedded-mcp-metadata", "name": "get_document_info", "primary_cli_path": "doc info", @@ -14683,7 +14680,8 @@ "title": "获取文档元信息", "use_when": [ "用户要查看文档/节点元信息(标题、类型、创建者、权限)时", - "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" + "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时", + "文档接口本身缺少 fileSize,但用户需要读取上传文件大小时" ] }, { @@ -15028,7 +15026,7 @@ "risk": "medium", "title": "重命名文档/文件", "use_when": [ - "用户要改文档/文件在列表与链接中展示的名称时" + "用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端" ] }, { @@ -16612,7 +16610,7 @@ }, { "agent_metadata_source": "embedded-skill-metadata", - "agent_summary": "修改文档空间中文档或文件的名称", + "agent_summary": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名", "agent_summary_source": "dws-agent-selection/drive", "availability": "available", "avoid_when": [ @@ -16623,7 +16621,7 @@ "cli_name": "rename", "cli_path": "drive rename", "confirmation": "not_required", - "description": "修改文档空间中文档或文件的名称。\n\n权限要求: 对文档有\"编辑\"权限。", + "description": "修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名;\n如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。\n\n权限要求: 对文档有\"编辑\"权限。", "effect": "write", "idempotency": "unknown", "interface_mode": "mcp", @@ -16638,7 +16636,7 @@ "risk": "medium", "title": "重命名文件/文档", "use_when": [ - "用户要重命名钉盘/文档空间中的文件或文档显示名称时" + "用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层" ] }, { @@ -24334,18 +24332,19 @@ }, { "agent_metadata_source": "embedded-skill-metadata", - "agent_summary": "添加待办提醒", + "agent_summary": "写入一条待办提醒规则(上游不支持规则读回)", "agent_summary_source": "dws-agent-selection/todo", "availability": "available", "avoid_when": [ "要重置/清除提醒规则时改用 dws todo task reset-reminder", - "提醒时间与目标待办未确认时不要添加" + "提醒时间与目标待办未确认时不要添加", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "canonical_path": "todo.add_todo_reminder", "cli_name": "add-reminder", "cli_path": "todo task add-reminder", "confirmation": "not_required", - "description": "添加待办提醒", + "description": "为已有待办写入一条提醒规则。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验该规则。", "effect": "write", "group": "task", "idempotency": "unknown", @@ -24361,7 +24360,7 @@ "risk": "medium", "title": "添加待办提醒", "use_when": [ - "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间)" + "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间);成功响应仅作为写入回执" ] }, { @@ -24553,13 +24552,14 @@ "availability": "available", "avoid_when": [ "需要按条件查多个待办时改用 dws todo task list", - "需要修改待办时改用 update/done/delete 等写命令" + "需要修改待办时改用 update/done/delete 等写命令", + "需要读取或验证提醒规则时不要使用;当前详情接口不返回 reminderRules" ], "canonical_path": "todo.get_todo_detail", "cli_name": "get", "cli_path": "todo task get", "confirmation": "not_required", - "description": "待办详情", + "description": "查询单条待办的详情。\n\n当前上游详情接口不返回 reminderRules;本命令不能读取或验证 add-reminder /\nreset-reminder 写入的提醒规则。提醒写命令的成功响应只能作为写入回执。", "effect": "read", "group": "task", "idempotency": "idempotent", @@ -24762,18 +24762,19 @@ }, { "agent_metadata_source": "embedded-skill-metadata", - "agent_summary": "重置待办提醒", + "agent_summary": "整体替换或清除待办提醒规则(上游不支持规则读回)", "agent_summary_source": "dws-agent-selection/todo", "availability": "available", "avoid_when": [ "只需追加一条提醒时改用 dws todo task add-reminder", - "新规则与目标待办未确认时不要重置" + "新规则与目标待办未确认时不要重置", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "canonical_path": "todo.reset_todo_reminder", "cli_name": "reset-reminder", "cli_path": "todo task reset-reminder", "confirmation": "not_required", - "description": "重置待办提醒", + "description": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。", "effect": "write", "group": "task", "idempotency": "unknown", @@ -24789,7 +24790,7 @@ "risk": "medium", "title": "重置待办提醒", "use_when": [ - "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除)" + "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执" ] }, { @@ -25038,7 +25039,7 @@ "cli_name": "+remind", "cli_path": "todo +remind", "confirmation": "user_required", - "description": "当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。", + "description": "当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。", "effect": "write", "idempotency": "unknown", "interface_mode": "composite", @@ -25047,7 +25048,7 @@ "primary_cli_path": "todo +remind", "reviewed": true, "risk": "medium", - "title": "给自己创建一条带截止/提醒时间的待办", + "title": "给自己创建一条带可选截止时间的待办", "use_when": [ "当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。" ] @@ -344210,7 +344211,7 @@ "structured-hint:internal/cli/schema_hints/products/doc.json", "structured-hint:internal/cli/schema_hints/selection-review.json#doc.get_document_info" ], - "agent_summary": "获取文档元信息(标题/类型/创建者/权限等)", + "agent_summary": "获取文档元信息(标题/类型/创建者/权限等,并从钉盘补齐缺失的文件大小)", "agent_summary_source": "dws-agent-selection/doc", "availability": "available", "avoid_when": [ @@ -344221,10 +344222,10 @@ "cli_name": "info", "cli_path": "doc info", "confirmation": "not_required", - "description": "获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。", + "description": "获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。\n文档接口未返回 fileSize 时,会从钉盘元数据补齐文件大小。", "display": "钉钉文档管理", "effect": "read", - "effect_source": "command-verb", + "effect_source": "agent-hint", "examples": [ "dws doc info --node \u003cDOC_ID\u003e --format json", "dws doc info --node \"https://alidocs.dingtalk.com/i/nodes/\u003cDOC_UUID\u003e\" --format json" @@ -344237,20 +344238,20 @@ "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", "selected": true, "source": "internal/cli/schema_hints/selection/doc.json", - "value": "获取文档元信息(标题/类型/创建者/权限等)" + "value": "获取文档元信息(标题/类型/创建者/权限等,并从钉盘补齐缺失的文件大小)" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", "source": "internal/cli/schema_hints/selection/doc.json", - "value": "获取文档元信息(标题/类型/创建者/权限等)" + "value": "获取文档元信息(标题/类型/创建者/权限等,并从钉盘补齐缺失的文件大小)" }, "availability": { "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "selected": true, "source": "internal/cli/schema_hints/metadata/doc.json", "value": "available" @@ -344264,7 +344265,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "source": "internal/cli/schema_hints/metadata/doc.json", "value": "available" }, @@ -344309,15 +344310,23 @@ "confirmation": { "candidates": [ { - "precedence": "inference_or_default", + "precedence": "reviewed_explicit", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "not_required" + }, + { + "precedence": "inference_or_default", + "selected": false, "source": "risk-default", "value": "not_required" } ], - "precedence": "inference_or_default", + "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "source": "risk-default", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", + "source": "internal/cli/schema_hints/metadata/doc.json", "value": "not_required" }, "description": { @@ -344326,7 +344335,7 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。" + "value": "获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。\n文档接口未返回 fileSize 时,会从钉盘元数据补齐文件大小。" }, { "precedence": "mcp_metadata", @@ -344338,20 +344347,28 @@ "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。" + "value": "获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。\n文档接口未返回 fileSize 时,会从钉盘元数据补齐文件大小。" }, "effect": { "candidates": [ { - "precedence": "inference_or_default", + "precedence": "reviewed_explicit", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "read" + }, + { + "precedence": "inference_or_default", + "selected": false, "source": "command-verb", "value": "read" } ], - "precedence": "inference_or_default", + "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "source": "command-verb", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", + "source": "internal/cli/schema_hints/metadata/doc.json", "value": "read" }, "examples": { @@ -344379,25 +344396,33 @@ "idempotency": { "candidates": [ { - "precedence": "inference_or_default", + "precedence": "reviewed_explicit", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "idempotent" + }, + { + "precedence": "inference_or_default", + "selected": false, "source": "effect-default", "value": "idempotent" } ], - "precedence": "inference_or_default", + "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "source": "effect-default", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", + "source": "internal/cli/schema_hints/metadata/doc.json", "value": "idempotent" }, "interface_mode": { "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "selected": true, "source": "internal/cli/schema_hints/metadata/doc.json", - "value": "mcp" + "value": "composite" }, { "precedence": "mcp_fallback", @@ -344408,20 +344433,42 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "source": "internal/cli/schema_hints/metadata/doc.json", - "value": "mcp" + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "The CLI uses doc/get_document_info as the authoritative response and best-effort enriches a missing fileSize from drive/get_file_info, so no single MCP interface represents the delivered result." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "The CLI uses doc/get_document_info as the authoritative response and best-effort enriches a missing fileSize from drive/get_file_info, so no single MCP interface represents the delivered result." }, "interface_ref": { "candidates": [ { - "precedence": "imported", + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null + } + ], + "overridden_candidates": [ + { + "precedence": "imported", + "selected": false, "source": "internal/cli/schema_hints/imported/wukong.json", - "value": { - "product_id": "doc", - "rpc_name": "get_document_info" - } + "value": "doc.get_document_info" }, { "precedence": "mcp_fallback", @@ -344430,13 +344477,11 @@ "value": "doc.get_document_info" } ], - "precedence": "imported", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/imported/wukong.json", - "value": { - "product_id": "doc", - "rpc_name": "get_document_info" - } + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": null }, "metadata_source": { "candidates": [ @@ -344456,7 +344501,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "selected": true, "source": "internal/cli/schema_hints/metadata/doc.json", "value": true @@ -344471,22 +344516,30 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/doc marks this tool as reviewed", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "source": "internal/cli/schema_hints/metadata/doc.json", "value": true }, "risk": { "candidates": [ { - "precedence": "inference_or_default", + "precedence": "reviewed_explicit", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", "selected": true, + "source": "internal/cli/schema_hints/metadata/doc.json", + "value": "low" + }, + { + "precedence": "inference_or_default", + "selected": false, "source": "effect-default", "value": "low" } ], - "precedence": "inference_or_default", + "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "source": "effect-default", + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", + "source": "internal/cli/schema_hints/metadata/doc.json", "value": "low" }, "title": { @@ -344518,7 +344571,8 @@ "source": "internal/cli/schema_hints/selection/doc.json", "value": [ "用户要查看文档/节点元信息(标题、类型、创建者、权限)时", - "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" + "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时", + "文档接口本身缺少 fileSize,但用户需要读取上传文件大小时" ] } ], @@ -344528,17 +344582,15 @@ "source": "internal/cli/schema_hints/selection/doc.json", "value": [ "用户要查看文档/节点元信息(标题、类型、创建者、权限)时", - "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" + "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时", + "文档接口本身缺少 fileSize,但用户需要读取上传文件大小时" ] } }, "has_parameters": true, "idempotency": "idempotent", - "interface_mode": "mcp", - "interface_ref": { - "product_id": "doc", - "rpc_name": "get_document_info" - }, + "interface_mode": "composite", + "interface_reason": "The CLI uses doc/get_document_info as the authoritative response and best-effort enriches a missing fileSize from drive/get_file_info, so no single MCP interface represents the delivered result.", "is_alias": false, "metadata_source": "embedded-mcp-metadata", "name": "get_document_info", @@ -344663,7 +344715,8 @@ "title": "获取文档元信息", "use_when": [ "用户要查看文档/节点元信息(标题、类型、创建者、权限)时", - "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" + "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时", + "文档接口本身缺少 fileSize,但用户需要读取上传文件大小时" ] }, "doc.import_get": { @@ -353166,7 +353219,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/doc.json", "value": [ - "用户要改文档/文件在列表与链接中展示的名称时" + "用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端" ] } ], @@ -353175,7 +353228,7 @@ "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", "source": "internal/cli/schema_hints/selection/doc.json", "value": [ - "用户要改文档/文件在列表与链接中展示的名称时" + "用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端" ] } }, @@ -353192,7 +353245,7 @@ "parameter_count": 2, "parameters": { "name": { - "description": "新名称 (必填)", + "description": "新名称 (必填;服务端保留原扩展名,传入常见扩展名时 CLI 会自动去掉一层)", "field_provenance": { "description": { "candidates": [ @@ -353200,7 +353253,7 @@ "precedence": "cobra_contract", "selected": true, "source": "cobra_usage", - "value": "新名称 (必填)" + "value": "新名称 (必填;服务端保留原扩展名,传入常见扩展名时 CLI 会自动去掉一层)" }, { "precedence": "mcp_metadata", @@ -353218,7 +353271,7 @@ "precedence": "cobra_contract", "resolution": "highest_precedence", "source": "cobra_usage", - "value": "新名称 (必填)" + "value": "新名称 (必填;服务端保留原扩展名,传入常见扩展名时 CLI 会自动去掉一层)" }, "property": { "candidates": [ @@ -353418,7 +353471,7 @@ "source": "reviewed_command_registry", "title": "重命名文档/文件", "use_when": [ - "用户要改文档/文件在列表与链接中展示的名称时" + "用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端" ] }, "doc.reply_comment": { @@ -390420,7 +390473,7 @@ "structured-hint:internal/cli/schema_hints/products/drive.json", "structured-hint:internal/cli/schema_hints/selection-review.json#drive.rename_document" ], - "agent_summary": "修改文档空间中文档或文件的名称", + "agent_summary": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名", "agent_summary_source": "dws-agent-selection/drive", "availability": "available", "avoid_when": [ @@ -390431,7 +390484,7 @@ "cli_name": "rename", "cli_path": "drive rename", "confirmation": "not_required", - "description": "修改文档空间中文档或文件的名称。\n\n权限要求: 对文档有\"编辑\"权限。", + "description": "修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名;\n如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。\n\n权限要求: 对文档有\"编辑\"权限。", "display": "钉盘文件管理", "effect": "write", "effect_source": "command-verb", @@ -390446,20 +390499,20 @@ "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", "selected": true, "source": "internal/cli/schema_hints/selection/drive.json", - "value": "修改文档空间中文档或文件的名称" + "value": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", "source": "internal/cli/schema_hints/selection/drive.json", - "value": "修改文档空间中文档或文件的名称" + "value": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名" }, "availability": { "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "selected": true, "source": "internal/cli/schema_hints/metadata/drive.json", "value": "available" @@ -390473,7 +390526,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "source": "internal/cli/schema_hints/metadata/drive.json", "value": "available" }, @@ -390507,6 +390560,14 @@ "source": "reviewed_command_registry", "source_ref": "drive rename", "value": "drive.rename_document" + }, + { + "precedence": "reviewed_manual", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", + "selected": false, + "source": "reviewed_manual_hint", + "source_ref": "drive rename", + "value": "drive.rename_document" } ], "precedence": "command_registry", @@ -390535,7 +390596,7 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "修改文档空间中文档或文件的名称。\n\n权限要求: 对文档有\"编辑\"权限。" + "value": "修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名;\n如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。\n\n权限要求: 对文档有\"编辑\"权限。" }, { "precedence": "mcp_metadata", @@ -390547,7 +390608,7 @@ "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "修改文档空间中文档或文件的名称。\n\n权限要求: 对文档有\"编辑\"权限。" + "value": "修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名;\n如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。\n\n权限要求: 对文档有\"编辑\"权限。" }, "effect": { "candidates": [ @@ -390601,7 +390662,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "selected": true, "source": "internal/cli/schema_hints/metadata/drive.json", "value": "mcp" @@ -390615,7 +390676,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "source": "internal/cli/schema_hints/metadata/drive.json", "value": "mcp" }, @@ -390623,7 +390684,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "selected": true, "source": "internal/cli/schema_hints/metadata/drive.json", "value": { @@ -390640,7 +390701,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "source": "internal/cli/schema_hints/metadata/drive.json", "value": { "product_id": "doc", @@ -390665,7 +390726,7 @@ "candidates": [ { "precedence": "reviewed_explicit", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "selected": true, "source": "internal/cli/schema_hints/metadata/drive.json", "value": true @@ -390680,7 +390741,7 @@ ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", - "review_reason": "dws-tool-metadata/drive marks this tool as reviewed", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "source": "internal/cli/schema_hints/metadata/drive.json", "value": true }, @@ -390726,7 +390787,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/drive.json", "value": [ - "用户要重命名钉盘/文档空间中的文件或文档显示名称时" + "用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层" ] } ], @@ -390735,7 +390796,7 @@ "review_reason": "人工审阅:结合本机 dws schema 实时 MCP description/parameters(经 interface_ref)、产品 Skill、Cobra Long 与 Runtime 确认门禁,按飞书风格重写选型文案;不改变命令身份、参数契约或接口绑定。", "source": "internal/cli/schema_hints/selection/drive.json", "value": [ - "用户要重命名钉盘/文档空间中的文件或文档显示名称时" + "用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层" ] } }, @@ -390752,15 +390813,22 @@ "parameter_count": 2, "parameters": { "name": { - "description": "新名称 (必填)", + "description": "新显示名称;服务端保留节点原扩展名,若传入常见文件扩展名,CLI 会自动去掉一层以避免双扩展名", "field_provenance": { "description": { "candidates": [ { - "precedence": "cobra_contract", + "precedence": "reviewed_manual", + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", "selected": true, + "source": "reviewed_manual_hint", + "value": "新显示名称;服务端保留节点原扩展名,若传入常见文件扩展名,CLI 会自动去掉一层以避免双扩展名" + }, + { + "precedence": "cobra_contract", + "selected": false, "source": "cobra_usage", - "value": "新名称 (必填)" + "value": "新名称 (必填;服务端保留原扩展名,传入常见扩展名时 CLI 会自动去掉一层)" }, { "precedence": "mcp_metadata", @@ -390775,10 +390843,11 @@ "value": "" } ], - "precedence": "cobra_contract", + "precedence": "reviewed_manual", "resolution": "highest_precedence", - "source": "cobra_usage", - "value": "新名称 (必填)" + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", + "source": "reviewed_manual_hint", + "value": "新显示名称;服务端保留节点原扩展名,若传入常见文件扩展名,CLI 会自动去掉一层以避免双扩展名" }, "property": { "candidates": [ @@ -390978,7 +391047,7 @@ "source": "reviewed_command_registry", "title": "重命名文件/文档", "use_when": [ - "用户要重命名钉盘/文档空间中的文件或文档显示名称时" + "用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层" ] }, "drive.search_files": { @@ -557287,21 +557356,23 @@ "internal/cli/schema_hints/selection/todo.json", "internal/cli/schema_mcp_metadata.json#tools.todo.add_todo_reminder", "live-dws-schema:todo.add_todo_reminder", + "skills/mono/references/intent-guide.md", "skills/mono/references/products/todo.md", "structured-hint:internal/cli/schema_hints/selection-review.json#todo.add_todo_reminder" ], - "agent_summary": "添加待办提醒", + "agent_summary": "写入一条待办提醒规则(上游不支持规则读回)", "agent_summary_source": "dws-agent-selection/todo", "availability": "available", "avoid_when": [ "要重置/清除提醒规则时改用 dws todo task reset-reminder", - "提醒时间与目标待办未确认时不要添加" + "提醒时间与目标待办未确认时不要添加", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "canonical_path": "todo.add_todo_reminder", "cli_name": "add-reminder", "cli_path": "todo task add-reminder", "confirmation": "not_required", - "description": "添加待办提醒", + "description": "为已有待办写入一条提醒规则。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验该规则。", "display": "待办任务管理", "effect": "write", "effect_source": "command-verb", @@ -557317,14 +557388,14 @@ "review_reason": "结合本机 dws schema 实时描述、Skill 与 Cobra 手写选型;pinned schema_mcp_metadata 仅对照。", "selected": true, "source": "internal/cli/schema_hints/selection/todo.json", - "value": "添加待办提醒" + "value": "写入一条待办提醒规则(上游不支持规则读回)" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "结合本机 dws schema 实时描述、Skill 与 Cobra 手写选型;pinned schema_mcp_metadata 仅对照。", "source": "internal/cli/schema_hints/selection/todo.json", - "value": "添加待办提醒" + "value": "写入一条待办提醒规则(上游不支持规则读回)" }, "availability": { "candidates": [ @@ -557357,7 +557428,8 @@ "source": "internal/cli/schema_hints/selection/todo.json", "value": [ "要重置/清除提醒规则时改用 dws todo task reset-reminder", - "提醒时间与目标待办未确认时不要添加" + "提醒时间与目标待办未确认时不要添加", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ] } ], @@ -557367,7 +557439,8 @@ "source": "internal/cli/schema_hints/selection/todo.json", "value": [ "要重置/清除提醒规则时改用 dws todo task reset-reminder", - "提醒时间与目标待办未确认时不要添加" + "提醒时间与目标待办未确认时不要添加", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ] }, "canonical_path": { @@ -557406,7 +557479,7 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "添加待办提醒" + "value": "为已有待办写入一条提醒规则。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验该规则。" }, { "precedence": "mcp_metadata", @@ -557418,7 +557491,7 @@ "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "添加待办提醒" + "value": "为已有待办写入一条提醒规则。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验该规则。" }, "effect": { "candidates": [ @@ -557591,7 +557664,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/todo.json", "value": [ - "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间)" + "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间);成功响应仅作为写入回执" ] } ], @@ -557600,7 +557673,7 @@ "review_reason": "结合本机 dws schema 实时描述、Skill 与 Cobra 手写选型;pinned schema_mcp_metadata 仅对照。", "source": "internal/cli/schema_hints/selection/todo.json", "value": [ - "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间)" + "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间);成功响应仅作为写入回执" ] } }, @@ -558105,7 +558178,7 @@ "source": "reviewed_command_registry", "title": "添加待办提醒", "use_when": [ - "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间)" + "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间);成功响应仅作为写入回执" ] }, "todo.create_personal_sub_todo": { @@ -559051,6 +559124,7 @@ "internal/cli/schema_hints/selection/todo.json", "internal/cli/schema_mcp_metadata.json#tools.todo.create_personal_todo", "live-dws-schema:todo.create_personal_todo", + "skills/mono/references/intent-guide.md", "skills/mono/references/products/todo.md", "structured-hint:internal/cli/schema_hints/imported/wukong.json#todo.create_personal_todo", "structured-hint:internal/cli/schema_hints/selection-review.json#todo.create_personal_todo" @@ -561927,13 +562001,14 @@ "availability": "available", "avoid_when": [ "需要按条件查多个待办时改用 dws todo task list", - "需要修改待办时改用 update/done/delete 等写命令" + "需要修改待办时改用 update/done/delete 等写命令", + "需要读取或验证提醒规则时不要使用;当前详情接口不返回 reminderRules" ], "canonical_path": "todo.get_todo_detail", "cli_name": "get", "cli_path": "todo task get", "confirmation": "not_required", - "description": "待办详情", + "description": "查询单条待办的详情。\n\n当前上游详情接口不返回 reminderRules;本命令不能读取或验证 add-reminder /\nreset-reminder 写入的提醒规则。提醒写命令的成功响应只能作为写入回执。", "display": "待办任务管理", "effect": "read", "effect_source": "command-verb", @@ -561989,7 +562064,8 @@ "source": "internal/cli/schema_hints/selection/todo.json", "value": [ "需要按条件查多个待办时改用 dws todo task list", - "需要修改待办时改用 update/done/delete 等写命令" + "需要修改待办时改用 update/done/delete 等写命令", + "需要读取或验证提醒规则时不要使用;当前详情接口不返回 reminderRules" ] } ], @@ -561999,7 +562075,8 @@ "source": "internal/cli/schema_hints/selection/todo.json", "value": [ "需要按条件查多个待办时改用 dws todo task list", - "需要修改待办时改用 update/done/delete 等写命令" + "需要修改待办时改用 update/done/delete 等写命令", + "需要读取或验证提醒规则时不要使用;当前详情接口不返回 reminderRules" ] }, "canonical_path": { @@ -562038,7 +562115,7 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "待办详情" + "value": "查询单条待办的详情。\n\n当前上游详情接口不返回 reminderRules;本命令不能读取或验证 add-reminder /\nreset-reminder 写入的提醒规则。提醒写命令的成功响应只能作为写入回执。" }, { "precedence": "mcp_metadata", @@ -562050,7 +562127,7 @@ "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "待办详情" + "value": "查询单条待办的详情。\n\n当前上游详情接口不返回 reminderRules;本命令不能读取或验证 add-reminder /\nreset-reminder 写入的提醒规则。提醒写命令的成功响应只能作为写入回执。" }, "effect": { "candidates": [ @@ -566319,18 +566396,19 @@ "skills/mono/references/products/todo.md", "structured-hint:internal/cli/schema_hints/selection-review.json#todo.reset_todo_reminder" ], - "agent_summary": "重置待办提醒", + "agent_summary": "整体替换或清除待办提醒规则(上游不支持规则读回)", "agent_summary_source": "dws-agent-selection/todo", "availability": "available", "avoid_when": [ "只需追加一条提醒时改用 dws todo task add-reminder", - "新规则与目标待办未确认时不要重置" + "新规则与目标待办未确认时不要重置", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "canonical_path": "todo.reset_todo_reminder", "cli_name": "reset-reminder", "cli_path": "todo task reset-reminder", "confirmation": "not_required", - "description": "重置待办提醒", + "description": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。", "display": "待办任务管理", "effect": "write", "effect_source": "command-verb", @@ -566346,14 +566424,14 @@ "review_reason": "结合本机 dws schema 实时描述、Skill 与 Cobra 手写选型;pinned schema_mcp_metadata 仅对照。", "selected": true, "source": "internal/cli/schema_hints/selection/todo.json", - "value": "重置待办提醒" + "value": "整体替换或清除待办提醒规则(上游不支持规则读回)" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", "review_reason": "结合本机 dws schema 实时描述、Skill 与 Cobra 手写选型;pinned schema_mcp_metadata 仅对照。", "source": "internal/cli/schema_hints/selection/todo.json", - "value": "重置待办提醒" + "value": "整体替换或清除待办提醒规则(上游不支持规则读回)" }, "availability": { "candidates": [ @@ -566386,7 +566464,8 @@ "source": "internal/cli/schema_hints/selection/todo.json", "value": [ "只需追加一条提醒时改用 dws todo task add-reminder", - "新规则与目标待办未确认时不要重置" + "新规则与目标待办未确认时不要重置", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ] } ], @@ -566396,7 +566475,8 @@ "source": "internal/cli/schema_hints/selection/todo.json", "value": [ "只需追加一条提醒时改用 dws todo task add-reminder", - "新规则与目标待办未确认时不要重置" + "新规则与目标待办未确认时不要重置", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ] }, "canonical_path": { @@ -566435,7 +566515,7 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "重置待办提醒" + "value": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。" }, { "precedence": "mcp_metadata", @@ -566447,7 +566527,7 @@ "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "重置待办提醒" + "value": "整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。\n\n当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules;\n成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。" }, "effect": { "candidates": [ @@ -566620,7 +566700,7 @@ "selected": true, "source": "internal/cli/schema_hints/selection/todo.json", "value": [ - "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除)" + "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执" ] } ], @@ -566629,7 +566709,7 @@ "review_reason": "结合本机 dws schema 实时描述、Skill 与 Cobra 手写选型;pinned schema_mcp_metadata 仅对照。", "source": "internal/cli/schema_hints/selection/todo.json", "value": [ - "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除)" + "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执" ] } }, @@ -566841,7 +566921,7 @@ "source": "reviewed_command_registry", "title": "重置待办提醒", "use_when": [ - "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除)" + "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执" ] }, "todo.shortcut_assign": { @@ -571405,7 +571485,7 @@ "cli_name": "+remind", "cli_path": "todo +remind", "confirmation": "user_required", - "description": "当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。", + "description": "当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。", "display": "待办任务管理", "effect": "write", "effect_source": "agent-hint", @@ -571503,13 +571583,13 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。" + "value": "当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。" } ], "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。" + "value": "当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。" }, "effect": { "candidates": [ @@ -571656,13 +571736,13 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "给自己创建一条带截止/提醒时间的待办" + "value": "给自己创建一条带可选截止时间的待办" } ], "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "给自己创建一条带截止/提醒时间的待办" + "value": "给自己创建一条带可选截止时间的待办" }, "use_when": { "candidates": [ @@ -571694,7 +571774,7 @@ "parameter_count": 2, "parameters": { "at": { - "description": "截止/提醒时间(ISO8601,可选,如 2026-03-10T18:00:00+08:00)", + "description": "截止时间(ISO8601,可选,不是提醒时间;如 2026-03-10T18:00:00+08:00)", "field_provenance": { "description": { "candidates": [ @@ -571702,7 +571782,7 @@ "precedence": "cobra_contract", "selected": true, "source": "cobra_usage", - "value": "截止/提醒时间(ISO8601,可选,如 2026-03-10T18:00:00+08:00)" + "value": "截止时间(ISO8601,可选,不是提醒时间;如 2026-03-10T18:00:00+08:00)" }, { "precedence": "default", @@ -571714,7 +571794,7 @@ "precedence": "cobra_contract", "resolution": "highest_precedence", "source": "cobra_usage", - "value": "截止/提醒时间(ISO8601,可选,如 2026-03-10T18:00:00+08:00)" + "value": "截止时间(ISO8601,可选,不是提醒时间;如 2026-03-10T18:00:00+08:00)" }, "property": { "candidates": [ @@ -571880,7 +571960,7 @@ "reviewed": true, "risk": "medium", "source": "reviewed_command_registry", - "title": "给自己创建一条带截止/提醒时间的待办", + "title": "给自己创建一条带可选截止时间的待办", "use_when": [ "当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。" ] diff --git a/internal/cli/schema_hints/metadata/doc.json b/internal/cli/schema_hints/metadata/doc.json index e9212b3f..670aa1d2 100644 --- a/internal/cli/schema_hints/metadata/doc.json +++ b/internal/cli/schema_hints/metadata/doc.json @@ -144,9 +144,16 @@ "runtime_gate": "none" }, "doc.get_document_info": { - "interface_mode": "mcp", + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", "availability": "available", - "reviewed": true + "interface_reason": "The CLI uses doc/get_document_info as the authoritative response and best-effort enriches a missing fileSize from drive/get_file_info, so no single MCP interface represents the delivered result.", + "reviewed": true, + "review_reason": "Reviewed composite metadata read fixes the document endpoint's missing fileSize without changing its authoritative title, type, owner, or permission fields.", + "runtime_gate": "none" }, "doc.import_get": { "effect": "write", diff --git a/internal/cli/schema_hints/metadata/drive.json b/internal/cli/schema_hints/metadata/drive.json index 3f9fb103..ff802f80 100644 --- a/internal/cli/schema_hints/metadata/drive.json +++ b/internal/cli/schema_hints/metadata/drive.json @@ -203,7 +203,14 @@ }, "interface_mode": "mcp", "availability": "available", - "reviewed": true + "reviewed": true, + "parameters": { + "name": { + "description": "新显示名称;服务端保留节点原扩展名,若传入常见文件扩展名,CLI 会自动去掉一层以避免双扩展名" + } + }, + "review_reason": "The reviewed CLI normalizes one familiar extension from --name because rename_document preserves the node's existing extension.", + "cli_path": "drive rename" }, "drive.search_files": { "interface_mode": "mcp", diff --git a/internal/cli/schema_hints/selection/doc.json b/internal/cli/schema_hints/selection/doc.json index 345b2413..b2a619bc 100644 --- a/internal/cli/schema_hints/selection/doc.json +++ b/internal/cli/schema_hints/selection/doc.json @@ -321,10 +321,11 @@ ] }, "doc.get_document_info": { - "agent_summary": "获取文档元信息(标题/类型/创建者/权限等)", + "agent_summary": "获取文档元信息(标题/类型/创建者/权限等,并从钉盘补齐缺失的文件大小)", "use_when": [ "用户要查看文档/节点元信息(标题、类型、创建者、权限)时", - "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时" + "准备读内容前必须先看 contentType/extension 以路由到 read/sheet/aitable/download 时", + "文档接口本身缺少 fileSize,但用户需要读取上传文件大小时" ], "avoid_when": [ "已确认是 adoc 且只要正文改用 dws doc read", @@ -581,7 +582,7 @@ "doc.rename_document": { "agent_summary": "兼容入口:重命名文档或文件;文件重命名能力已迁移到 drive。", "use_when": [ - "用户要改文档/文件在列表与链接中展示的名称时" + "用户要改文档/文件在列表与链接中展示的名称时;即使名称带常见扩展名,CLI 也会去掉一层再交给保留原扩展名的服务端" ], "avoid_when": [ "改正文标题/章节标题改用 doc block update", diff --git a/internal/cli/schema_hints/selection/drive.json b/internal/cli/schema_hints/selection/drive.json index 9b65af9e..47012981 100644 --- a/internal/cli/schema_hints/selection/drive.json +++ b/internal/cli/schema_hints/selection/drive.json @@ -545,9 +545,9 @@ ] }, "drive.rename_document": { - "agent_summary": "修改文档空间中文档或文件的名称", + "agent_summary": "修改文档空间中文档或文件的名称,并避免服务端保留原扩展名导致双扩展名", "use_when": [ - "用户要重命名钉盘/文档空间中的文件或文档显示名称时" + "用户要重命名钉盘/文档空间中的文件或文档显示名称时;--name 带常见扩展名也可,CLI 会自动去掉一层" ], "avoid_when": [ "要改正文里的标题/章节 H1 改用 dws doc block update,不要用 rename", diff --git a/internal/cli/schema_hints/selection/todo.json b/internal/cli/schema_hints/selection/todo.json index a01ea70c..b3d8df98 100644 --- a/internal/cli/schema_hints/selection/todo.json +++ b/internal/cli/schema_hints/selection/todo.json @@ -103,13 +103,14 @@ ] }, "todo.add_todo_reminder": { - "agent_summary": "添加待办提醒", + "agent_summary": "写入一条待办提醒规则(上游不支持规则读回)", "use_when": [ - "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间)" + "已知 taskId,需要按截止时间偏移或自定义时间戳添加提醒时(dueTime 模式要求待办已有截止时间);成功响应仅作为写入回执" ], "avoid_when": [ "要重置/清除提醒规则时改用 dws todo task reset-reminder", - "提醒时间与目标待办未确认时不要添加" + "提醒时间与目标待办未确认时不要添加", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "examples": [ "dws todo task add-reminder --task-id --base-time dueTime --due-date-offset -30", @@ -224,7 +225,8 @@ ], "avoid_when": [ "需要按条件查多个待办时改用 dws todo task list", - "需要修改待办时改用 update/done/delete 等写命令" + "需要修改待办时改用 update/done/delete 等写命令", + "需要读取或验证提醒规则时不要使用;当前详情接口不返回 reminderRules" ], "examples": [ "dws todo task get --task-id ", @@ -356,13 +358,14 @@ ] }, "todo.reset_todo_reminder": { - "agent_summary": "重置待办提醒", + "agent_summary": "整体替换或清除待办提醒规则(上游不支持规则读回)", "use_when": [ - "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除)" + "需要清除或整体替换待办提醒规则时(可不传 reminder-rules 以清除);成功响应仅作为写入回执" ], "avoid_when": [ "只需追加一条提醒时改用 dws todo task add-reminder", - "新规则与目标待办未确认时不要重置" + "新规则与目标待办未确认时不要重置", + "业务要求写后读取并核验 reminderRules 时不要声称可验证;当前 task get/list 均不返回提醒规则" ], "examples": [ "dws todo task reset-reminder --task-id ", diff --git a/internal/helpers/common.go b/internal/helpers/common.go index b1c9adee..a8c32dca 100644 --- a/internal/helpers/common.go +++ b/internal/helpers/common.go @@ -14,6 +14,9 @@ package helpers import ( + "path/filepath" + "strings" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output" "github.com/spf13/cobra" @@ -49,3 +52,26 @@ func commandBoolFlag(cmd *cobra.Command, name string) bool { } return false } + +// renameBaseName removes one familiar file extension before calling +// rename_document. That service preserves the node's current extension, so +// forwarding "report.txt" for a .txt file would otherwise produce +// "report.txt.txt". Unknown dotted suffixes are preserved because they may be +// part of a display name (for example "release.v2"). +func renameBaseName(name string) string { + trimmed := strings.TrimSpace(name) + extension := strings.ToLower(strings.TrimPrefix(filepath.Ext(trimmed), ".")) + if _, ok := renamePreservedExtensions[extension]; !ok { + return trimmed + } + return strings.TrimSuffix(trimmed, filepath.Ext(trimmed)) +} + +var renamePreservedExtensions = map[string]struct{}{ + "7z": {}, "able": {}, "adoc": {}, "adraw": {}, "aform": {}, "amind": {}, + "appt": {}, "awbd": {}, "axls": {}, "csv": {}, "doc": {}, "docx": {}, + "gif": {}, "gz": {}, "jpeg": {}, "jpg": {}, "json": {}, "md": {}, + "mp3": {}, "mp4": {}, "pdf": {}, "png": {}, "ppt": {}, "pptx": {}, + "rar": {}, "svg": {}, "tar": {}, "txt": {}, "wav": {}, "webp": {}, + "xls": {}, "xlsx": {}, "xml": {}, "zip": {}, +} diff --git a/internal/helpers/contract_defects_test.go b/internal/helpers/contract_defects_test.go new file mode 100644 index 00000000..639c66b0 --- /dev/null +++ b/internal/helpers/contract_defects_test.go @@ -0,0 +1,206 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package helpers + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "reflect" + "strings" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" + "github.com/spf13/cobra" +) + +type contractDefectCaller struct { + dryRun bool + calls []guardedMutationCall + responses map[string]string + errors map[string]error +} + +func (c *contractDefectCaller) CallTool(_ context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) { + c.calls = append(c.calls, guardedMutationCall{productID: productID, toolName: toolName, args: args}) + key := productID + "/" + toolName + if err := c.errors[key]; err != nil { + return nil, err + } + text := c.responses[key] + if text == "" { + text = `{}` + } + return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: text}}}, nil +} + +func (*contractDefectCaller) Format() string { return "json" } +func (c *contractDefectCaller) DryRun() bool { return c.dryRun } +func (*contractDefectCaller) Fields() string { return "" } +func (*contractDefectCaller) JQ() string { return "" } + +func executeContractDefectCommand(t *testing.T, caller *contractDefectCaller, build func() *cobra.Command, args ...string) (string, error) { + t.Helper() + previousDeps := deps + t.Cleanup(func() { deps = previousDeps }) + + InitDeps(caller) + var stdout bytes.Buffer + deps.Out.w = &stdout + deps.Out.errW = io.Discard + + root := build() + if root.PersistentFlags().Lookup("yes") == nil { + root.PersistentFlags().Bool("yes", false, "confirm high-risk operation") + } + if root.PersistentFlags().Lookup("dry-run") == nil { + root.PersistentFlags().Bool("dry-run", false, "preview without executing") + } + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs(args) + err := root.Execute() + return stdout.String(), err +} + +func TestApprovalRevokeDryRunSkipsConfirmationAndEmitsPreview(t *testing.T) { + caller := &contractDefectCaller{dryRun: true} + output, err := executeContractDefectCommand(t, caller, newOaCommand, + "approval", "revoke", "--instance-id", "instance-dry-run", "--dry-run") + if err != nil { + t.Fatalf("approval revoke dry-run returned error: %v", err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run tool calls = %#v, want none", caller.calls) + } + if !strings.Contains(output, `"tool": "revoke_processInstance"`) || + !strings.Contains(output, `"processInstanceId": "instance-dry-run"`) { + t.Fatalf("dry-run output = %q, want revoke preview", output) + } +} + +func TestDocVersionRevertDryRunSkipsRemotePreflightAndEmitsPreview(t *testing.T) { + caller := &contractDefectCaller{dryRun: true} + output, err := executeContractDefectCommand(t, caller, newDocCommand, + "version", "revert", "--node", "node-dry-run", "--version", "7", "--dry-run") + if err != nil { + t.Fatalf("doc version revert dry-run returned error: %v", err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run tool calls = %#v, want none", caller.calls) + } + if !strings.Contains(output, `"tool": "revert_doc_version"`) || + !strings.Contains(output, `"version": 7`) { + t.Fatalf("dry-run output = %q, want version revert preview", output) + } +} + +func TestRenameDocumentRemovesOnePreservedExtension(t *testing.T) { + tests := []struct { + name string + build func() *cobra.Command + args []string + }{ + { + name: "doc", + build: newDocCommand, + args: []string{"rename", "--node", "node-1", "--name", "report.txt"}, + }, + { + name: "drive", + build: newDriveCommand, + args: []string{"rename", "--node", "node-1", "--name", "report.txt"}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + caller := &contractDefectCaller{} + if _, err := executeContractDefectCommand(t, caller, test.build, test.args...); err != nil { + t.Fatalf("rename returned error: %v", err) + } + if len(caller.calls) != 1 { + t.Fatalf("tool calls = %#v, want one rename call", caller.calls) + } + if got := caller.calls[0].args["newName"]; got != "report" { + t.Fatalf("newName = %#v, want report", got) + } + }) + } + + if got := renameBaseName("release.v2"); got != "release.v2" { + t.Fatalf("unknown dotted suffix = %q, want release.v2", got) + } + if got := renameBaseName("REPORT.XLSX"); got != "REPORT" { + t.Fatalf("case-insensitive extension normalization = %q, want REPORT", got) + } +} + +func TestDocInfoEnrichesMissingFileSizeFromDriveMetadata(t *testing.T) { + caller := &contractDefectCaller{ + responses: map[string]string{ + "doc/get_document_info": `{"result":{"title":"budget.xlsx","fileSize":null}}`, + "drive/get_file_info": `{"result":{"fileSize":93682}}`, + }, + } + output, err := executeContractDefectCommand(t, caller, newDocCommand, + "info", "--node", "node-1") + if err != nil { + t.Fatalf("doc info returned error: %v", err) + } + wantCalls := []guardedMutationCall{ + {productID: "doc", toolName: "get_document_info", args: map[string]any{"nodeId": "node-1"}}, + {productID: "drive", toolName: "get_file_info", args: map[string]any{"fileId": "node-1"}}, + } + if !reflect.DeepEqual(caller.calls, wantCalls) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, wantCalls) + } + var payload struct { + Result struct { + FileSize int64 `json:"fileSize"` + } `json:"result"` + } + if err := json.Unmarshal([]byte(output), &payload); err != nil { + t.Fatalf("decode doc info output %q: %v", output, err) + } + if payload.Result.FileSize != 93682 { + t.Fatalf("fileSize = %d, want 93682", payload.Result.FileSize) + } +} + +func TestDocInfoFallsBackWhenDriveMetadataIsUnavailable(t *testing.T) { + caller := &contractDefectCaller{ + responses: map[string]string{ + "doc/get_document_info": `{"result":{"title":"online document"}}`, + }, + errors: map[string]error{ + "drive/get_file_info": fmt.Errorf("drive metadata unavailable"), + }, + } + output, err := executeContractDefectCommand(t, caller, newDocCommand, + "info", "--node", "node-1") + if err != nil { + t.Fatalf("doc info fallback returned error: %v", err) + } + if !strings.Contains(output, `"title": "online document"`) { + t.Fatalf("fallback output = %q, want original document metadata", output) + } +} + +func TestDocInfoDryRunUsesSingleStandardPreview(t *testing.T) { + caller := &contractDefectCaller{dryRun: true} + output, err := executeContractDefectCommand(t, caller, newDocCommand, + "info", "--node", "node-dry-run", "--dry-run") + if err != nil { + t.Fatalf("doc info dry-run returned error: %v", err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run tool calls = %#v, want none", caller.calls) + } + if !strings.Contains(output, `"tool": "get_document_info"`) || + !strings.Contains(output, `"nodeId": "node-dry-run"`) { + t.Fatalf("dry-run output = %q, want document info preview", output) + } +} diff --git a/internal/helpers/doc.go b/internal/helpers/doc.go index 8b16a3ed..8415c489 100644 --- a/internal/helpers/doc.go +++ b/internal/helpers/doc.go @@ -61,6 +61,60 @@ func docVersionExists(ctx context.Context, nodeID string, version int) (bool, er return false, nil } +// docInfoWithDriveSize keeps the document metadata response authoritative and +// best-effort enriches its missing fileSize from drive/get_file_info. The +// document metadata endpoint does not expose a size for several uploaded file +// types even though the drive endpoint does. +func docInfoWithDriveSize(ctx context.Context, nodeID string) error { + docText, err := callMCPToolReturnTextOnServer(ctx, "doc", "get_document_info", map[string]any{ + "nodeId": nodeID, + }) + if err != nil { + return err + } + + var docResponse map[string]any + if err := json.Unmarshal([]byte(docText), &docResponse); err != nil { + deps.Out.PrintRaw(docText) + return nil + } + docResult, wrapped := docResponse["result"].(map[string]any) + if !wrapped { + docResult = docResponse + } + if current, exists := docResult["fileSize"]; exists && current != nil { + return deps.Out.PrintJSON(docResponse) + } + + fileID := nodeID + if extracted := extractNodeIDFromDocURL(nodeID); extracted != "" { + fileID = extracted + } + driveText, err := callMCPToolReturnTextOnServer(ctx, "drive", "get_file_info", map[string]any{ + "fileId": fileID, + }) + if err != nil { + return deps.Out.PrintJSON(docResponse) + } + + var driveResponse map[string]any + if err := json.Unmarshal([]byte(driveText), &driveResponse); err != nil { + return deps.Out.PrintJSON(docResponse) + } + driveResult, ok := driveResponse["result"].(map[string]any) + if !ok { + driveResult = driveResponse + } + fileSize, exists := driveResult["fileSize"] + if !exists || fileSize == nil { + fileSize, exists = driveResult["size"] + } + if exists && fileSize != nil { + docResult["fileSize"] = fileSize + } + return deps.Out.PrintJSON(docResponse) +} + // docVersionNextCursor 从 list_doc_versions 响应中提取分页游标;没有下一页时返回 ""。 func docVersionNextCursor(v any) string { switch val := v.(type) { @@ -917,7 +971,8 @@ func newDocCommand() *cobra.Command { infoCmd := &cobra.Command{ Use: "info", Short: "获取文档元信息", - Long: `获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。`, + Long: `获取文档标题、类型、创建者、创建时间、权限等元信息 (不含内容)。 +文档接口未返回 fileSize 时,会从钉盘元数据补齐文件大小。`, Example: ` dws doc info --node DOC_ID dws doc info --node "https://alidocs.dingtalk.com/i/nodes/"`, RunE: func(cmd *cobra.Command, args []string) error { @@ -925,9 +980,10 @@ func newDocCommand() *cobra.Command { if err != nil { return err } - return callMCPTool("get_document_info", map[string]any{ - "nodeId": nodeID, - }) + if commandDryRun(cmd) { + return callMCPToolOnServer("doc", "get_document_info", map[string]any{"nodeId": nodeID}) + } + return docInfoWithDriveSize(cmd.Context(), nodeID) }, } @@ -1550,7 +1606,7 @@ WARNING: --mode overwrite 为破坏性写入,会清空原文档全部内容。 } return callMCPTool("rename_document", map[string]any{ "nodeId": nodeID, - "newName": flagOrFallback(cmd, "name", "title"), + "newName": renameBaseName(flagOrFallback(cmd, "name", "title")), }) }, } @@ -1740,7 +1796,7 @@ WARNING: --mode overwrite 为破坏性写入,会清空原文档全部内容。 // rename renameCmd.Flags().String("node", "", "文档/文件 ID 或 URL (必填)") - renameCmd.Flags().String("name", "", "新名称 (必填)") + renameCmd.Flags().String("name", "", "新名称 (必填;服务端保留原扩展名,传入常见扩展名时 CLI 会自动去掉一层)") // delete deleteCmd.Flags().String("node", "", "文档/文件 ID 或 URL (必填)") @@ -2709,15 +2765,17 @@ CLI 内部自动完成全部流程: return fmt.Errorf("flag --version is required") } version, _ := cmd.Flags().GetInt("version") - exists, err := docVersionExists(cmd.Context(), nodeID, version) - if err != nil { - return err - } - if !exists { - return fmt.Errorf("文档版本 %d 不存在,已停止回滚;请先执行 dws doc version list --node %s --format json 获取可回滚版本", version, nodeID) - } - if !confirmDangerousAction(cmd, fmt.Sprintf("revert document to version %d", version), nodeID) { - return nil + if !commandDryRun(cmd) { + exists, err := docVersionExists(cmd.Context(), nodeID, version) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("文档版本 %d 不存在,已停止回滚;请先执行 dws doc version list --node %s --format json 获取可回滚版本", version, nodeID) + } + if !confirmDangerousAction(cmd, fmt.Sprintf("revert document to version %d", version), nodeID) { + return nil + } } return callMCPToolOnServer("doc", "revert_doc_version", map[string]any{ "nodeId": nodeID, diff --git a/internal/helpers/drive.go b/internal/helpers/drive.go index 15bb07a5..bc046e95 100644 --- a/internal/helpers/drive.go +++ b/internal/helpers/drive.go @@ -834,7 +834,8 @@ func newDriveCommand() *cobra.Command { driveRenameCmd := &cobra.Command{ Use: "rename", Short: "重命名文件/文档", - Long: `修改文档空间中文档或文件的名称。 + Long: `修改文档空间中文档或文件的名称。服务端会保留节点的原扩展名; +如果 --name 带常见文件扩展名,CLI 会自动去掉一层,避免出现 report.txt.txt。 权限要求: 对文档有"编辑"权限。`, Example: ` dws drive rename --node DOC_ID --name "新名称"`, @@ -848,12 +849,12 @@ func newDriveCommand() *cobra.Command { } return callMCPToolOnServer("doc", "rename_document", map[string]any{ "nodeId": nodeID, - "newName": flagOrFallback(cmd, "name", "title"), + "newName": renameBaseName(flagOrFallback(cmd, "name", "title")), }) }, } driveRenameCmd.Flags().String("node", "", "文档/文件 ID 或 URL (必填)") - driveRenameCmd.Flags().String("name", "", "新名称 (必填)") + driveRenameCmd.Flags().String("name", "", "新名称 (必填;服务端保留原扩展名,传入常见扩展名时 CLI 会自动去掉一层)") driveStatsCmd := &cobra.Command{ Use: "stats", diff --git a/internal/helpers/oa.go b/internal/helpers/oa.go index 19f917e4..690fa4a9 100644 --- a/internal/helpers/oa.go +++ b/internal/helpers/oa.go @@ -129,7 +129,7 @@ func newOaCommand() *cobra.Command { return err } instanceId := mustGetFlag(cmd, "instance-id") - if !confirmDelete("审批实例", instanceId) { + if !commandDryRun(cmd) && !confirmDelete("审批实例", instanceId) { return nil } argsMap := map[string]any{ diff --git a/internal/helpers/todo.go b/internal/helpers/todo.go index 60c934b6..189162c0 100644 --- a/internal/helpers/todo.go +++ b/internal/helpers/todo.go @@ -282,6 +282,10 @@ func newTodoCommand() *cobra.Command { todoTaskGetCmd := &cobra.Command{ Use: "get", Short: "待办详情", + Long: `查询单条待办的详情。 + +当前上游详情接口不返回 reminderRules;本命令不能读取或验证 add-reminder / +reset-reminder 写入的提醒规则。提醒写命令的成功响应只能作为写入回执。`, Example: ` dws todo task get --task-id # 查询 taskId: dws todo task list`, @@ -427,6 +431,10 @@ func newTodoCommand() *cobra.Command { todoTaskAddReminderCmd := &cobra.Command{ Use: "add-reminder", Short: "添加待办提醒", + Long: `为已有待办写入一条提醒规则。 + +当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules; +成功响应是写入回执,不代表 CLI 能再次读取并核验该规则。`, Example: ` dws todo task add-reminder --task-id --base-time dueTime --due-date-offset -30 dws todo task add-reminder --task-id --base-time customTime --reminder-time-stamp 2026-03-10T18:00:00+08:00 # 查询 taskId: dws todo task list @@ -473,6 +481,10 @@ func newTodoCommand() *cobra.Command { todoTaskUpdateReminderCmd := &cobra.Command{ Use: "reset-reminder", Short: "重置待办提醒", + Long: `整体替换待办提醒规则;不传 --reminder-rules 时清除提醒。 + +当前上游没有提醒规则查询接口,task get/list 也不返回 reminderRules; +成功响应是写入回执,不代表 CLI 能再次读取并核验最终规则。`, Example: ` dws todo task reset-reminder --task-id dws todo task reset-reminder --task-id --reminder-rules # 查询 taskId: dws todo task list @@ -924,7 +936,7 @@ func rejectUnsupportedTodoReminderFlags(cmd *cobra.Command) error { return &CLIError{ Code: CodeMissingParam, Message: fmt.Sprintf("todo 当前不支持独立 reminder 参数: %s", strings.Join(unsupported, ", ")), - Suggestion: "请使用 --due 表示截止时间;如果用户要的是精确提醒时间,请明确说明该能力当前不支持,而不是把 --due 当作 reminder。", + Suggestion: "请先用 --due 设置截止时间;需要独立提醒时,再用 dws todo task add-reminder。当前上游不支持读取提醒规则,不能用 task get/list 验证写入结果。", Operation: "todo.task.reminder", } } diff --git a/internal/shortcut/smart/remind.go b/internal/shortcut/smart/remind.go index 7addc82f..e4d31a40 100644 --- a/internal/shortcut/smart/remind.go +++ b/internal/shortcut/smart/remind.go @@ -21,7 +21,7 @@ import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" ) -// Remind: create a personal todo for YOURSELF with an optional due/reminder time, +// Remind: create a personal todo for YOURSELF with an optional due time, // in one command. It resolves the current user and explicitly passes executorIds; // the real todo backend does not reliably default a missing executor to "me". // @@ -35,13 +35,13 @@ var Remind = shortcut.Shortcut{ Service: "todo", Command: "+remind", Product: "todo", - Description: "给自己创建一条带截止/提醒时间的待办", - Intent: "当你想给自己记一件事、并(可选)设一个截止/提醒时间,又不想先查自己的 userId 时使用;" + - "内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 会按 ISO8601 解析为截止时间。会真实创建待办。", + Description: "给自己创建一条带可选截止时间的待办", + Intent: "当你想给自己记一件事、并(可选)设一个截止时间,又不想先查自己的 userId 时使用;" + + "内部先解析当前登录用户的 userId,再显式设置 executorIds,--at 只会按 ISO8601 写入截止时间 dueTime,不会创建独立提醒规则。会真实创建待办。", Risk: shortcut.RiskWrite, Flags: []shortcut.Flag{ {Name: "task", Type: shortcut.FlagString, Desc: "待办标题/内容", Required: true}, - {Name: "at", Type: shortcut.FlagString, Desc: "截止/提醒时间(ISO8601,可选,如 2026-03-10T18:00:00+08:00)"}, + {Name: "at", Type: shortcut.FlagString, Desc: "截止时间(ISO8601,可选,不是提醒时间;如 2026-03-10T18:00:00+08:00)"}, }, Tips: []string{`dws todo +remind --task "交周报" --at 2026-03-10T18:00:00+08:00`}, Execute: func(rt *shortcut.RuntimeContext) error { @@ -59,7 +59,7 @@ var Remind = shortcut.Shortcut{ "executorIds": []string{userID}, } - // Optional due/reminder time. The todo helper feeds --due through + // Optional due time. The todo helper feeds --due through // parseISOTimeToMillis and stores dueTime as epoch milliseconds (int64), // so we do the same here rather than passing a raw string. if rt.Changed("at") { diff --git a/internal/shortcut/smart/remind_contract_test.go b/internal/shortcut/smart/remind_contract_test.go new file mode 100644 index 00000000..bb4daf58 --- /dev/null +++ b/internal/shortcut/smart/remind_contract_test.go @@ -0,0 +1,20 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package smart + +import ( + "strings" + "testing" +) + +func TestRemindShortcutDoesNotAdvertiseDueTimeAsReminder(t *testing.T) { + if strings.Contains(Remind.Description, "提醒时间") || strings.Contains(Remind.Intent, "截止/提醒") { + t.Fatalf("shortcut contract still conflates dueTime with a reminder: %#v", Remind) + } + for _, flag := range Remind.Flags { + if flag.Name == "at" && !strings.Contains(flag.Desc, "不是提醒时间") { + t.Fatalf("--at description = %q, want explicit dueTime boundary", flag.Desc) + } + } +} diff --git a/skills/mono/references/intent-guide.md b/skills/mono/references/intent-guide.md index 5905571c..42e158c3 100644 --- a/skills/mono/references/intent-guide.md +++ b/skills/mono/references/intent-guide.md @@ -26,7 +26,7 @@ | "搜一下有没有叫XX的文件" | 全局搜索 | `drive search` | `wiki node search` | 未指定空间 → drive search 全局聚合搜索 | | "在知识库里创建一个文档" | 创建空文件实体 | `wiki node create --type adoc` | `doc create` | 空间内创建节点归 wiki;doc create 是向已有文档写入内容,不是创建文件节点 | | "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室);视频会议(conference)当前开源 CLI 不支持 | -| "明早 9 点提醒我提交周报" | 创建个人待办,但需先声明 reminder 边界 | `todo` | `calendar` | todo 当前只支持 dueTime 截止时间,不支持独立精确 reminder | +| "明早 9 点提醒我提交周报" | 创建个人待办后添加提醒 | `todo task create` + `todo task add-reminder` | `calendar` | `+remind --at` 只写截止时间;提醒可写入,但上游不支持规则读回 | | "帮我建一个项目群" | 创建群聊 | `chat group create` | — | 群聊管理,不是日历日程 | | "把张三拉进群" | 添加群成员 | `chat group members add` | — | 先查 userId,再添加 | | "通知群里的人都来开会" | 个人身份群发 | `chat message send` | `chat message send-by-bot` | 以个人身份向群发消息 | @@ -330,7 +330,7 @@ dws chat message send --open-dingtalk-id --msg-type file --file **用 `todo` 的场景**: - "记一下这周要做的事" — 个人任务管理 -- "创建一个待办提醒" — 仍归 `todo`,但要先说明当前只有 dueTime 截止时间,没有独立 reminder schedule +- "创建一个待办提醒" — 仍归 `todo`:先创建待办,再用 `task add-reminder` 写入;需说明上游不支持读取 reminderRules,不能写后读回核验 **判断关键**:钉钉日志系统(日报/周报模版,含按模版创建汇报)→ `report`;文档/知识库长文→ `doc`;任务清单→ `todo` diff --git a/skills/mono/references/products/todo.md b/skills/mono/references/products/todo.md index 3deb78a9..ae88bde3 100644 --- a/skills/mono/references/products/todo.md +++ b/skills/mono/references/products/todo.md @@ -457,9 +457,10 @@ dws todo task list-sub --task-id --format json - 优先级值: 10=低, 20=普通, 30=较高, 40=紧急 - `--due` 是截止时间 dueTime,不是提醒时间;使用 ISO-8601 格式(如 2026-03-10T18:00:00+08:00) -- 当前不支持单独的 `reminder` / `remind-at` 精确提醒能力;不要把 `--due` 解释成“几点提醒” +- `todo +remind --at` 同样只写截止时间 dueTime,不会创建独立提醒规则;不要把它解释成“几点提醒” - `--recurrence`:仅在与 `--due` 同时设置时有效;当前仅支持按天循环。字符串内需含换行,示例:`DTSTART:20260320T020000Z\nRRULE:FREQ=DAILY;INTERVAL=1`(DTSTART 表示首次截止时间,需与业务约定一致) -- 若用户的真实诉求是“到点提醒我”,需要先说明能力边界;当前 CLI 只能表达 deadline / recurrence,不能表达独立 reminder schedule +- 独立提醒可在待办创建后通过 `task add-reminder` 写入,或用 `task reset-reminder` 整体替换/清除 +- 当前上游没有提醒规则查询接口,`task get/list` 均不返回 `reminderRules`;写命令成功响应只能作为写入回执,不能声称已读回核验 - `task list` 的 `--status` 对应 MCP `get_user_todos_in_current_org` 的 `todoStatus` 参数 - `task list --query-all` 查询当前用户跨组织的全部待办;不传时保持当前组织范围 - todo 是个人待办管理产品 diff --git a/skills/multi/dingtalk-todo/SKILL.md b/skills/multi/dingtalk-todo/SKILL.md index 447ebe5a..33ce05cc 100644 --- a/skills/multi/dingtalk-todo/SKILL.md +++ b/skills/multi/dingtalk-todo/SKILL.md @@ -39,7 +39,7 @@ metadata: | `dws todo +list-comment` | read | 查询待办评论列表 | | `dws todo +list-sub` | read | 查询子待办列表 | | `dws todo +overdue` | read | 列出我已过期未完成的待办 | -| `dws todo +remind` | write | 给自己创建一条带截止/提醒时间的待办 | +| `dws todo +remind` | write | 给自己创建一条带可选截止时间的待办(`--at` 只写 dueTime,不创建提醒规则) | | `dws todo +todo-done` | write | 按标题关键词把我的某条待办标记完成(自动定位 taskId) | diff --git a/skills/multi/dingtalk-todo/references/todo.md b/skills/multi/dingtalk-todo/references/todo.md index 800a5ed3..8d36fab4 100644 --- a/skills/multi/dingtalk-todo/references/todo.md +++ b/skills/multi/dingtalk-todo/references/todo.md @@ -455,9 +455,10 @@ dws todo task list-sub --task-id --format json - 优先级值: 10=低, 20=普通, 30=较高, 40=紧急 - `--due` 是截止时间 dueTime,不是提醒时间;使用 ISO-8601 格式(如 2026-03-10T18:00:00+08:00) -- 当前不支持单独的 `reminder` / `remind-at` 精确提醒能力;不要把 `--due` 解释成“几点提醒” +- `todo +remind --at` 同样只写截止时间 dueTime,不会创建独立提醒规则;不要把它解释成“几点提醒” - `--recurrence`:仅在与 `--due` 同时设置时有效;当前仅支持按天循环。字符串内需含换行,示例:`DTSTART:20260320T020000Z\nRRULE:FREQ=DAILY;INTERVAL=1`(DTSTART 表示首次截止时间,需与业务约定一致) -- 若用户的真实诉求是“到点提醒我”,需要先说明能力边界;当前 CLI 只能表达 deadline / recurrence,不能表达独立 reminder schedule +- 独立提醒可在待办创建后通过 `task add-reminder` 写入,或用 `task reset-reminder` 整体替换/清除 +- 当前上游没有提醒规则查询接口,`task get/list` 均不返回 `reminderRules`;写命令成功响应只能作为写入回执,不能声称已读回核验 - `task list` 的 `--status` 对应 MCP `get_user_todos_in_current_org` 的 `todoStatus` 参数 - `task list --query-all` 查询当前用户跨组织的全部待办;不传时保持当前组织范围 - todo 是个人待办管理产品