From f79a6fc707bf64398734f2e468b9d471f0428bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=AE=E7=9F=A5=E5=A4=8F?= Date: Fri, 7 Aug 2026 16:25:38 +0800 Subject: [PATCH 1/7] fix(minutes): type permission apply --policy as int --- CHANGELOG.md | 7 ++ internal/helpers/minutes.go | 13 ++-- .../helpers/pr868_surfaces_coverage_test.go | 15 ++++ skills/mono/references/products/minutes.md | 73 ++++++++++++++++++- .../dingtalk-minutes/references/minutes.md | 73 ++++++++++++++++++- 5 files changed, 169 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 277cf601..6497665b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th ### Changed - **Sheet CSV formula writes** — `dws sheet csv-put` and batch `csv-put` now expose the service contract that CSV fields beginning with `=` are written as formulas. Prefix the field with an apostrophe to write literal text beginning with `=`; CSV content continues to pass through unchanged. +- **Minutes `permission apply --policy` type** — `--policy` is now declared as + an `int` flag and its required check uses `Flags().Changed`, matching the + numeric-parameter convention. `--help` reports `int` instead of `string`; + accepted values (2/3/4) and gateway behavior are unchanged. +- **Minutes skill references** — document `hot-word delete` and + `permission apply`, list both in the command trees, add their intent + routing, and use a neutral hot-word example in the delete samples. ### Fixed diff --git a/internal/helpers/minutes.go b/internal/helpers/minutes.go index ac52f2be..e3f11ce8 100644 --- a/internal/helpers/minutes.go +++ b/internal/helpers/minutes.go @@ -1346,7 +1346,7 @@ func newMinutesCommand() *cobra.Command { Short: "批量删除个人热词", Long: `批量删除听记个人热词。 支持一次删除多个热词(逗号分隔)。删除后对应热词不再参与后续语音识别优化。`, - Example: ` dws minutes hot-word delete --words "钉钉" + Example: ` dws minutes hot-word delete --words "天气" dws minutes hot-word delete --words "OKR,钉钉,Copilot"`, RunE: func(cmd *cobra.Command, args []string) error { if err := validateRequiredFlags(cmd, "words"); err != nil { @@ -1384,7 +1384,7 @@ func newMinutesCommand() *cobra.Command { "不确定现有热词时先用 hot-word list", }, Examples: []string{ - "dws minutes hot-word delete --words \"钉钉\"", + "dws minutes hot-word delete --words \"天气\"", "dws minutes hot-word delete --words \"OKR,钉钉,Copilot\"", }, }, @@ -1883,11 +1883,12 @@ func newMinutesCommand() *cobra.Command { if err := validateRequiredFlagWithAliases(cmd, "id", "url", "task-uuid", "uuid"); err != nil { return err } - if err := validateRequiredFlags(cmd, "policy"); err != nil { - return err + // 手动校验必填参数(避免 validateRequiredFlags 对 Int 的误判) + if !cmd.Flags().Changed("policy") { + return fmt.Errorf("missing required flag --policy") } - policyID, err := strconv.ParseInt(mustGetFlag(cmd, "policy"), 10, 64) + policyID, err := cmd.Flags().GetInt("policy") if err != nil || policyID < 2 || policyID > 4 { return fmt.Errorf("flag --policy must be an integer between 2 and 4 (2=可编辑, 3=可查看/下载, 4=仅查看)") } @@ -1942,7 +1943,7 @@ func newMinutesCommand() *cobra.Command { _ = permissionApplyCmd.Flags().MarkHidden("task-uuid") permissionApplyCmd.Flags().String("uuid", "", "--id 的别名") _ = permissionApplyCmd.Flags().MarkHidden("uuid") - permissionApplyCmd.Flags().String("policy", "", "权限类型: 2=可编辑, 3=可查看/下载, 4=仅查看 (必填)") + permissionApplyCmd.Flags().Int("policy", 0, "权限类型: 2=可编辑, 3=可查看/下载, 4=仅查看 (必填)") permissionCmd.AddCommand(permissionAddCmd, permissionRemoveCmd, permissionApplyCmd) diff --git a/internal/helpers/pr868_surfaces_coverage_test.go b/internal/helpers/pr868_surfaces_coverage_test.go index ed8bb74e..829a95c7 100644 --- a/internal/helpers/pr868_surfaces_coverage_test.go +++ b/internal/helpers/pr868_surfaces_coverage_test.go @@ -91,6 +91,21 @@ func TestCrossPlatformCoverageMinutesNewSurfaces(t *testing.T) { t.Fatal("expected missing policy") } }) + t.Run("permission apply policy flag is int", func(t *testing.T) { + // 数值参数声明为 int 类型 flag;必填校验走 cmd.Flags().Changed, + // 不能用 validateRequiredFlags(它把 int 零值当成未传)。 + cmd, _, err := newMinutesCommand().Find([]string{"permission", "apply"}) + if err != nil { + t.Fatalf("find permission apply: %v", err) + } + flag := cmd.Flags().Lookup("policy") + if flag == nil { + t.Fatal("flag --policy not found") + } + if flag.Value.Type() != "int" { + t.Fatalf("flag --policy type = %q, want %q", flag.Value.Type(), "int") + } + }) t.Run("audio-memo list default", func(t *testing.T) { caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"items":[]}`}}} diff --git a/skills/mono/references/products/minutes.md b/skills/mono/references/products/minutes.md index dc1ce7f4..05ddd46d 100644 --- a/skills/mono/references/products/minutes.md +++ b/skills/mono/references/products/minutes.md @@ -51,15 +51,17 @@ dws minutes │ └── status --id # 查询脑图生成状态 ├── hot-word # 个人热词管理 │ ├── add --words "..." # 添加热词(提升语音识别准确率) +│ ├── delete --words "..." # 批量删除个人热词 │ └── list # 查询我的热词列表 ├── replace-text --id --search "..." --replace "..." # 替换转写文本中的错误文字 ├── upload # 音频上传(上传本地音频文件生成听记) │ ├── create --file-name "..." --file-size # 创建上传会话,获取上传地址 │ ├── complete --session-id # 确认上传完成 │ └── cancel --session-id # 取消上传会话 -└── permission # 权限管理(添加/移除听记成员权限) +└── permission # 权限管理(添加/移除听记成员权限、为自己申请权限) ├── add --ids --member-uids --policy <0-4> [--cover] [--sub-resources "..."] # 添加成员权限 - └── remove --ids --member-uids # 移除成员权限 + ├── remove --ids --member-uids # 移除成员权限 + └── apply --id --policy <2-4> # 为当前用户申请听记权限 ``` **高频错误命令 vs 正确命令对照(真实 badcase 提炼):** @@ -300,6 +302,7 @@ dws minutes │ └── status --id ├── hot-word │ ├── add --words "..." +│ ├── delete --words "..." │ └── list ├── replace-text --id --search "..." --replace "..." ├── upload @@ -308,7 +311,8 @@ dws minutes │ └── cancel --session-id ├── permission │ ├── add --ids --member-uids --policy <0-4> [--cover] [--sub-resources "..."] -│ └── remove --ids --member-uids +│ ├── remove --ids --member-uids +│ └── apply --id --policy <2-4> └── tag # 听记标签/分组管理(标签在听记页面手动创建) ├── list # 查询我的听记标签/分组列表(返回 tagId 和标签名称) └── query --tag-id [--limit] [--cursor] # 根据标签ID查询该标签下的听记列表 @@ -1047,6 +1051,19 @@ Flags: 添加听记个人热词,用于优化语音识别中专有名词、人名等的识别准确率。支持一次添加多个热词(逗号分隔),每个热词长度不超过 10 个汉字或 5 个英文单词。 +### 批量删除个人热词 +``` +Usage: + dws minutes hot-word delete [flags] +Example: + dws minutes hot-word delete --words "天气" + dws minutes hot-word delete --words "OKR,钉钉,Copilot" +Flags: + --words string 要删除的热词,多个用逗号分隔 (必填) +``` + +批量删除听记个人热词。支持一次删除多个热词(逗号分隔)。删除后对应热词不再参与后续语音识别优化,适用于清理误加、过时或不再需要的热词。 + ### 查询我的热词列表 ``` Usage: @@ -1211,6 +1228,38 @@ Flags: - 如果成员是听记的创建者(所有者),无法通过此命令移除其权限 - 建议在移除前先确认成员的当前权限,避免误操作 +### 为当前用户申请听记权限 +``` +Usage: + dws minutes permission apply [flags] +Example: + dws minutes permission apply --id --policy 4 + dws minutes permission apply --id --policy 2 +Flags: + --id string 听记 taskUuid (必填) + --policy int 权限类型: 2=可编辑, 3=可查看/下载, 4=仅查看 (必填) +``` + +为当前登录用户申请指定听记的权限,无需传入用户 ID,系统自动识别当前用户身份。 +适用于用户无权限访问某听记(如打开分享链接提示无权限)时,主动向听记所有者发起权限申请。 + +**权限类型说明:** + +| --policy 值 | 含义 | 说明 | +|------------|------|------| +| 2 | 可编辑 | 可编辑听记的纪要、待办等内容 | +| 3 | 可查看/下载 | 可查看和下载听记内容,不可编辑 | +| 4 | 仅查看 | 仅可查看听记内容,不可下载 | + +**与 permission add 的区别:** +- `permission add` 是听记所有者/管理员**给别人授权**,需传 `--member-uids` +- `permission apply` 是当前用户**为自己申请权限**,不需要任何用户 ID,不支持申请管理员/所有者权限(0/1) + +**典型使用场景:** +- 打开同事分享的听记链接提示无权限,申请查看权限 +- 需要编辑某篇只读听记的纪要,申请可编辑权限 +- 需要下载听记音频但当前仅有查看权限,申请可查看/下载权限 + ### 查询我的听记标签/分组列表 ``` Usage: @@ -1313,6 +1362,20 @@ tagId 可通过 `dws minutes tag list` 获取。 - "把'AI听记'加进热词库" - "这个专有名词总转写错,帮我加到热词" +### 删除热词 +用户说"删热词/删除热词/移除热词/去掉某个热词/这个热词不要了" → `hot-word delete` + +**自然语言示例:** +- "把'天气'从热词里删掉" +- "删除热词'魔镜'" +- "移除我加错的那个热词" +- "帮我把'飞书,悟空'这几个热词删了" + +**路由规则:** +- 用户想去掉/移除已有热词 → `hot-word delete --words "..."`(多个用逗号分隔) +- 不确定当前有哪些热词时,可先 `hot-word list` 确认再删 +- 用户身份由系统自动识别,无需传 uid + ### 查找替换 用户说"把所有的A替换成B/查找替换/批量替换文字" → `replace-text`,**执行成功后必须主动引导用户:"要不要把『新文字』加到热词里,避免后续新听记再识别错?" 用户确认后再调用 `hot-word add`** @@ -1595,6 +1658,7 @@ tagId 可通过 `dws minutes tag list` 获取。 用户说"把某人加到这个听记中/共享听记给某人/给某人添加听记权限/把听记分享给同事/让某人也能看这个听记" → `permission add` 用户说"添加参会者/批量添加参会者/添加成员/批量添加成员/加参会人/加入成员" → `permission add`(**注意:听记模块没有独立的"添加参会者/成员"命令,统一走权限接口**) 用户说"移除某人的权限/取消共享/不让某人看这个听记了" → `permission remove` +用户说"我没权限看这个听记/帮我申请这个听记的权限/申请查看权限/申请编辑权限" → `permission apply`(**注意:为当前用户自己申请,不是给别人授权**) **自然语言示例:** - "把张三加到这个听记中" @@ -1611,6 +1675,9 @@ tagId 可通过 `dws minutes tag list` 获取。 - "批量添加成员到这个听记" - "把张三加为参会人" - "移除张三对这个听记的权限" +- "我打不开这个听记,帮我申请一下权限" +- "帮我申请这个听记的查看权限" +- "申请这篇听记的编辑权限" - "取消小王对这个听记的访问" - "不让某人看这个听记了" diff --git a/skills/multi/dingtalk-minutes/references/minutes.md b/skills/multi/dingtalk-minutes/references/minutes.md index 7a8c9b19..25b75ce7 100644 --- a/skills/multi/dingtalk-minutes/references/minutes.md +++ b/skills/multi/dingtalk-minutes/references/minutes.md @@ -60,15 +60,17 @@ dws minutes │ └── status --id # 查询脑图生成状态 ├── hot-word # 个人热词管理 │ ├── add --words "..." # 添加热词(提升语音识别准确率) +│ ├── delete --words "..." # 批量删除个人热词 │ └── list # 查询我的热词列表 ├── replace-text --id --search "..." --replace "..." # 替换转写文本中的错误文字 ├── upload # 音频上传(上传本地音频文件生成听记) │ ├── create --file-name "..." --file-size # 创建上传会话,获取上传地址 │ ├── complete --session-id # 确认上传完成 │ └── cancel --session-id # 取消上传会话 -├── permission # 权限管理(添加/移除听记成员权限) +├── permission # 权限管理(添加/移除听记成员权限、为自己申请权限) │ ├── add --ids --member-uids --policy <0-4> [--cover] [--sub-resources "..."] # 添加成员权限 -│ └── remove --ids --member-uids # 移除成员权限 +│ ├── remove --ids --member-uids # 移除成员权限 +│ └── apply --id --policy <2-4> # 为当前用户申请听记权限 └── tag # 听记标签/分组管理(标签在听记页面手动创建) ├── list # 查询我的听记标签/分组列表(返回 tagId 和标签名称) └── query --tag-id [--limit] [--cursor] # 根据标签ID查询该标签下的听记列表 @@ -409,6 +411,7 @@ dws minutes │ └── status --id ├── hot-word │ ├── add --words "..." +│ ├── delete --words "..." │ └── list ├── replace-text --id --search "..." --replace "..." ├── upload @@ -417,7 +420,8 @@ dws minutes │ └── cancel --session-id ├── permission │ ├── add --ids --member-uids --policy <0-4> [--cover] [--sub-resources "..."] -│ └── remove --ids --member-uids +│ ├── remove --ids --member-uids +│ └── apply --id --policy <2-4> └── tag ├── list └── query --tag-id [--limit] [--cursor] @@ -1160,6 +1164,19 @@ Flags: 添加听记个人热词,用于优化语音识别中专有名词、人名等的识别准确率。支持一次添加多个热词(逗号分隔),每个热词长度不超过 10 个汉字或 5 个英文单词。 +### 批量删除个人热词 +``` +Usage: + dws minutes hot-word delete [flags] +Example: + dws minutes hot-word delete --words "天气" + dws minutes hot-word delete --words "OKR,钉钉,Copilot" +Flags: + --words string 要删除的热词,多个用逗号分隔 (必填) +``` + +批量删除听记个人热词。支持一次删除多个热词(逗号分隔)。删除后对应热词不再参与后续语音识别优化,适用于清理误加、过时或不再需要的热词。 + ### 查询我的热词列表 ``` Usage: @@ -1324,6 +1341,38 @@ Flags: - 如果成员是听记的创建者(所有者),无法通过此命令移除其权限 - 建议在移除前先确认成员的当前权限,避免误操作 +### 为当前用户申请听记权限 +``` +Usage: + dws minutes permission apply [flags] +Example: + dws minutes permission apply --id --policy 4 + dws minutes permission apply --id --policy 2 +Flags: + --id string 听记 taskUuid (必填) + --policy int 权限类型: 2=可编辑, 3=可查看/下载, 4=仅查看 (必填) +``` + +为当前登录用户申请指定听记的权限,无需传入用户 ID,系统自动识别当前用户身份。 +适用于用户无权限访问某听记(如打开分享链接提示无权限)时,主动向听记所有者发起权限申请。 + +**权限类型说明:** + +| --policy 值 | 含义 | 说明 | +|------------|------|------| +| 2 | 可编辑 | 可编辑听记的纪要、待办等内容 | +| 3 | 可查看/下载 | 可查看和下载听记内容,不可编辑 | +| 4 | 仅查看 | 仅可查看听记内容,不可下载 | + +**与 permission add 的区别:** +- `permission add` 是听记所有者/管理员**给别人授权**,需传 `--member-uids` +- `permission apply` 是当前用户**为自己申请权限**,不需要任何用户 ID,不支持申请管理员/所有者权限(0/1) + +**典型使用场景:** +- 打开同事分享的听记链接提示无权限,申请查看权限 +- 需要编辑某篇只读听记的纪要,申请可编辑权限 +- 需要下载听记音频但当前仅有查看权限,申请可查看/下载权限 + ### 查询我的听记标签/分组列表 ``` Usage: @@ -1426,6 +1475,20 @@ tagId 可通过 `dws minutes tag list` 获取。 - "把'AI听记'加进热词库" - "这个专有名词总转写错,帮我加到热词" +### 删除热词 +用户说"删热词/删除热词/移除热词/去掉某个热词/这个热词不要了" → `hot-word delete` + +**自然语言示例:** +- "把'天气'从热词里删掉" +- "删除热词'魔镜'" +- "移除我加错的那个热词" +- "帮我把'飞书,悟空'这几个热词删了" + +**路由规则:** +- 用户想去掉/移除已有热词 → `hot-word delete --words "..."`(多个用逗号分隔) +- 不确定当前有哪些热词时,可先 `hot-word list` 确认再删 +- 用户身份由系统自动识别,无需传 uid + ### 查找替换 用户说"把所有的A替换成B/查找替换/批量替换文字" → `replace-text`,**执行成功后必须主动引导用户:"要不要把『新文字』加到热词里,避免后续新听记再识别错?" 用户确认后再调用 `hot-word add`** @@ -1708,6 +1771,7 @@ tagId 可通过 `dws minutes tag list` 获取。 用户说"把某人加到这个听记中/共享听记给某人/给某人添加听记权限/把听记分享给同事/让某人也能看这个听记" → `permission add` 用户说"添加参会者/批量添加参会者/添加成员/批量添加成员/加参会人/加入成员" → `permission add`(**注意:听记模块没有独立的"添加参会者/成员"命令,统一走权限接口**) 用户说"移除某人的权限/取消共享/不让某人看这个听记了" → `permission remove` +用户说"我没权限看这个听记/帮我申请这个听记的权限/申请查看权限/申请编辑权限" → `permission apply`(**注意:为当前用户自己申请,不是给别人授权**) **自然语言示例:** - "把张三加到这个听记中" @@ -1724,6 +1788,9 @@ tagId 可通过 `dws minutes tag list` 获取。 - "批量添加成员到这个听记" - "把张三加为参会人" - "移除张三对这个听记的权限" +- "我打不开这个听记,帮我申请一下权限" +- "帮我申请这个听记的查看权限" +- "申请这篇听记的编辑权限" - "取消小王对这个听记的访问" - "不让某人看这个听记了" From 06b0a9eef361e0191aabd54c2d83c1ee773bd45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=AE=E7=9F=A5=E5=A4=8F?= Date: Fri, 7 Aug 2026 17:56:32 +0800 Subject: [PATCH 2/7] docs(minutes): drop hot-word delete intent routing --- CHANGELOG.md | 6 ++++-- skills/mono/references/products/minutes.md | 14 -------------- .../multi/dingtalk-minutes/references/minutes.md | 14 -------------- 3 files changed, 4 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6497665b..ab3f9e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,10 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th numeric-parameter convention. `--help` reports `int` instead of `string`; accepted values (2/3/4) and gateway behavior are unchanged. - **Minutes skill references** — document `hot-word delete` and - `permission apply`, list both in the command trees, add their intent - routing, and use a neutral hot-word example in the delete samples. + `permission apply`, list both in the command trees, add intent routing for + `permission apply`, and use a neutral hot-word example in the delete samples. + `hot-word delete` intentionally gets no intent routing while its leaf still + declares `confirmation: not_required`. ### Fixed diff --git a/skills/mono/references/products/minutes.md b/skills/mono/references/products/minutes.md index 05ddd46d..36667c70 100644 --- a/skills/mono/references/products/minutes.md +++ b/skills/mono/references/products/minutes.md @@ -1362,20 +1362,6 @@ tagId 可通过 `dws minutes tag list` 获取。 - "把'AI听记'加进热词库" - "这个专有名词总转写错,帮我加到热词" -### 删除热词 -用户说"删热词/删除热词/移除热词/去掉某个热词/这个热词不要了" → `hot-word delete` - -**自然语言示例:** -- "把'天气'从热词里删掉" -- "删除热词'魔镜'" -- "移除我加错的那个热词" -- "帮我把'飞书,悟空'这几个热词删了" - -**路由规则:** -- 用户想去掉/移除已有热词 → `hot-word delete --words "..."`(多个用逗号分隔) -- 不确定当前有哪些热词时,可先 `hot-word list` 确认再删 -- 用户身份由系统自动识别,无需传 uid - ### 查找替换 用户说"把所有的A替换成B/查找替换/批量替换文字" → `replace-text`,**执行成功后必须主动引导用户:"要不要把『新文字』加到热词里,避免后续新听记再识别错?" 用户确认后再调用 `hot-word add`** diff --git a/skills/multi/dingtalk-minutes/references/minutes.md b/skills/multi/dingtalk-minutes/references/minutes.md index 25b75ce7..2a8e6db6 100644 --- a/skills/multi/dingtalk-minutes/references/minutes.md +++ b/skills/multi/dingtalk-minutes/references/minutes.md @@ -1475,20 +1475,6 @@ tagId 可通过 `dws minutes tag list` 获取。 - "把'AI听记'加进热词库" - "这个专有名词总转写错,帮我加到热词" -### 删除热词 -用户说"删热词/删除热词/移除热词/去掉某个热词/这个热词不要了" → `hot-word delete` - -**自然语言示例:** -- "把'天气'从热词里删掉" -- "删除热词'魔镜'" -- "移除我加错的那个热词" -- "帮我把'飞书,悟空'这几个热词删了" - -**路由规则:** -- 用户想去掉/移除已有热词 → `hot-word delete --words "..."`(多个用逗号分隔) -- 不确定当前有哪些热词时,可先 `hot-word list` 确认再删 -- 用户身份由系统自动识别,无需传 uid - ### 查找替换 用户说"把所有的A替换成B/查找替换/批量替换文字" → `replace-text`,**执行成功后必须主动引导用户:"要不要把『新文字』加到热词里,避免后续新听记再识别错?" 用户确认后再调用 `hot-word add`** From 6774d423b74b0822b3d6f2f19e9f6321450d1982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=AE=E7=9F=A5=E5=A4=8F?= Date: Sun, 9 Aug 2026 23:43:51 +0800 Subject: [PATCH 3/7] docs(minutes): drop hot-word delete references from skill docs --- CHANGELOG.md | 8 +++----- skills/mono/references/products/minutes.md | 15 --------------- .../multi/dingtalk-minutes/references/minutes.md | 15 --------------- 3 files changed, 3 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab3f9e5d..bfe3217c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,9 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th an `int` flag and its required check uses `Flags().Changed`, matching the numeric-parameter convention. `--help` reports `int` instead of `string`; accepted values (2/3/4) and gateway behavior are unchanged. -- **Minutes skill references** — document `hot-word delete` and - `permission apply`, list both in the command trees, add intent routing for - `permission apply`, and use a neutral hot-word example in the delete samples. - `hot-word delete` intentionally gets no intent routing while its leaf still - declares `confirmation: not_required`. +- **Minutes skill references** — document `permission apply` in both Minutes + skill references: list it in the command trees, describe its policy values and + how it differs from `permission add`, and add its intent routing. ### Fixed diff --git a/skills/mono/references/products/minutes.md b/skills/mono/references/products/minutes.md index 36667c70..f6bfac66 100644 --- a/skills/mono/references/products/minutes.md +++ b/skills/mono/references/products/minutes.md @@ -51,7 +51,6 @@ dws minutes │ └── status --id # 查询脑图生成状态 ├── hot-word # 个人热词管理 │ ├── add --words "..." # 添加热词(提升语音识别准确率) -│ ├── delete --words "..." # 批量删除个人热词 │ └── list # 查询我的热词列表 ├── replace-text --id --search "..." --replace "..." # 替换转写文本中的错误文字 ├── upload # 音频上传(上传本地音频文件生成听记) @@ -302,7 +301,6 @@ dws minutes │ └── status --id ├── hot-word │ ├── add --words "..." -│ ├── delete --words "..." │ └── list ├── replace-text --id --search "..." --replace "..." ├── upload @@ -1051,19 +1049,6 @@ Flags: 添加听记个人热词,用于优化语音识别中专有名词、人名等的识别准确率。支持一次添加多个热词(逗号分隔),每个热词长度不超过 10 个汉字或 5 个英文单词。 -### 批量删除个人热词 -``` -Usage: - dws minutes hot-word delete [flags] -Example: - dws minutes hot-word delete --words "天气" - dws minutes hot-word delete --words "OKR,钉钉,Copilot" -Flags: - --words string 要删除的热词,多个用逗号分隔 (必填) -``` - -批量删除听记个人热词。支持一次删除多个热词(逗号分隔)。删除后对应热词不再参与后续语音识别优化,适用于清理误加、过时或不再需要的热词。 - ### 查询我的热词列表 ``` Usage: diff --git a/skills/multi/dingtalk-minutes/references/minutes.md b/skills/multi/dingtalk-minutes/references/minutes.md index 2a8e6db6..df980463 100644 --- a/skills/multi/dingtalk-minutes/references/minutes.md +++ b/skills/multi/dingtalk-minutes/references/minutes.md @@ -60,7 +60,6 @@ dws minutes │ └── status --id # 查询脑图生成状态 ├── hot-word # 个人热词管理 │ ├── add --words "..." # 添加热词(提升语音识别准确率) -│ ├── delete --words "..." # 批量删除个人热词 │ └── list # 查询我的热词列表 ├── replace-text --id --search "..." --replace "..." # 替换转写文本中的错误文字 ├── upload # 音频上传(上传本地音频文件生成听记) @@ -411,7 +410,6 @@ dws minutes │ └── status --id ├── hot-word │ ├── add --words "..." -│ ├── delete --words "..." │ └── list ├── replace-text --id --search "..." --replace "..." ├── upload @@ -1164,19 +1162,6 @@ Flags: 添加听记个人热词,用于优化语音识别中专有名词、人名等的识别准确率。支持一次添加多个热词(逗号分隔),每个热词长度不超过 10 个汉字或 5 个英文单词。 -### 批量删除个人热词 -``` -Usage: - dws minutes hot-word delete [flags] -Example: - dws minutes hot-word delete --words "天气" - dws minutes hot-word delete --words "OKR,钉钉,Copilot" -Flags: - --words string 要删除的热词,多个用逗号分隔 (必填) -``` - -批量删除听记个人热词。支持一次删除多个热词(逗号分隔)。删除后对应热词不再参与后续语音识别优化,适用于清理误加、过时或不再需要的热词。 - ### 查询我的热词列表 ``` Usage: From b150911da934789b84350398c7415e57d75554ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=AE=E7=9F=A5=E5=A4=8F?= Date: Mon, 10 Aug 2026 11:09:08 +0800 Subject: [PATCH 4/7] docs(minutes): scope permission member-uids rule and add apply routing --- skills/mono/references/products/minutes.md | 5 +++-- skills/multi/dingtalk-minutes/references/minutes.md | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/skills/mono/references/products/minutes.md b/skills/mono/references/products/minutes.md index f6bfac66..3aac478e 100644 --- a/skills/mono/references/products/minutes.md +++ b/skills/mono/references/products/minutes.md @@ -1656,8 +1656,9 @@ tagId 可通过 `dws minutes tag list` 获取。 - 用户提到"加/添加/共享/分享/让...看/让...访问"等增加权限语义 → `permission add` - 用户提到"添加参会者/加参会者/批量添加参会者/添加成员/加成员/批量添加成员/加参会人/添加参会人"等成员语义 → `permission add`(听记没有独立的"添加成员"接口,**统一走权限接口**) - 用户提到"移除/取消/删除/不让...看"等移除权限语义 → `permission remove` -- 如果用户未指定权限类型,默认使用 `--policy 4`(可查看/下载/编辑) -- 如果用户未提供 member-uids,需要先引导用户提供目标成员的钉钉 UID(可通过 `dws contact user search --query "姓名"` 查询) +- 用户提到"我没权限/打不开/帮我申请/申请查看权限/申请编辑权限"等**为自己申请**语义 → `permission apply`(只需 `--id` + `--policy`;**不需要也不接受 `--member-uids`**,当前用户身份由系统自动识别;未说明权限等级时先与用户确认要 2(可编辑)/ 3(可查看下载)/ 4(仅查看)) +- 如果用户未指定权限类型,`permission add` 默认使用 `--policy 4`(可查看/下载/编辑) +- `permission add` / `permission remove` 需要目标成员的钉钉 UID:如果用户未提供 member-uids,需要先引导用户提供(可通过 `dws contact user search --query "姓名"` 查询);`permission apply` 不适用这一条 **典型执行链路:** 1. 用户说"把张三加到这个听记中" → AI 需获取张三的 UID diff --git a/skills/multi/dingtalk-minutes/references/minutes.md b/skills/multi/dingtalk-minutes/references/minutes.md index df980463..a3c0bbe5 100644 --- a/skills/multi/dingtalk-minutes/references/minutes.md +++ b/skills/multi/dingtalk-minutes/references/minutes.md @@ -1769,8 +1769,9 @@ tagId 可通过 `dws minutes tag list` 获取。 - 用户提到"加/添加/共享/分享/让...看/让...访问"等增加权限语义 → `permission add` - 用户提到"添加参会者/加参会者/批量添加参会者/添加成员/加成员/批量添加成员/加参会人/添加参会人"等成员语义 → `permission add`(听记没有独立的"添加成员"接口,**统一走权限接口**) - 用户提到"移除/取消/删除/不让...看"等移除权限语义 → `permission remove` -- 如果用户未指定权限类型,默认使用 `--policy 4`(可查看/下载/编辑) -- 如果用户未提供 member-uids,需要先引导用户提供目标成员的钉钉 UID(可通过 `dws aisearch person --keyword "姓名" --dimension name` 查询) +- 用户提到"我没权限/打不开/帮我申请/申请查看权限/申请编辑权限"等**为自己申请**语义 → `permission apply`(只需 `--id` + `--policy`;**不需要也不接受 `--member-uids`**,当前用户身份由系统自动识别;未说明权限等级时先与用户确认要 2(可编辑)/ 3(可查看下载)/ 4(仅查看)) +- 如果用户未指定权限类型,`permission add` 默认使用 `--policy 4`(可查看/下载/编辑) +- `permission add` / `permission remove` 需要目标成员的钉钉 UID:如果用户未提供 member-uids,需要先引导用户提供(可通过 `dws aisearch person --keyword "姓名" --dimension name` 查询);`permission apply` 不适用这一条 **典型执行链路:** 1. 用户说"把张三加到这个听记中" → AI 需获取张三的 UID From b55f2437807af943ac2c9207f6c94817259c1298 Mon Sep 17 00:00:00 2001 From: chichuan Date: Mon, 10 Aug 2026 23:25:52 +0800 Subject: [PATCH 5/7] ci(test): shard helper changes in full suite --- .github/workflows/ci.yml | 1 + test/scripts/changelog_pr_gate_test.go | 31 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97e71d3f..46218c94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,6 +148,7 @@ jobs: filename === '.github/actionlint.yaml' || filename.startsWith('scripts/') || filename.startsWith('verify/') || + filename.startsWith('internal/helpers/') || filename.startsWith('internal/generator/') || filename.startsWith('internal/cli/schema') || filename.startsWith('internal/interfacesnapshot/') || diff --git a/test/scripts/changelog_pr_gate_test.go b/test/scripts/changelog_pr_gate_test.go index 18d1d8d3..fd2e9377 100644 --- a/test/scripts/changelog_pr_gate_test.go +++ b/test/scripts/changelog_pr_gate_test.go @@ -439,6 +439,36 @@ if (!isInterfaceSensitive("internal/corecmd/corecmd.go")) { } } +func TestHighRiskClassificationShardsHelperChanges(t *testing.T) { + root, err := filepath.Abs(filepath.Join("..", "..")) + if err != nil { + t.Fatalf("Abs(repo root) error = %v", err) + } + data, err := os.ReadFile(filepath.Join(root, ".github", "workflows", "ci.yml")) + if err != nil { + t.Fatalf("ReadFile(ci.yml) error = %v", err) + } + workflow := string(data) + start := strings.Index(workflow, " const isDocsOnly =") + end := strings.Index(workflow, " const classifyFiles =") + if start < 0 || end <= start { + t.Fatal("Code Admission workflow missing high-risk classifier boundaries") + } + classifier := strings.TrimSpace(workflow[start:end]) + probe := classifier + ` +if (!isHighRisk("internal/helpers/minutes.go")) { + throw new Error("helper changes must use the sharded full suite"); +} +if (isHighRisk("internal/helpersx/minutes.go")) { + throw new Error("helper high-risk classification must respect the path boundary"); +} +` + cmd := exec.Command("node", "-e", probe) + if output, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("high-risk classifier rejected helper sharding contract: %v\n%s", err, output) + } +} + func TestChangelogPRFastPathWorkflowContract(t *testing.T) { root, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { @@ -505,6 +535,7 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) { "filename === '.github/actionlint.yaml'", "filename.startsWith('scripts/')", "filename.startsWith('verify/')", + "filename.startsWith('internal/helpers/')", "filename === 'test/fixtures/cli-interface-baseline.txt'", "filename.startsWith('internal/interfacesnapshot/')", "filename.startsWith('internal/cobracmd/')", From 783e1eeef9606f9279c77e4ac3d1605736d43fda Mon Sep 17 00:00:00 2001 From: chichuan Date: Tue, 11 Aug 2026 00:13:48 +0800 Subject: [PATCH 6/7] fix(ci): stabilize minutes coverage contracts --- .../helpers/pr868_surfaces_coverage_test.go | 11 +++++++ internal/helpers/product_examples_test.go | 9 ++++++ skills/mono/references/products/minutes.md | 2 +- .../dingtalk-minutes/references/minutes.md | 2 +- test/unit/skill_docs_policy_test.go | 30 +++++++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/internal/helpers/pr868_surfaces_coverage_test.go b/internal/helpers/pr868_surfaces_coverage_test.go index 829a95c7..1a2f86a5 100644 --- a/internal/helpers/pr868_surfaces_coverage_test.go +++ b/internal/helpers/pr868_surfaces_coverage_test.go @@ -31,6 +31,17 @@ func executePR868Command(t *testing.T, root *cobra.Command, args ...string) erro } func TestCrossPlatformCoverageMinutesNewSurfaces(t *testing.T) { + t.Run("permission add requires explicit policy", func(t *testing.T) { + caller := &scriptedToolCaller{} + installScriptedCaller(t, caller) + err := executePR868Command(t, newMinutesCommand(), "permission", "add", "--ids", "task-1", "--member-uids", "user-1") + if err == nil || !strings.Contains(err.Error(), "--policy") { + t.Fatalf("permission add without --policy error = %v", err) + } + if caller.calls != 0 { + t.Fatalf("permission add called MCP %d times before required policy validation", caller.calls) + } + }) t.Run("hot-word delete dry-run", func(t *testing.T) { installScriptedCaller(t, &scriptedToolCaller{dry: true, format: "json"}) if err := executePR868Command(t, newMinutesCommand(), "hot-word", "delete", "--words", "钉钉,OKR"); err != nil { diff --git a/internal/helpers/product_examples_test.go b/internal/helpers/product_examples_test.go index 0b3daadf..dbfd500d 100644 --- a/internal/helpers/product_examples_test.go +++ b/internal/helpers/product_examples_test.go @@ -11,6 +11,7 @@ import ( "time" "unicode" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -157,7 +158,15 @@ func TestCrossPlatformCoverageProductCommandExamplesAreExecutableContracts(t *te deps.Out.errW = io.Discard // Product examples execute real RunE paths; whiteboard insert retries must // not burn the suite timeout on real sleep (race CI uses a 12m package cap). + // Sheet export uses helperAfter for progressive polling, so keep that clock + // deterministic too; context deadlines alone still accumulate heavily under + // Windows coverage instrumentation. whiteboardSleep = func(time.Duration) {} + testseam.Swap(t, &helperAfter, func(time.Duration) <-chan time.Time { + ch := make(chan time.Time, 1) + ch <- time.Now() + return ch + }) httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil } httpGetFile = func(_ context.Context, _ string, _ map[string]string, destPath string) error { if destPath == "" { diff --git a/skills/mono/references/products/minutes.md b/skills/mono/references/products/minutes.md index 3aac478e..afeb727a 100644 --- a/skills/mono/references/products/minutes.md +++ b/skills/mono/references/products/minutes.md @@ -1657,7 +1657,7 @@ tagId 可通过 `dws minutes tag list` 获取。 - 用户提到"添加参会者/加参会者/批量添加参会者/添加成员/加成员/批量添加成员/加参会人/添加参会人"等成员语义 → `permission add`(听记没有独立的"添加成员"接口,**统一走权限接口**) - 用户提到"移除/取消/删除/不让...看"等移除权限语义 → `permission remove` - 用户提到"我没权限/打不开/帮我申请/申请查看权限/申请编辑权限"等**为自己申请**语义 → `permission apply`(只需 `--id` + `--policy`;**不需要也不接受 `--member-uids`**,当前用户身份由系统自动识别;未说明权限等级时先与用户确认要 2(可编辑)/ 3(可查看下载)/ 4(仅查看)) -- 如果用户未指定权限类型,`permission add` 默认使用 `--policy 4`(可查看/下载/编辑) +- `permission add` 的 `--policy` 是必填参数,没有默认值。用户未指定权限类型时,先确认要 0(管理员)/ 1(所有者)/ 2(可编辑)/ 3(可查看下载)/ 4(仅查看);即使确认选择 4,命令中仍必须显式传入 `--policy 4` - `permission add` / `permission remove` 需要目标成员的钉钉 UID:如果用户未提供 member-uids,需要先引导用户提供(可通过 `dws contact user search --query "姓名"` 查询);`permission apply` 不适用这一条 **典型执行链路:** diff --git a/skills/multi/dingtalk-minutes/references/minutes.md b/skills/multi/dingtalk-minutes/references/minutes.md index a3c0bbe5..538e940e 100644 --- a/skills/multi/dingtalk-minutes/references/minutes.md +++ b/skills/multi/dingtalk-minutes/references/minutes.md @@ -1770,7 +1770,7 @@ tagId 可通过 `dws minutes tag list` 获取。 - 用户提到"添加参会者/加参会者/批量添加参会者/添加成员/加成员/批量添加成员/加参会人/添加参会人"等成员语义 → `permission add`(听记没有独立的"添加成员"接口,**统一走权限接口**) - 用户提到"移除/取消/删除/不让...看"等移除权限语义 → `permission remove` - 用户提到"我没权限/打不开/帮我申请/申请查看权限/申请编辑权限"等**为自己申请**语义 → `permission apply`(只需 `--id` + `--policy`;**不需要也不接受 `--member-uids`**,当前用户身份由系统自动识别;未说明权限等级时先与用户确认要 2(可编辑)/ 3(可查看下载)/ 4(仅查看)) -- 如果用户未指定权限类型,`permission add` 默认使用 `--policy 4`(可查看/下载/编辑) +- `permission add` 的 `--policy` 是必填参数,没有默认值。用户未指定权限类型时,先确认要 0(管理员)/ 1(所有者)/ 2(可编辑)/ 3(可查看下载)/ 4(仅查看);即使确认选择 4,命令中仍必须显式传入 `--policy 4` - `permission add` / `permission remove` 需要目标成员的钉钉 UID:如果用户未提供 member-uids,需要先引导用户提供(可通过 `dws aisearch person --keyword "姓名" --dimension name` 查询);`permission apply` 不适用这一条 **典型执行链路:** diff --git a/test/unit/skill_docs_policy_test.go b/test/unit/skill_docs_policy_test.go index faefe8ca..ed0429bd 100644 --- a/test/unit/skill_docs_policy_test.go +++ b/test/unit/skill_docs_policy_test.go @@ -394,6 +394,36 @@ func TestCrossPlatformCoverageDocSkillReferenceLoadingBudget(t *testing.T) { } } +func TestMinutesPermissionAddRequiresExplicitPolicy(t *testing.T) { + _, filename, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("runtime.Caller(0) failed") + } + root := filepath.Clean(filepath.Join(filepath.Dir(filename), "..", "..")) + paths := []string{ + filepath.Join(root, "skills", "mono", "references", "products", "minutes.md"), + filepath.Join(root, "skills", "multi", "dingtalk-minutes", "references", "minutes.md"), + } + for _, path := range paths { + content, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + text := string(content) + for _, required := range []string{ + "`permission add` 的 `--policy` 是必填参数,没有默认值", + "命令中仍必须显式传入 `--policy 4`", + } { + if !strings.Contains(text, required) { + t.Errorf("%s missing permission add policy contract %q", path, required) + } + } + if strings.Contains(text, "`permission add` 默认使用 `--policy 4`") { + t.Errorf("%s still documents a nonexistent permission add policy default", path) + } + } +} + func hasAny(s string, needles []string) bool { for _, needle := range needles { if strings.Contains(s, needle) { From d3e444cb563c5bf1ed6703cb267f586a847c3ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=AE=E7=9F=A5=E5=A4=8F?= Date: Tue, 11 Aug 2026 10:43:51 +0800 Subject: [PATCH 7/7] docs(changelog): move the Minutes policy notes into Unreleased The two Minutes notes (permission apply --policy int typing and the skill reference updates) landed in the released 1.0.58-beta.2 section after the branch merged main. That rewrites published release notes and would drop both notes from the next release generated out of Unreleased. Move them verbatim into a Changed subsection under Unreleased; the beta.2 section is byte-identical to main again. --- CHANGELOG.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 848d7eb5..1b6e73cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,16 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th targets to the initial card-creation request and prepending its returned `atTag` to the automatic streaming update. +### Changed + +- **Minutes `permission apply --policy` type** — `--policy` is now declared as + an `int` flag and its required check uses `Flags().Changed`, matching the + numeric-parameter convention. `--help` reports `int` instead of `string`; + accepted values (2/3/4) and gateway behavior are unchanged. +- **Minutes skill references** — document `permission apply` in both Minutes + skill references: list it in the command trees, describe its policy values and + how it differs from `permission add`, and add its intent routing. + ## [1.0.58-beta.2] - 2026-08-10 ### Added @@ -40,13 +50,6 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th - **`sheet insert-dimension` / `delete-dimension` / `update-dimension` 的 `--length` 严格校验**(用户可见行为变更)— 解析由 `fmt.Sscanf("%d")` 改为 `strconv.Atoi`。此前只消费前缀数字,`--length 2x` / `3foo` 会被静默当成 `2` / `3` 并对错误的行列数执行操作(删除方向不可回滚);现在整个值必须是合法正整数,否则报错「`--length` 必须为正整数(>= 1)」且不发起任何请求。**升级影响**:原先依赖这种宽松解析、在传畸形 `--length` 的脚本会开始报错,请把参数修正为纯数字。合法数字值行为不变,上限仍为 5000。`add-dimension` 的 `--length` 是 `Int` 类型 flag,一直由 cobra 严格校验,不受影响。 - **CLI 接口兼容门禁支持 reviewed flag 类型豁免**(无用户可见变更)— `authoritative-interface-integrity` 与 `check-command-compatibility.sh` 此前一律拒绝历史命令的 flag 类型变更,即使新类型只是把同一套校验从 RunE 前移到解析期,也没有任何评审通道。现在两道门禁各带一张精确豁免表:命令路径 + flag 名 + 旧类型 → 新类型四元组全等才命中、方向敏感(`string`→`int` 与 `int`→`string` 是两个不同的键,只有被评审的方向可用),且仅当该 flag 的其他契约(shorthand / required / hidden / no-opt / scope)纹丝不动时才放行,因此豁免夹带不了别的破坏。首条也是目前唯一一条登记的是 `dws minutes permission apply --policy` 的 `string` → `int`(配合 #912):旧实现在 RunE 里做 `strconv.ParseInt(v, 10, 64)` 再校验 `[2,4]`,新实现由 pflag 以 `strconv.ParseInt(s, 0, 64)` 解析后仍校验 `[2,4]`,**历史上能成功的调用集是新调用集的子集**(base 0 额外接受 `0x3` 这类写法,只放宽不收紧),非法值依然失败、只是报错文案与时机前移;flag 默认值由 `""` 变 `"0"` 是类型的必然结果,两道门禁都不比较默认值,且该 flag 必须显式给出、默认值不可达。两张表必须逐字一致并有守卫测试锚定漂移——重复是被迫的而非选择:`check-authoritative-interface-baselines.sh` 会把整个 `scripts/policy/interface-baseline` 目录复制进检出历史版本的 worktree 再编译,那份拷贝不能 import 本分支新增的包。 - **Schema 兼容门禁支持 reviewed 参数类型豁免**(无用户可见变更)— 接上一条。`schema-compatibility` 是同一个 `Interface Integrity` job 里排在两道 CLI 接口门禁之后的第三道检查,此前也一律拒绝已发布参数的 `type` 变更。由于前两道先失败、`set -e` 让它从未在 CI 上暴露,上一条豁免只解决了三分之二。现在 `checkParameterCompatibility` 也带一张精确豁免表:`/` + 参数名 + 旧类型 + 新类型四元组全等才命中、方向敏感,且仅当该参数**除 `type` 外的全部已发布字段逐字段相等**时才放行。这里刻意用相等性比较而非「没有产生其他兼容性错误」:放宽 `required` / `cli_required`、清空 `required_when`、扩宽 `enum`、清空 `interface_type`、经 reviewed mapping exclusion 清空 `property`——这些变化单独看都是兼容的、根本不产生错误,若以错误列表代替相等性检查,它们就能搭着一次已评审的类型迁移一起蒙混过关。结构体整体比较还意味着将来给 `parameterSchema` 新增字段时会自动纳入守卫,而不是悄悄放宽每一条既有条目。唯一条目是 `minutes/minutes.apply_minutes_permission` 的 `policy` 由 `"string"` 迁移到 `"integer"`(配合 #912):该 `type` 由 Cobra flag 类型投影而来(provenance `cobra_flag_type`),描述的是 CLI 如何接受取值;消费方据此拼装的是命令行,而 `--policy 4` 在两种声明下是同一个 argv,加引号的 `--policy "4"` 到 pflag 仍是 4,RunE 也仍校验 `[2,4]`——而且该参数映射的 property `policyId` 一直以数字上报,新声明比旧声明更贴近真实请求。表里的类型值必须是 `schemaType` 实际产出的带引号形态(`"string"` 而非裸 `string`),守卫测试用 `schemaType` 复算并校验类型名属于 JSON Schema 的封闭取值集合——`reviewedInterfaceRefRedirect` 曾因键的书写形态错误两次静默失效,这里不重犯。 -- **Minutes `permission apply --policy` type** — `--policy` is now declared as - an `int` flag and its required check uses `Flags().Changed`, matching the - numeric-parameter convention. `--help` reports `int` instead of `string`; - accepted values (2/3/4) and gateway behavior are unchanged. -- **Minutes skill references** — document `permission apply` in both Minutes - skill references: list it in the command trees, describe its policy values and - how it differs from `permission add`, and add its intent routing. ## [1.0.58-beta.1] - 2026-08-07