Compare commits

...
Author SHA1 Message Date
修雨 bc332133a2 fix(ci): sync public interface baseline 2026-07-16 10:32:38 +08:00
修雨 e615bd433c fix: surface invalid sheet and todo targets (#623)
* fix: surface invalid sheet and todo targets

* docs: record invalid target fixes

* fix: expose todo attachment listing schema

* fix: make Windows helper coverage portable

* test: run quality regressions in platform coverage
2026-07-16 10:24:58 +08:00
修雨 474ce88d47 fix(ci): harden CLI smoke and Schema compatibility gates (#629)
* fix(ci): harden PR gate enforcement

* fix(schema): allow compatible positional evolution
2026-07-16 09:59:21 +08:00
24 changed files with 5058 additions and 944 deletions
+10 -3
View File
@@ -60,14 +60,21 @@ jobs:
return;
}
const protectedPaths = files
.map(({ filename }) => filename)
.filter((filename) =>
const isProtectedPath = (filename) =>
typeof filename === 'string' &&
(
filename.startsWith('.github/workflows/') ||
filename.startsWith('scripts/policy/') ||
filename.startsWith('scripts/release/') ||
filename === 'test/fixtures/cli-interface-baseline.txt' ||
filename === '.goreleaser.yaml' ||
filename === 'Makefile'
);
const protectedPaths = [...new Set(
files
.flatMap(({ filename, previous_filename }) => [filename, previous_filename])
.filter(isProtectedPath)
)];
if (protectedPaths.length > 0) {
await setStatus('failure', 'Modifies protected release/CI infrastructure');
+4
View File
@@ -12,6 +12,10 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
- **Sheet imports and Aitable workflow writes** (#624) — adds `dws sheet import` / `sheet import create` for converting local xlsx/xls files into new online sheets, `sheet import get` for polling import tasks, and `dws aitable workflow create/update` for applying validated `workflow-dsl/v1` definitions, with matching reviewed Agent Schema and bundled Skill guidance.
- **Official multi-platform Homebrew channel** — stable `Formula/dingtalk-workspace-cli.rb` and keg-only `Formula/dingtalk-workspace-cli-beta.rb` live in this repository and select signed macOS Intel/Apple Silicon or Linux amd64/arm64 artifacts at install time. Stable and beta releases open isolated Formula update PRs after final artifact signing, so beta never replaces the stable Formula. Agent Skills stay under `pkgshare` without mutating the user's home directory, and both tracks are covered by the six-channel post-release verifier.
### Fixed
- **Sheet and Todo invalid-target failures** — `sheet range read/get` now rejects a null cell-info response instead of printing `null` and exiting successfully, while Todo completion and attachment listing verify that a task exists before calling lenient backend endpoints. Attachment listing is also published through Runtime Schema for schema-first Agent discovery.
## [1.0.52] - 2026-07-14
This release seals the `v1.0.52` line with personal event subscriptions, a deterministic 22-product Agent command catalog, local user-operation auditing, expanded Open product commands, safer macOS credentials and release signing, and more reliable Connect and IM delivery.
+37 -4
View File
@@ -23,12 +23,15 @@ The repository defines five focused checks in addition to its existing CI:
variance to avoid failing unchanged code on test-path noise. Set
`COVERAGE_ENFORCE_OVERALL=true` once repository coverage reaches 80% to make
the overall target fail closed as well.
- **CLI Smoke** builds the release binary and renders offline help for the root
and every public top-level command.
- **CLI Smoke** builds the release binary, reads the root command list from the
structured Interface contract, and renders offline help for every public
top-level command. It rejects Cobra's unknown-command root-help fallback and
fails when the checked-in development fixture is stale.
- **Mock MCP Smoke** runs the existing HTTP and stdio MCP lifecycle tests
(`Initialize -> ListTools -> CallTool`).
- **AI Behavior Check** applies to pull requests labeled `ai-generated`. It
limits the change to 30 files and blocks release/CI infrastructure changes.
limits the change to 30 files and blocks release/CI infrastructure changes,
including policy implementations and the checked-in Interface fixture.
It uses `pull_request_target` without checking out PR code, so the policy
cannot be bypassed by changing the workflow in the same pull request. The
evaluator writes an `AI Behavior Check` commit status to the PR head SHA so
@@ -45,11 +48,41 @@ make authoritative-interface-integrity BASE_REF=<merge-base>
make schema-compatibility BASE_REF=<merge-base>
make skill-command-integrity
make cli-smoke
make coverage-gate BASE_REF=<merge-base>
# Run on the corresponding native runner with its generated profile:
make coverage-gate-platform BASE_REF=<merge-base> PROFILE=<coverage-profile>
```
`make coverage-gate` is the enforcement step, not a profile generator. It
expects the candidate, policy, and merge-base profiles (`coverage.txt`,
`coverage-policy.txt`, and `coverage-base.txt`) produced by the preceding CI
steps. A clean local checkout can reproduce the Linux/overall CI gate with:
```sh
base_ref=$(git merge-base HEAD origin/main)
root=$(pwd)
base_worktree=$(mktemp -d "${TMPDIR:-/tmp}/dws-coverage-base.XXXXXX")
rmdir "$base_worktree"
cleanup() { git worktree remove --force "$base_worktree" >/dev/null 2>&1 || true; }
trap cleanup EXIT HUP INT TERM
go test -count=1 -coverprofile=coverage.txt -covermode=atomic \
./ ./cmd/... ./internal/... ./skills/...
go test -count=1 -coverprofile=coverage-policy.txt -covermode=atomic \
./pkg/... ./scripts/policy/...
git worktree add --detach "$base_worktree" "$base_ref"
(
cd "$base_worktree"
go test -count=1 -coverprofile="$root/coverage-base.txt" -covermode=atomic \
./ ./cmd/... ./internal/... ./skills/...
)
make coverage-gate BASE_REF="$base_ref"
```
The native-platform target likewise expects `PROFILE` to have already been
generated on that operating system. CI owns those generation steps; copying
only either enforcement command into a clean checkout is intentionally an
incomplete invocation.
CI derives the authoritative Interface snapshots from both the PR merge-base
and the latest reachable stable release tag. The complete Schema snapshot comes
from the PR merge-base, which contains the registry-first Schema introduced on
@@ -0,0 +1,56 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package app
import (
"bytes"
"encoding/json"
"testing"
)
func TestTodoListAttachmentDeliveredSchemaMatchesExecutableHelp(t *testing.T) {
const (
canonicalPath = "todo.list_todo_attachment"
cliPath = "todo task list-attachment"
)
root := NewRootCommand()
command := exactCommandForTest(root, cliPath)
if command == nil {
t.Fatalf("executable command %q is missing", cliPath)
}
var stdout, stderr bytes.Buffer
root.SetOut(&stdout)
root.SetErr(&stderr)
root.SetArgs([]string{"schema", cliPath, "--format", "json"})
if err := root.Execute(); err != nil {
t.Fatalf("execute embedded schema leaf: %v; stderr=%s", err, stderr.String())
}
var tool map[string]any
if err := json.Unmarshal(stdout.Bytes(), &tool); err != nil {
t.Fatalf("decode embedded schema leaf: %v", err)
}
if got := schemaContractString(tool["canonical_path"]); got != canonicalPath {
t.Fatalf("canonical_path = %q, want %q", got, canonicalPath)
}
if got := schemaContractString(tool["primary_cli_path"]); got != cliPath {
t.Fatalf("primary_cli_path = %q, want %q", got, cliPath)
}
if got := schemaContractString(tool["availability"]); got != "available" {
t.Fatalf("availability = %q, want available", got)
}
if problem := schemaHelpFlagCompletenessProblem(canonicalPath, cliPath, command, tool); problem != "" {
t.Fatal(problem)
}
taskID := schemaContractMap(tool["parameters"])["task-id"]
if taskID == nil {
t.Fatal("delivered Schema is missing --task-id")
}
if required, ok := taskID["required"].(bool); !ok || !required {
t.Fatalf("task-id required = %#v, want true", taskID["required"])
}
}
+10 -10
View File
@@ -1,18 +1,18 @@
{
"version": 1,
"source_hash": "sha256:53567141622d90e02ce50fe3bc1733357a25b9860a0795eb396d9361f43c41d1",
"surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb",
"source_hash": "sha256:d398f66780f4959874c4ffc48be5be08c716ba2daeed8ae3d8690504846a3906",
"surface_hash": "sha256:631b73b174f48cb0cca7c8449e80920739010d0db223dac2855101f4dbd0eda6",
"coverage": {
"surface_products": 22,
"products_with_metadata": 22,
"surface_tools": 568,
"tools_with_metadata": 568,
"tools_with_agent_summary": 568,
"tools_with_use_when": 568,
"tools_with_avoid_when": 568,
"tools_with_examples": 568,
"tools_with_interface_mode": 568,
"unmatched_skill_tools": 125,
"surface_tools": 569,
"tools_with_metadata": 569,
"tools_with_agent_summary": 569,
"tools_with_use_when": 569,
"tools_with_avoid_when": 569,
"tools_with_examples": 569,
"tools_with_interface_mode": 569,
"unmatched_skill_tools": 124,
"unreviewed_skill_tools": 7
},
"products": {
@@ -3439,6 +3439,265 @@
"需要按完成状态、优先级、角色或截止日期范围查询当前用户待办列表时"
]
},
"todo task list-attachment": {
"agent_summary": "查询待办附件列表",
"agent_summary_source": "dws-agent-selection/todo",
"availability": "available",
"avoid_when": [
"要上传附件时改用 dws todo task add-attachment",
"要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment"
],
"confirmation": "not_required",
"effect": "read",
"effect_source": "command-verb",
"examples": [
"dws todo task list-attachment --task-id \u003ctaskId\u003e",
"dws todo task list-attachment --task-id \u003ctaskId\u003e --format json"
],
"field_provenance": {
"agent_summary": {
"value": "查询待办附件列表",
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"candidates": [
{
"value": "查询待办附件列表",
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。"
}
]
},
"availability": {
"value": "available",
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"candidates": [
{
"value": "available",
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed"
}
]
},
"avoid_when": {
"value": [
"要上传附件时改用 dws todo task add-attachment",
"要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"candidates": [
{
"value": [
"要上传附件时改用 dws todo task add-attachment",
"要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。"
}
]
},
"confirmation": {
"value": "not_required",
"source": "risk-default",
"precedence": "inference_or_default",
"resolution": "highest_precedence",
"candidates": [
{
"value": "not_required",
"source": "risk-default",
"precedence": "inference_or_default",
"selected": true
}
]
},
"effect": {
"value": "read",
"source": "command-verb",
"precedence": "inference_or_default",
"resolution": "highest_precedence",
"candidates": [
{
"value": "read",
"source": "command-verb",
"precedence": "inference_or_default",
"selected": true
}
]
},
"examples": {
"value": [
"dws todo task list-attachment --task-id \u003ctaskId\u003e",
"dws todo task list-attachment --task-id \u003ctaskId\u003e --format json"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"candidates": [
{
"value": [
"dws todo task list-attachment --task-id \u003ctaskId\u003e",
"dws todo task list-attachment --task-id \u003ctaskId\u003e --format json"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。"
}
]
},
"idempotency": {
"value": "idempotent",
"source": "effect-default",
"precedence": "inference_or_default",
"resolution": "highest_precedence",
"candidates": [
{
"value": "idempotent",
"source": "effect-default",
"precedence": "inference_or_default",
"selected": true
}
]
},
"interface_mode": {
"value": "composite",
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"candidates": [
{
"value": "composite",
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed"
}
]
},
"interface_reason": {
"value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"candidates": [
{
"value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed"
}
]
},
"interface_ref": {
"value": null,
"source": "internal/cli/schema_hints/metadata/todo.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/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "final interface mode composite forbids a direct MCP interface_ref"
}
]
},
"reviewed": {
"value": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"candidates": [
{
"value": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed"
},
{
"value": true,
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"selected": false,
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。"
}
]
},
"risk": {
"value": "low",
"source": "effect-default",
"precedence": "inference_or_default",
"resolution": "highest_precedence",
"candidates": [
{
"value": "low",
"source": "effect-default",
"precedence": "inference_or_default",
"selected": true
}
]
},
"use_when": {
"value": [
"已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"candidates": [
{
"value": [
"已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时"
],
"source": "internal/cli/schema_hints/selection/todo.json",
"precedence": "reviewed_explicit",
"selected": true,
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。"
}
]
}
},
"idempotency": "idempotent",
"interface_mode": "composite",
"interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
"reviewed": true,
"risk": "low",
"source_refs": [
"CommandRegistry:canonical_path=todo.list_todo_attachment",
"Skill:skills/mono/references/products/todo.md",
"cobra-help:dws todo task list-attachment",
"internal/cli/schema_hints/metadata/todo.json",
"internal/cli/schema_hints/selection/todo.json",
"live-dws-schema:todo.list_todo_attachment#FAILED",
"runtime-call:internal/helpers/todo.go#list_todo_attachment",
"skills/mono/references/products/todo.md"
],
"use_when": [
"已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时"
]
},
"todo task remove-executor": {
"agent_summary": "移除待办执行人",
"agent_summary_source": "dws-agent-selection/todo",
+11 -39
View File
@@ -1,11 +1,11 @@
{
"version": 1,
"source_hash": "sha256:53567141622d90e02ce50fe3bc1733357a25b9860a0795eb396d9361f43c41d1",
"surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb",
"source_hash": "sha256:d398f66780f4959874c4ffc48be5be08c716ba2daeed8ae3d8690504846a3906",
"surface_hash": "sha256:631b73b174f48cb0cca7c8449e80920739010d0db223dac2855101f4dbd0eda6",
"source_files": 150,
"hint_files": 46,
"hint_products": 39,
"hint_tools": 1288,
"hint_tools": 1290,
"interface_metadata": {
"source": "mcp-tools-list+cli-registry",
"revision": "4574f7022c32cf4c033e9b7b4156e2fec815fed8",
@@ -29,14 +29,14 @@
"coverage": {
"surface_products": 22,
"products_with_metadata": 22,
"surface_tools": 568,
"tools_with_metadata": 568,
"tools_with_agent_summary": 568,
"tools_with_use_when": 568,
"tools_with_avoid_when": 568,
"tools_with_examples": 568,
"tools_with_interface_mode": 568,
"unmatched_skill_tools": 125,
"surface_tools": 569,
"tools_with_metadata": 569,
"tools_with_agent_summary": 569,
"tools_with_use_when": 569,
"tools_with_avoid_when": 569,
"tools_with_examples": 569,
"tools_with_interface_mode": 569,
"unmatched_skill_tools": 124,
"unreviewed_skill_tools": 7
},
"source_products": [
@@ -3944,20 +3944,6 @@
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
}
},
{
"tool_path": "todo task list-attachment",
"source": "skills/mono/references/products/todo.md",
"line": 216,
"candidates": [
"todo task add-attachment",
"todo task add-executor",
"todo task add-participant"
],
"review": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
}
},
{
"tool_path": "todo task remove-attachment",
"source": "skills/mono/references/products/todo.md",
@@ -3986,20 +3972,6 @@
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
}
},
{
"tool_path": "todo task list-attachment",
"source": "skills/mono/references/products/todo.md",
"line": 375,
"candidates": [
"todo task add-attachment",
"todo task add-executor",
"todo task add-participant"
],
"review": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
}
},
{
"tool_path": "todo task remove-attachment",
"source": "skills/mono/references/products/todo.md",
+456 -10
View File
@@ -1,18 +1,18 @@
{
"version": 1,
"source_hash": "sha256:9a907efbcea3339bcb3881aad578e77b15f4a3f4a8b53efe4c2791db894bb41a",
"surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb",
"source_hash": "sha256:8ad3b1c08185ecdae8bb7539017fd5b57ebb085508c8326149e241d95239af4a",
"surface_hash": "sha256:631b73b174f48cb0cca7c8449e80920739010d0db223dac2855101f4dbd0eda6",
"catalog": {
"agent_metadata": {
"products_with_metadata": 22,
"source": "embedded-skill-metadata",
"source_hash": "sha256:53567141622d90e02ce50fe3bc1733357a25b9860a0795eb396d9361f43c41d1",
"surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb",
"source_hash": "sha256:d398f66780f4959874c4ffc48be5be08c716ba2daeed8ae3d8690504846a3906",
"surface_hash": "sha256:631b73b174f48cb0cca7c8449e80920739010d0db223dac2855101f4dbd0eda6",
"surface_products": 22,
"surface_tools": 568,
"tools_with_agent_summary": 568,
"tools_with_metadata": 568,
"unmatched_skill_tools": 125,
"surface_tools": 569,
"tools_with_agent_summary": 569,
"tools_with_metadata": 569,
"unmatched_skill_tools": 124,
"version": 1
},
"count": 22,
@@ -17936,7 +17936,7 @@
"id": "todo",
"name": "待办任务管理",
"runtime": true,
"tool_count": 17,
"tool_count": 18,
"tools": [
{
"agent_metadata_source": "embedded-skill-metadata",
@@ -18294,6 +18294,34 @@
"需要按完成状态、优先级、角色或截止日期范围查询当前用户待办列表时"
]
},
{
"agent_metadata_source": "embedded-skill-metadata",
"agent_summary": "查询待办附件列表",
"agent_summary_source": "dws-agent-selection/todo",
"availability": "available",
"avoid_when": [
"要上传附件时改用 dws todo task add-attachment",
"要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment"
],
"canonical_path": "todo.list_todo_attachment",
"cli_name": "list-attachment",
"cli_path": "todo task list-attachment",
"confirmation": "not_required",
"description": "查询待办任务的附件列表",
"effect": "read",
"group": "task",
"idempotency": "idempotent",
"interface_mode": "composite",
"interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
"name": "list_todo_attachment",
"primary_cli_path": "todo task list-attachment",
"reviewed": true,
"risk": "low",
"title": "查询待办任务的附件列表",
"use_when": [
"已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时"
]
},
{
"agent_metadata_source": "embedded-skill-metadata",
"agent_summary": "获取待办评论列表",
@@ -19042,7 +19070,7 @@
}
],
"source": "embedded-command-catalog",
"tool_count": 568
"tool_count": 569
},
"tools": {
"aisearch.enterprise_person_search": {
@@ -424437,6 +424465,424 @@
"需要按完成状态、优先级、角色或截止日期范围查询当前用户待办列表时"
]
},
"todo.list_todo_attachment": {
"agent_metadata_source": "embedded-skill-metadata",
"agent_source_refs": [
"CommandRegistry:canonical_path=todo.list_todo_attachment",
"Skill:skills/mono/references/products/todo.md",
"cobra-help:dws todo task list-attachment",
"internal/cli/schema_hints/metadata/todo.json",
"internal/cli/schema_hints/selection/todo.json",
"live-dws-schema:todo.list_todo_attachment#FAILED",
"runtime-call:internal/helpers/todo.go#list_todo_attachment",
"skills/mono/references/products/todo.md"
],
"agent_summary": "查询待办附件列表",
"agent_summary_source": "dws-agent-selection/todo",
"availability": "available",
"avoid_when": [
"要上传附件时改用 dws todo task add-attachment",
"要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment"
],
"canonical_path": "todo.list_todo_attachment",
"cli_name": "list-attachment",
"cli_path": "todo task list-attachment",
"confirmation": "not_required",
"description": "查询待办任务的附件列表",
"display": "待办任务管理",
"effect": "read",
"effect_source": "command-verb",
"examples": [
"dws todo task list-attachment --task-id \u003ctaskId\u003e",
"dws todo task list-attachment --task-id \u003ctaskId\u003e --format json"
],
"field_provenance": {
"agent_summary": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"selected": true,
"source": "internal/cli/schema_hints/selection/todo.json",
"value": "查询待办附件列表"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"source": "internal/cli/schema_hints/selection/todo.json",
"value": "查询待办附件列表"
},
"availability": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"selected": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "available"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "available"
},
"avoid_when": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"selected": true,
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"要上传附件时改用 dws todo task add-attachment",
"要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment"
]
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"要上传附件时改用 dws todo task add-attachment",
"要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment"
]
},
"canonical_path": {
"candidates": [
{
"precedence": "command_registry",
"selected": true,
"source": "reviewed_command_registry",
"source_ref": "todo task list-attachment",
"value": "todo.list_todo_attachment"
}
],
"precedence": "command_registry",
"resolution": "registry_identity",
"source": "reviewed_command_registry",
"source_ref": "todo task list-attachment",
"value": "todo.list_todo_attachment"
},
"confirmation": {
"candidates": [
{
"precedence": "inference_or_default",
"selected": true,
"source": "risk-default",
"value": "not_required"
}
],
"precedence": "inference_or_default",
"resolution": "highest_precedence",
"source": "risk-default",
"value": "not_required"
},
"description": {
"candidates": [
{
"precedence": "cobra_help",
"selected": true,
"source": "cobra_help",
"value": "查询待办任务的附件列表"
}
],
"precedence": "cobra_help",
"resolution": "highest_precedence",
"source": "cobra_help",
"value": "查询待办任务的附件列表"
},
"effect": {
"candidates": [
{
"precedence": "inference_or_default",
"selected": true,
"source": "command-verb",
"value": "read"
}
],
"precedence": "inference_or_default",
"resolution": "highest_precedence",
"source": "command-verb",
"value": "read"
},
"examples": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"selected": true,
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"dws todo task list-attachment --task-id \u003ctaskId\u003e",
"dws todo task list-attachment --task-id \u003ctaskId\u003e --format json"
]
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"dws todo task list-attachment --task-id \u003ctaskId\u003e",
"dws todo task list-attachment --task-id \u003ctaskId\u003e --format json"
]
},
"idempotency": {
"candidates": [
{
"precedence": "inference_or_default",
"selected": true,
"source": "effect-default",
"value": "idempotent"
}
],
"precedence": "inference_or_default",
"resolution": "highest_precedence",
"source": "effect-default",
"value": "idempotent"
},
"interface_mode": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"selected": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "composite"
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "composite"
},
"interface_reason": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"selected": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command."
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command."
},
"interface_ref": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "final interface mode composite forbids a direct MCP interface_ref",
"selected": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": null
}
],
"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/todo.json",
"value": null
},
"reviewed": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"selected": true,
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": true
},
{
"precedence": "reviewed_explicit",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"selected": false,
"source": "internal/cli/schema_hints/selection/todo.json",
"value": true
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "dws-tool-metadata/todo marks this tool as reviewed",
"source": "internal/cli/schema_hints/metadata/todo.json",
"value": true
},
"risk": {
"candidates": [
{
"precedence": "inference_or_default",
"selected": true,
"source": "effect-default",
"value": "low"
}
],
"precedence": "inference_or_default",
"resolution": "highest_precedence",
"source": "effect-default",
"value": "low"
},
"title": {
"candidates": [
{
"precedence": "cobra_help",
"selected": true,
"source": "cobra_help",
"value": "查询待办任务的附件列表"
}
],
"precedence": "cobra_help",
"resolution": "highest_precedence",
"source": "cobra_help",
"value": "查询待办任务的附件列表"
},
"use_when": {
"candidates": [
{
"precedence": "reviewed_explicit",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"selected": true,
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时"
]
}
],
"precedence": "reviewed_explicit",
"resolution": "highest_precedence",
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"source": "internal/cli/schema_hints/selection/todo.json",
"value": [
"已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时"
]
}
},
"group": "task",
"has_parameters": true,
"idempotency": "idempotent",
"interface_mode": "composite",
"interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
"is_alias": false,
"name": "list_todo_attachment",
"parameter_count": 1,
"parameters": {
"task-id": {
"description": "待办任务 ID (必填)",
"field_provenance": {
"description": {
"candidates": [
{
"precedence": "cobra_contract",
"selected": true,
"source": "cobra_usage",
"value": "待办任务 ID (必填)"
},
{
"precedence": "default",
"selected": false,
"source": "default",
"value": ""
}
],
"precedence": "cobra_contract",
"resolution": "highest_precedence",
"source": "cobra_usage",
"value": "待办任务 ID (必填)"
},
"property": {
"candidates": [
{
"precedence": "reviewed_mapping_exclusion",
"review_reason": "Reviewed unpinned adapter: --task-id is nested under todoAttachmentListRequest at runtime, while the immutable pinned MCP snapshot has no interface_ref for todo.list_todo_attachment.",
"selected": true,
"source": "reviewed_mapping_exclusion",
"value": ""
},
{
"precedence": "inference",
"selected": false,
"source": "flag_name_inference",
"value": "taskId"
}
],
"precedence": "reviewed_mapping_exclusion",
"resolution": "highest_precedence",
"review_reason": "Reviewed unpinned adapter: --task-id is nested under todoAttachmentListRequest at runtime, while the immutable pinned MCP snapshot has no interface_ref for todo.list_todo_attachment.",
"source": "reviewed_mapping_exclusion",
"value": ""
},
"required": {
"candidates": [
{
"precedence": "inference",
"selected": true,
"source": "usage_required_inference",
"value": true
},
{
"precedence": "default",
"selected": false,
"source": "default",
"value": false
}
],
"precedence": "inference",
"resolution": "highest_precedence",
"source": "usage_required_inference",
"value": true
},
"required_when": {
"candidates": [
{
"precedence": "default",
"selected": true,
"source": "default",
"value": ""
}
],
"precedence": "default",
"resolution": "highest_precedence",
"source": "default",
"value": ""
},
"type": {
"candidates": [
{
"precedence": "cobra_contract",
"selected": true,
"source": "cobra_flag_type",
"value": "string"
}
],
"precedence": "cobra_contract",
"resolution": "highest_precedence",
"source": "cobra_flag_type",
"value": "string"
}
},
"required": true,
"type": "string"
}
},
"path": "todo.list_todo_attachment",
"primary_cli_path": "todo task list-attachment",
"product_id": "todo",
"reviewed": true,
"risk": "low",
"source": "reviewed_command_registry",
"title": "查询待办任务的附件列表",
"use_when": [
"已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时"
]
},
"todo.list_todo_comment": {
"agent_metadata_source": "embedded-skill-metadata",
"agent_source_refs": [
@@ -369,7 +369,6 @@
"oa approval revert-activities",
"oa approval revert-task",
"oa approval search-forms",
"todo task list-attachment",
"todo task list-sub",
"todo task remove-attachment"
]
@@ -2293,6 +2293,10 @@
"canonical_path": "todo.add_todo_attachment",
"cli_path": "todo task add-attachment"
},
{
"canonical_path": "todo.list_todo_attachment",
"cli_path": "todo task list-attachment"
},
{
"canonical_path": "todo.add_task_executors",
"cli_path": "todo task add-executor"
-4
View File
@@ -876,10 +876,6 @@
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
},
"todo task list-attachment": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
},
"todo task list-sub": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
@@ -76,6 +76,12 @@
"availability": "available",
"reviewed": true
},
"todo.list_todo_attachment": {
"interface_mode": "composite",
"availability": "available",
"interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
"reviewed": true
},
"todo.remove_task_executors": {
"effect": "write",
"risk": "medium",
@@ -825,10 +825,6 @@
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
},
"todo task list-attachment": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
},
"todo task list-sub": {
"status": "stale",
"reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射"
@@ -7,7 +7,7 @@
"channel": "open-source"
},
"coverage": {
"source_tools": 568,
"source_tools": 569,
"matched_tools": 71
},
"tools": {
@@ -286,6 +286,29 @@
"live-dws-schema:todo.list_todo_comment"
]
},
"todo.list_todo_attachment": {
"agent_summary": "查询待办附件列表",
"use_when": [
"已知 taskId,需要查看该待办当前附件及其 attachmentId、文件名和大小时"
],
"avoid_when": [
"要上传附件时改用 dws todo task add-attachment",
"要删除附件时先用本命令确认 attachmentId,再改用 dws todo task remove-attachment"
],
"examples": [
"dws todo task list-attachment --task-id <taskId>",
"dws todo task list-attachment --task-id <taskId> --format json"
],
"reviewed": true,
"review_reason": "依据当前 Cobra Help、Todo Skill 与 helper 中的 list_todo_attachment 调用手工审阅;当前 live leaf Schema 缺失,记录失败证据并以源码契约收口。",
"source_refs": [
"CommandRegistry:canonical_path=todo.list_todo_attachment",
"cobra-help:dws todo task list-attachment",
"Skill:skills/mono/references/products/todo.md",
"runtime-call:internal/helpers/todo.go#list_todo_attachment",
"live-dws-schema:todo.list_todo_attachment#FAILED"
]
},
"todo.remove_task_executors": {
"agent_summary": "移除待办执行人",
"use_when": [
@@ -1874,6 +1874,7 @@
"sheet.write_image --mime-type": "local upload metadata",
"sheet.write_image --name": "local upload metadata",
"todo.add_todo_attachment --file-path": "local upload input used to construct attachmentList",
"todo.list_todo_attachment --task-id": "Reviewed unpinned adapter: --task-id is nested under todoAttachmentListRequest at runtime, while the immutable pinned MCP snapshot has no interface_ref for todo.list_todo_attachment.",
"wiki.create_wikiSpace --icon": "runtime extension sends icon, which is absent from the pinned create_wikiSpace metadata",
"wiki.delete_document --workspace": "local validation/authorization context; not sent to delete_document",
"wiki.list_wikiSpaces --cursor": "composite route maps to pageToken for wiki.list_wikiSpaces or nextToken for drive.list_spaces",
@@ -0,0 +1,158 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package helpers
import (
"context"
"errors"
"io"
"os"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
"github.com/spf13/cobra"
)
type qualityEvaluationCall struct {
productID string
toolName string
args map[string]any
}
type qualityEvaluationCaller struct {
calls []qualityEvaluationCall
dryRun bool
}
func (c *qualityEvaluationCaller) CallTool(_ context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) {
c.calls = append(c.calls, qualityEvaluationCall{productID: productID, toolName: toolName, args: args})
switch toolName {
case "get_cell_infos":
return qualityEvaluationTextResult("null"), nil
case "get_todo_detail":
taskID, _ := args["taskId"].(string)
if taskID == "INVALID" {
return nil, errors.New("task not found")
}
return qualityEvaluationTextResult(`{"success":true,"result":{"todoDetailModel":{"taskId":"` + taskID + `"}}}`), nil
case "update_todo_done_status":
return qualityEvaluationTextResult(`{"success":true}`), nil
case "list_todo_attachment":
return qualityEvaluationTextResult(`{"success":true,"attachments":[]}`), nil
default:
return qualityEvaluationTextResult(`{}`), nil
}
}
func (*qualityEvaluationCaller) Format() string { return "json" }
func (c *qualityEvaluationCaller) DryRun() bool { return c.dryRun }
func (*qualityEvaluationCaller) Fields() string { return "" }
func (*qualityEvaluationCaller) JQ() string { return "" }
func qualityEvaluationTextResult(text string) *edition.ToolResult {
return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: text}}}
}
func executeQualityEvaluationCommand(t *testing.T, product string, caller *qualityEvaluationCaller, cmd *cobra.Command, args ...string) error {
t.Helper()
previousDeps := deps
previousArgs := os.Args
t.Cleanup(func() {
deps = previousDeps
os.Args = previousArgs
})
InitDeps(caller)
deps.Out.w = io.Discard
os.Args = append([]string{"dws", product}, args...)
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetArgs(args)
return cmd.Execute()
}
func TestCrossPlatformCoverageSheetRangeReadRejectsNullToolResponse(t *testing.T) {
for _, command := range []string{"read", "get"} {
t.Run(command, func(t *testing.T) {
caller := &qualityEvaluationCaller{}
err := executeQualityEvaluationCommand(t, "sheet", caller, newSheetCommand(),
"range", command, "--node", "INVALID")
if err == nil {
t.Fatal("sheet range command accepted a null MCP response")
}
if len(caller.calls) != 1 || caller.calls[0].toolName != "get_cell_infos" {
t.Fatalf("calls = %#v, want one get_cell_infos call", caller.calls)
}
})
}
}
func TestCrossPlatformCoverageTodoCommandsRejectMissingTaskBeforeTargetCall(t *testing.T) {
tests := []struct {
name string
args []string
targetTool string
}{
{name: "done", args: []string{"task", "done", "--task-id", "INVALID", "--status", "true"}, targetTool: "update_todo_done_status"},
{name: "list attachment", args: []string{"task", "list-attachment", "--task-id", "INVALID"}, targetTool: "list_todo_attachment"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
caller := &qualityEvaluationCaller{}
err := executeQualityEvaluationCommand(t, "todo", caller, newTodoCommand(), tt.args...)
if err == nil {
t.Fatal("todo command accepted a missing task")
}
if len(caller.calls) != 1 || caller.calls[0].toolName != "get_todo_detail" {
t.Fatalf("calls = %#v, want only get_todo_detail preflight before %s", caller.calls, tt.targetTool)
}
})
}
}
func TestCrossPlatformCoverageTodoCommandsPreflightExistingTask(t *testing.T) {
tests := []struct {
name string
args []string
targetTool string
}{
{name: "done", args: []string{"task", "done", "--task-id", "12345", "--status", "true"}, targetTool: "update_todo_done_status"},
{name: "list attachment", args: []string{"task", "list-attachment", "--task-id", "12345"}, targetTool: "list_todo_attachment"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
caller := &qualityEvaluationCaller{}
if err := executeQualityEvaluationCommand(t, "todo", caller, newTodoCommand(), tt.args...); err != nil {
t.Fatalf("todo command returned error: %v", err)
}
if len(caller.calls) != 2 || caller.calls[0].toolName != "get_todo_detail" || caller.calls[1].toolName != tt.targetTool {
t.Fatalf("calls = %#v, want get_todo_detail then %s", caller.calls, tt.targetTool)
}
})
}
}
func TestCrossPlatformCoverageTodoTaskPreflightIsSkippedForDryRun(t *testing.T) {
tests := []struct {
name string
args []string
}{
{name: "done", args: []string{"task", "done", "--task-id", "12345", "--status", "true"}},
{name: "list attachment", args: []string{"task", "list-attachment", "--task-id", "12345"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
caller := &qualityEvaluationCaller{dryRun: true}
if err := executeQualityEvaluationCommand(t, "todo", caller, newTodoCommand(), tt.args...); err != nil {
t.Fatalf("todo dry-run returned error: %v", err)
}
if len(caller.calls) != 0 {
t.Fatalf("dry-run made remote calls: %#v", caller.calls)
}
})
}
}
+7
View File
@@ -146,6 +146,13 @@ func callMCPToolCellInfos(toolArgs map[string]any) error {
deps.Out.PrintRaw(text)
return nil
}
if parsed == nil {
return &CLIError{
Code: CodeMCPToolError,
Message: "get_cell_infos returned a null response",
Suggestion: "请确认 --node 指向当前用户可访问的钉钉在线表格",
}
}
// 清理空 dataValidation / hyperlink
if cells, ok := parsed["cells"].([]any); ok {
for _, row := range cells {
+41 -3
View File
@@ -22,6 +22,33 @@ import (
const todoListPageSizeMax = 20
func ensureTodoTaskExists(ctx context.Context, taskID string) error {
text, err := callMCPToolReturnTextOnServer(ctx, "todo", "get_todo_detail", map[string]any{
"taskId": taskID,
})
if err != nil {
return fmt.Errorf("待办任务 %q 不存在或不可访问: %w", taskID, err)
}
var body map[string]any
if err := json.Unmarshal([]byte(text), &body); err != nil {
return fmt.Errorf("校验待办任务 %q 时无法解析详情响应: %w", taskID, err)
}
result, ok := body["result"].(map[string]any)
if !ok {
return fmt.Errorf("待办任务 %q 不存在或详情响应缺少 result", taskID)
}
detail, ok := result["todoDetailModel"].(map[string]any)
if !ok {
return fmt.Errorf("待办任务 %q 不存在或详情响应缺少 todoDetailModel", taskID)
}
returnedTaskID := stringFromJSONScalar(detail["taskId"])
if returnedTaskID == "" || returnedTaskID != taskID {
return fmt.Errorf("待办任务 %q 不存在或详情响应中的 taskId 不匹配", taskID)
}
return nil
}
func newTodoCommand() *cobra.Command {
todoCmd := &cobra.Command{
Use: "todo",
@@ -231,8 +258,14 @@ func newTodoCommand() *cobra.Command {
if err := validateRequiredFlags(cmd, "task-id", "status"); err != nil {
return err
}
taskID := mustGetFlag(cmd, "task-id")
if !deps.Caller.DryRun() {
if err := ensureTodoTaskExists(cmd.Context(), taskID); err != nil {
return err
}
}
return callMCPTool("update_todo_done_status", map[string]any{
"taskId": mustGetFlag(cmd, "task-id"),
"taskId": taskID,
"isDone": mustGetFlag(cmd, "status"),
})
},
@@ -535,10 +568,15 @@ func newTodoCommand() *cobra.Command {
if err := validateRequiredFlags(cmd, "task-id"); err != nil {
return err
}
taskId := mustGetFlag(cmd, "task-id")
taskID := mustGetFlag(cmd, "task-id")
if !deps.Caller.DryRun() {
if err := ensureTodoTaskExists(cmd.Context(), taskID); err != nil {
return err
}
}
return callMCPTool("list_todo_attachment", map[string]any{
"todoAttachmentListRequest": map[string]any{
"taskId": taskId,
"taskId": taskID,
},
})
},
+83 -9
View File
@@ -19,30 +19,104 @@ if [ ! -f "$BASELINE" ]; then
fi
SNAPSHOT_HOME="$(mktemp -d)"
trap 'rm -rf "$SNAPSHOT_HOME"' EXIT
SNAPSHOT_BIN="$(mktemp)"
CURRENT="$(mktemp)"
trap 'rm -rf "$CURRENT" "$SNAPSHOT_HOME" "$SNAPSHOT_BIN"' EXIT
cd "$ROOT"
go build -o "$SNAPSHOT_BIN" ./scripts/policy/interface-baseline
HOME="$SNAPSHOT_HOME" DWS_DISABLE_KEYCHAIN=1 DWS_LANG=zh "$SNAPSHOT_BIN" >"$CURRENT"
root_commands() {
awk '
/^\[root\]$/ {
in_root = 1
next
}
/^\[/ {
if (in_root) {
exit
}
next
}
in_root {
line = $0
sub(/^[[:space:]]*/, "", line)
if (line ~ /^commands:[[:space:]]*/) {
sub(/^commands:[[:space:]]*/, "", line)
gsub(/,[[:space:]]*/, " ", line)
print line
found = 1
exit
}
}
END {
if (!found) {
exit 1
}
}
' "$1"
}
baseline_commands="$(root_commands "$BASELINE")" || {
printf 'error: no root commands found in interface baseline\n' >&2
exit 1
}
current_commands="$(root_commands "$CURRENT")" || {
printf 'error: no root commands found in current interface contract\n' >&2
exit 1
}
if [ "$baseline_commands" != "$current_commands" ]; then
printf 'error: interface baseline root commands are stale (run make update-interface-baseline)\n' >&2
printf ' baseline: %s\n current: %s\n' "$baseline_commands" "$current_commands" >&2
exit 1
fi
# DWS_DISABLE_KEYCHAIN=1 routes the DEK to a file (the Linux scheme) instead of
# the macOS system Keychain. Without it, running the binary under this fresh
# HOME triggers a GUI Keychain authorization prompt that blocks the
# non-interactive smoke test indefinitely. Linux CI already uses the file DEK.
run_help() {
capture_help() {
HOME="$SNAPSHOT_HOME" DWS_DISABLE_KEYCHAIN=1 DWS_LANG=zh "$BIN" "$@" --help 2>&1
}
root_help="$(capture_help)" || {
printf 'error: dws --help exited non-zero\n%s\n' "$root_help" >&2
exit 1
}
run_command_help() {
output="$(HOME="$SNAPSHOT_HOME" DWS_DISABLE_KEYCHAIN=1 DWS_LANG=zh "$BIN" "$@" --help 2>&1)" || {
printf 'error: dws %s --help exited non-zero\n%s\n' "$*" "$output" >&2
return 1
}
if [ "$output" = "$root_help" ]; then
printf 'error: dws %s --help resolved to root help instead of command-specific help\n' "$*" >&2
return 1
fi
usage_line="$(printf '%s\n' "$output" | awk '/^Usage:$/ { getline; print; exit }')"
expected_usage=" dws $*"
case "$usage_line" in
"$expected_usage"|"$expected_usage "*) ;;
*)
printf 'error: dws %s --help reported unexpected usage identity: %s\n' "$*" "$usage_line" >&2
return 1
;;
esac
}
run_help
command_line="$(sed -n '/^\[root\]$/{n;s/^ commands: //;p;q;}' "$BASELINE")"
if [ -z "$command_line" ]; then
printf 'error: no root commands found in interface baseline\n' >&2
# Cobra currently treats an unknown command followed by --help as root help
# with exit status 0. Keep a negative case here so exit status alone can never
# make the smoke check pass for a command that did not resolve.
unknown_command="__dws_cli_smoke_unknown_command__"
if run_command_help "$unknown_command" >/dev/null 2>&1; then
printf 'error: unknown command %s unexpectedly produced command-specific help\n' "$unknown_command" >&2
exit 1
fi
count=0
for command in $(printf '%s\n' "$command_line" | tr -d ','); do
run_help "$command"
for command in $current_commands; do
run_command_help "$command"
count=$((count + 1))
done
+32 -5
View File
@@ -491,7 +491,7 @@ func checkToolCompatibility(toolPath string, oldTool, newTool toolSchema) []stri
failures = append(failures, fmt.Sprintf("schema tool %q changed %s", toolPath, field.name))
}
}
if !equalPositionals(oldTool.Positionals, newTool.Positionals) {
if !compatiblePositionals(oldTool.Positionals, newTool.Positionals) {
failures = append(failures, fmt.Sprintf("schema tool %q changed positionals", toolPath))
}
if oldTool.DryRun != "" && oldTool.DryRun != newTool.DryRun {
@@ -510,12 +510,39 @@ func checkToolCompatibility(toolPath string, oldTool, newTool toolSchema) []stri
return failures
}
func equalPositionals(oldPositionals, newPositionals []positionalSchema) bool {
if len(oldPositionals) != len(newPositionals) {
func compatiblePositionals(oldPositionals, newPositionals []positionalSchema) bool {
if len(newPositionals) < len(oldPositionals) {
return false
}
for index := range oldPositionals {
if oldPositionals[index] != newPositionals[index] {
for index, oldPositional := range oldPositionals {
newPositional := newPositionals[index]
if oldPositional.Name != newPositional.Name ||
oldPositional.Index != newPositional.Index ||
oldPositional.Type != newPositional.Type {
return false
}
if !oldPositional.Required && newPositional.Required {
return false
}
if oldPositional.Variadic && !newPositional.Variadic {
return false
}
if !oldPositional.Variadic && newPositional.Variadic && index != len(newPositionals)-1 {
return false
}
}
if len(newPositionals) == len(oldPositionals) {
return true
}
if len(oldPositionals) > 0 && newPositionals[len(oldPositionals)-1].Variadic {
return false
}
for index := len(oldPositionals); index < len(newPositionals); index++ {
if newPositionals[index].Required {
return false
}
if index > len(oldPositionals) && newPositionals[index-1].Variadic {
return false
}
}
+89
View File
@@ -229,6 +229,91 @@ func TestSchemaCompatibilityAllowsAdditionsAndLooserInputs(t *testing.T) {
}
}
func TestSchemaCompatibilityAllowsLooserAndAppendedOptionalPositionals(t *testing.T) {
baseline := baselineContract()
mutateTool(&baseline, func(tool *toolSchema) {
tool.Positionals[0].Required = true
})
current := cloneContract(baseline)
mutateTool(&current, func(tool *toolSchema) {
tool.Positionals[0].Required = false
tool.Positionals = append(tool.Positionals, positionalSchema{
Name: "template",
Index: 1,
Type: "string",
})
})
if failures := checkCompatibility(baseline, current); len(failures) != 0 {
t.Fatalf("looser and appended optional positionals should pass: %v", failures)
}
}
func TestCompatiblePositionals(t *testing.T) {
baseline := []positionalSchema{
{Name: "content", Index: 0, Type: "string", Required: true},
{Name: "format", Index: 1, Type: "string"},
}
tests := []struct {
name string
old []positionalSchema
current []positionalSchema
compatible bool
}{
{name: "unchanged", old: baseline, current: clonePositionals(baseline), compatible: true},
{name: "required becomes optional", old: baseline, current: []positionalSchema{
{Name: "content", Index: 0, Type: "string"},
{Name: "format", Index: 1, Type: "string"},
}, compatible: true},
{name: "append optional", old: baseline, current: append(clonePositionals(baseline), positionalSchema{
Name: "template", Index: 2, Type: "string",
}), compatible: true},
{name: "last positional becomes variadic", old: baseline, current: []positionalSchema{
{Name: "content", Index: 0, Type: "string", Required: true},
{Name: "format", Index: 1, Type: "string", Variadic: true},
}, compatible: true},
{name: "removed", old: baseline, current: clonePositionals(baseline[:1])},
{name: "renamed", old: baseline, current: []positionalSchema{
{Name: "body", Index: 0, Type: "string", Required: true},
{Name: "format", Index: 1, Type: "string"},
}},
{name: "reindexed", old: baseline, current: []positionalSchema{
{Name: "content", Index: 1, Type: "string", Required: true},
{Name: "format", Index: 2, Type: "string"},
}},
{name: "retyped", old: baseline, current: []positionalSchema{
{Name: "content", Index: 0, Type: "number", Required: true},
{Name: "format", Index: 1, Type: "string"},
}},
{name: "optional becomes required", old: baseline, current: []positionalSchema{
{Name: "content", Index: 0, Type: "string", Required: true},
{Name: "format", Index: 1, Type: "string", Required: true},
}},
{name: "append required", old: baseline, current: append(clonePositionals(baseline), positionalSchema{
Name: "template", Index: 2, Type: "string", Required: true,
})},
{name: "variadic becomes fixed", old: []positionalSchema{
{Name: "content", Index: 0, Type: "string", Variadic: true},
}, current: []positionalSchema{
{Name: "content", Index: 0, Type: "string"},
}},
{name: "append after variadic", old: []positionalSchema{
{Name: "content", Index: 0, Type: "string", Variadic: true},
}, current: []positionalSchema{
{Name: "content", Index: 0, Type: "string", Variadic: true},
{Name: "format", Index: 1, Type: "string"},
}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := compatiblePositionals(test.old, test.current); got != test.compatible {
t.Fatalf("compatiblePositionals() = %t, want %t", got, test.compatible)
}
})
}
}
func TestSchemaCompatibilityRejectsContractDrift(t *testing.T) {
tests := []struct {
name string
@@ -386,6 +471,10 @@ func mutateParameter(contract *schemaContract, mutate func(*parameterSchema)) {
})
}
func clonePositionals(source []positionalSchema) []positionalSchema {
return append([]positionalSchema(nil), source...)
}
func writeTestFile(t *testing.T, path, body string) {
t.Helper()
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
+3735 -851
View File
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package scripts_test
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestAIBehaviorPolicyProtectsEnforcementInputs(t *testing.T) {
t.Parallel()
path, err := filepath.Abs(filepath.Join("..", "..", ".github", "workflows", "ai-behavior-check.yml"))
if err != nil {
t.Fatalf("Abs(ai-behavior-check.yml) error = %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("ReadFile(%s) error = %v", path, err)
}
workflow := string(data)
for _, want := range []string{
"filename.startsWith('scripts/policy/')",
"filename === 'test/fixtures/cli-interface-baseline.txt'",
"previous_filename",
} {
if !strings.Contains(workflow, want) {
t.Errorf("AI behavior policy does not protect %q", want)
}
}
}