diff --git a/internal/app/event_stop_safety_test.go b/internal/app/event_stop_safety_test.go index de2c323a..3278d629 100644 --- a/internal/app/event_stop_safety_test.go +++ b/internal/app/event_stop_safety_test.go @@ -71,7 +71,7 @@ func TestEventStopDryRunPrecedesConfirmationAndReturnsPreview(t *testing.T) { } } -func TestEventStopDryRunDoesNotBypassTargetValidation(t *testing.T) { +func TestCrossPlatformCoverageEventStopDryRunDoesNotBypassTargetValidation(t *testing.T) { for _, test := range []struct { name string args []string diff --git a/internal/app/root_construction_bench_test.go b/internal/app/root_construction_bench_test.go new file mode 100644 index 00000000..9389e7ad --- /dev/null +++ b/internal/app/root_construction_bench_test.go @@ -0,0 +1,33 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app + +import "testing" + +// BenchmarkNewRootCommand measures building the real Cobra tree — roughly 800 +// leaves with their flags, Schema annotations and PostMount work. +// +// It is the other half of the cold-start attribution: `dws version` never +// decodes the Schema catalog, so whatever it spends beyond process start is +// mostly this. Without splitting the two, an optimization aimed at JSON parsing +// could target the smaller cost. +func BenchmarkNewRootCommand(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + root := NewRootCommand() + if root == nil { + b.Fatal("nil root") + } + } +} diff --git a/internal/cli/catalogcodegen/dev_generated.go b/internal/cli/catalogcodegen/dev_generated.go new file mode 100644 index 00000000..f42391fa --- /dev/null +++ b/internal/cli/catalogcodegen/dev_generated.go @@ -0,0 +1,1005 @@ +// Copyright 2026 Alibaba Group +// Code generated by scripts/dev/catalog_codegen_probe.py; DO NOT EDIT. +// +// Feasibility probe for storing the Schema catalog as compiled-in Go literals +// instead of embedded JSON, mirroring how internal/shortcut stores its 376 +// definitions. field_provenance is omitted: no runtime consumer reads it. + +package catalogcodegen + +// Tools holds the dev product's leaf ToolSpecs as static data, so there is +// no decode step on the first lookup. +var Tools = map[string]Tool{ + "dev.add_dev_app_members": { + Name: "add_dev_app_members", + CanonicalPath: "dev.add_dev_app_members", + CLIPath: "dev app member add", + PrimaryCLIPath: "dev app member add", + ProductID: "dev", + Group: "app.member", + Title: "添加开放平台应用成员", + Description: "添加开放平台应用成员", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "向应用添加开发者或管理员成员", + UseWhen: []string{"需要授予指定用户应用协作角色时"}, + AvoidWhen: []string{"只查看现有成员时使用 dev app member list"}, + Examples: []string{"dws dev app member add --unified-app-id --user-ids userId1,userId2 --member-type DEVELOPER --dry-run"}, + HasParameters: true, + ParameterCount: 3, + Parameters: map[string]Param{ + "member-type": {Name: "member-type", Type: "string", Description: "成员类型,如 DEVELOPER (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "user-ids": {Name: "user-ids", Type: "string", Description: "成员 userId 列表,多个用逗号分隔 (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.apply_dev_app_permissions": { + Name: "apply_dev_app_permissions", + CanonicalPath: "dev.apply_dev_app_permissions", + CLIPath: "dev app permission add", + PrimaryCLIPath: "dev app permission add", + ProductID: "dev", + Group: "app.permission", + Title: "申请开放平台应用权限点", + Description: "申请开放平台应用权限点", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "为应用申请一个或多个接口权限", + UseWhen: []string{"已确认 scopeValue 并需要新增权限时"}, + AvoidWhen: []string{"查找或核对已有权限时使用 dev app permission list"}, + Examples: []string{"dws dev app permission add --unified-app-id --scope-values Contact.User.mobile --dry-run"}, + HasParameters: true, + ParameterCount: 2, + Parameters: map[string]Param{ + "scope-values": {Name: "scope-values", Type: "string", Description: "权限点 scopeValue,多个用逗号或分号分隔", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.connect_status": { + Name: "connect_status", + CanonicalPath: "dev.connect_status", + CLIPath: "dev connect status", + PrimaryCLIPath: "dev connect status", + ProductID: "dev", + Group: "connect", + Title: "查看连接器健康状态(healthy/degraded/down,pid、收发活动、日志路径;--json 供外部托管消费)", + Description: "查看后台连接器守护进程状态", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "medium", + Confirmation: "not_required", + Idempotency: "unknown", + InterfaceMode: "local", + Availability: "available", + AgentSummary: "查看后台连接器守护进程状态", + UseWhen: []string{"需要确认本地 connect 守护进程是否在跑、pid/日志路径"}, + AvoidWhen: []string{"要停止守护进程时用 dev connect stop"}, + Examples: []string{"dws dev connect status --unified-app-id --json"}, + HasParameters: true, + ParameterCount: 3, + Parameters: map[string]Param{ + "json": {Name: "json", Type: "boolean", Description: "以 JSON 输出健康报告(供 launchd/systemd/pm2/cron 判断是否重启)", Property: "json", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "robot-client-id": {Name: "robot-client-id", Type: "string", Description: "机器人 clientId(定位守护进程)", Property: "robotClientId", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "统一应用 ID(当未用 clientId 起守护进程时定位)", Property: "unifiedAppId", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.connect_stop": { + Name: "connect_stop", + CanonicalPath: "dev.connect_stop", + CLIPath: "dev connect stop", + PrimaryCLIPath: "dev connect stop", + ProductID: "dev", + Group: "connect", + Title: "优雅停止后台连接器守护进程(释放单实例锁与 Stream 连接)", + Description: "优雅停止后台连接器守护进程", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "medium", + Confirmation: "not_required", + Idempotency: "unknown", + InterfaceMode: "local", + Availability: "available", + AgentSummary: "优雅停止后台连接器守护进程", + UseWhen: []string{"用户明确要求停止本地 Stream/连接器守护进程"}, + AvoidWhen: []string{"只想查看状态时用 dev connect status"}, + Examples: []string{"dws dev connect stop --unified-app-id "}, + HasParameters: true, + ParameterCount: 2, + Parameters: map[string]Param{ + "robot-client-id": {Name: "robot-client-id", Type: "string", Description: "机器人 clientId(定位守护进程)", Property: "robotClientId", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "统一应用 ID(当未用 clientId 起守护进程时定位)", Property: "unifiedAppId", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.create_dev_app": { + Name: "create_dev_app", + CanonicalPath: "dev.create_dev_app", + CLIPath: "dev app create", + PrimaryCLIPath: "dev app create", + ProductID: "dev", + Group: "app", + Title: "创建开放平台企业内部应用", + Description: "创建开放平台企业内部应用", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "创建钉钉开放平台应用并返回 unifiedAppId/凭证信息", + UseWhen: []string{"需要新建企业内部应用或三方个人应用并拿到 unifiedAppId"}, + AvoidWhen: []string{"应用已存在只需改信息时用 dev app update", "只查文档时用 devdoc"}, + Examples: []string{"dws dev app create --name \"我的 AI 机器人\" --desc \"接 opencode\" --dry-run --format json"}, + HasParameters: true, + ParameterCount: 3, + Parameters: map[string]Param{ + "desc": {Name: "desc", Type: "string", Description: "应用描述", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "icon-media-id": {Name: "icon-media-id", Type: "string", Description: "应用图标 mediaId", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "name": {Name: "name", Type: "string", Description: "应用名称 (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.create_dev_app_version": { + Name: "create_dev_app_version", + CanonicalPath: "dev.create_dev_app_version", + CLIPath: "dev app version create", + PrimaryCLIPath: "dev app version create", + ProductID: "dev", + Group: "app.version", + Title: "基于当前配置创建应用新版本", + Description: "基于当前配置创建应用新版本", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "为应用当前配置创建待发布版本", + UseWhen: []string{"配置变更完成后需要生成版本进入发布流程时"}, + AvoidWhen: []string{"只是查看已有版本时使用 dev app version list"}, + Examples: []string{"dws dev app version create --unified-app-id --desc \"新增机器人能力\" --dry-run"}, + HasParameters: true, + ParameterCount: 3, + Parameters: map[string]Param{ + "desc": {Name: "desc", Type: "string", Description: "版本描述", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "version": {Name: "version", Type: "string", Description: "高级可选:显式版本号,如 1.0.1;默认不传,由服务端基于最新已发布版本自动递增", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.delete_dev_app": { + Name: "delete_dev_app", + CanonicalPath: "dev.delete_dev_app", + CLIPath: "dev app delete", + PrimaryCLIPath: "dev app delete", + ProductID: "dev", + Group: "app", + Title: "删除开放平台企业内部应用(不可逆,需 --confirm-name 二次确认)", + Description: "删除开放平台企业内部应用(不可逆,需 --confirm-name 二次确认)", + Aliases: nil, + IsAlias: false, + Effect: "destructive", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "删除开放平台企业内部应用(不可恢复)", + UseWhen: []string{"用户明确要求永久删除应用,且接受不可恢复后果"}, + AvoidWhen: []string{"只需临时下架时用 dev app disable", "用户未确认应用名/影响范围时不要删除"}, + Examples: []string{"dws dev app delete --unified-app-id --dry-run --format json"}, + HasParameters: true, + ParameterCount: 2, + Parameters: map[string]Param{ + "confirm-name": {Name: "confirm-name", Type: "string", Description: "二次确认:必须与被删应用的名称一致(不可逆操作的防误删)", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.disable_dev_app": { + Name: "disable_dev_app", + CanonicalPath: "dev.disable_dev_app", + CLIPath: "dev app disable", + PrimaryCLIPath: "dev app disable", + ProductID: "dev", + Group: "app", + Title: "停用开放平台企业内部应用", + Description: "停用开放平台企业内部应用", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "停用指定开放平台应用", + UseWhen: []string{"需要让应用暂时不可用但保留配置时"}, + AvoidWhen: []string{"永久删除应用使用 dev app delete"}, + Examples: []string{"dws dev app disable --unified-app-id --dry-run --format json"}, + HasParameters: true, + ParameterCount: 1, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.disable_dev_app_robot": { + Name: "disable_dev_app_robot", + CanonicalPath: "dev.disable_dev_app_robot", + CLIPath: "dev app robot disable", + PrimaryCLIPath: "dev app robot disable", + ProductID: "dev", + Group: "app.robot", + Title: "停用现有应用的机器人能力", + Description: "停用现有应用的机器人能力", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "停用指定应用的机器人能力", + UseWhen: []string{"需要暂时让应用机器人离线时"}, + AvoidWhen: []string{"停用整个应用使用 dev app disable"}, + Examples: []string{"dws dev app robot disable --unified-app-id --dry-run"}, + HasParameters: true, + ParameterCount: 1, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.enable_dev_app": { + Name: "enable_dev_app", + CanonicalPath: "dev.enable_dev_app", + CLIPath: "dev app enable", + PrimaryCLIPath: "dev app enable", + ProductID: "dev", + Group: "app", + Title: "启用开放平台企业内部应用", + Description: "启用开放平台企业内部应用", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "启用指定开放平台应用", + UseWhen: []string{"需要恢复一个已停用应用时"}, + AvoidWhen: []string{"启用应用内机器人能力使用 dev app robot enable"}, + Examples: []string{"dws dev app enable --unified-app-id --dry-run --format json"}, + HasParameters: true, + ParameterCount: 1, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.enable_dev_app_robot": { + Name: "enable_dev_app_robot", + CanonicalPath: "dev.enable_dev_app_robot", + CLIPath: "dev app robot enable", + PrimaryCLIPath: "dev app robot enable", + ProductID: "dev", + Group: "app.robot", + Title: "启用现有应用机器人能力(纯启用,无需配置字段)", + Description: "启用现有应用机器人能力(纯启用,无需配置字段)", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "启用指定应用的机器人能力", + UseWhen: []string{"机器人配置存在但处于 OFFLINE 时"}, + AvoidWhen: []string{"建立本地 Stream 连接使用 dev connect"}, + Examples: []string{"dws dev app robot enable --unified-app-id --dry-run"}, + HasParameters: true, + ParameterCount: 1, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.get_dev_app": { + Name: "get_dev_app", + CanonicalPath: "dev.get_dev_app", + CLIPath: "dev app get", + PrimaryCLIPath: "dev app get", + ProductID: "dev", + Group: "app", + Title: "查询开放平台企业内部应用详情", + Description: "查询开放平台企业内部应用详情", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "获取指定开放平台应用详情", + UseWhen: []string{"已知 unifiedAppId 或 appKey 并需要核对应用配置或状态时"}, + AvoidWhen: []string{"需要搜索或分页浏览多个应用时使用 dev app list"}, + Examples: []string{"dws dev app get --unified-app-id --format json", "dws dev app get --app-key --format json"}, + HasParameters: true, + ParameterCount: 2, + Parameters: map[string]Param{ + "app-key": {Name: "app-key", Type: "string", Description: "按 appKey/clientId 查询应用详情(与 --unified-app-id 二选一)", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(与 --app-key 二选一)", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.get_dev_app_credentials": { + Name: "get_dev_app_credentials", + CanonicalPath: "dev.get_dev_app_credentials", + CLIPath: "dev app credentials get", + PrimaryCLIPath: "dev app credentials get", + ProductID: "dev", + Group: "app.credentials", + Title: "读取开放平台应用凭证", + Description: "读取开放平台应用凭证", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "读取指定应用的客户端凭证", + UseWhen: []string{"已知 unifiedAppId 且需要 clientId 或 clientSecret 时"}, + AvoidWhen: []string{"不要把凭证内容用于普通应用详情查询或写入日志"}, + Examples: []string{"dws dev app credentials get --unified-app-id --format json"}, + HasParameters: true, + ParameterCount: 1, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.get_dev_app_version_detail": { + Name: "get_dev_app_version_detail", + CanonicalPath: "dev.get_dev_app_version_detail", + CLIPath: "dev app version get", + PrimaryCLIPath: "dev app version get", + ProductID: "dev", + Group: "app.version", + Title: "查询指定版本详情", + Description: "查询指定版本详情", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "获取指定应用版本的详细内容", + UseWhen: []string{"已知 versionId 并需要核对版本配置时"}, + AvoidWhen: []string{"查看发布进度时使用 dev app version status"}, + Examples: []string{"dws dev app version get --unified-app-id --version-id "}, + HasParameters: true, + ParameterCount: 2, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "version-id": {Name: "version-id", Type: "string", Description: "版本 ID (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.get_dev_app_version_status": { + Name: "get_dev_app_version_status", + CanonicalPath: "dev.get_dev_app_version_status", + CLIPath: "dev app version status", + PrimaryCLIPath: "dev app version status", + ProductID: "dev", + Group: "app.version", + Title: "查询版本发布/审批状态", + Description: "查询版本发布/审批状态", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "查询指定应用版本的发布或审批状态", + UseWhen: []string{"需要判断版本是否已发布、审核中或受阻时"}, + AvoidWhen: []string{"需要版本配置详情时使用 dev app version get"}, + Examples: []string{"dws dev app version status --unified-app-id --version-id "}, + HasParameters: true, + ParameterCount: 2, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "version-id": {Name: "version-id", Type: "string", Description: "版本 ID (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.get_extension_robot_config": { + Name: "get_extension_robot_config", + CanonicalPath: "dev.get_extension_robot_config", + CLIPath: "dev app robot get", + PrimaryCLIPath: "dev app robot get", + ProductID: "dev", + Group: "app.robot", + Title: "查询现有应用的机器人配置", + Description: "查询现有应用的机器人配置", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "获取指定应用的机器人配置和状态", + UseWhen: []string{"需要判断机器人是否未配置、离线或在线时"}, + AvoidWhen: []string{"查询机器人建号异步任务使用 dev app robot result"}, + Examples: []string{"dws dev app robot get --unified-app-id --format json"}, + HasParameters: true, + ParameterCount: 1, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.get_extension_webapp_config": { + Name: "get_extension_webapp_config", + CanonicalPath: "dev.get_extension_webapp_config", + CLIPath: "dev app webapp get", + PrimaryCLIPath: "dev app webapp get", + ProductID: "dev", + Group: "app.webapp", + Title: "查询网页应用配置", + Description: "查询网页应用配置", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "获取指定应用的网页入口配置", + UseWhen: []string{"需要核对应用 H5 或 PC 首页地址时"}, + AvoidWhen: []string{"修改网页入口时使用 dev app webapp config"}, + Examples: []string{"dws dev app webapp get --unified-app-id --format json"}, + HasParameters: true, + ParameterCount: 1, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.list_dev_app": { + Name: "list_dev_app", + CanonicalPath: "dev.list_dev_app", + CLIPath: "dev app list", + PrimaryCLIPath: "dev app list", + ProductID: "dev", + Group: "app", + Title: "查询开放平台企业内部应用列表", + Description: "查询开放平台企业内部应用列表", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "按条件分页查询开放平台应用", + UseWhen: []string{"需要按名称、创建人或应用键筛选应用时"}, + AvoidWhen: []string{"已经持有明确 unifiedAppId 时使用 dev app get"}, + Examples: []string{"dws dev app list --name \"DemoApp\" --page-size 20 --format json"}, + HasParameters: true, + ParameterCount: 11, + Parameters: map[string]Param{ + "app-group-id": {Name: "app-group-id", Type: "integer", Description: "应用分组 ID", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "app-key": {Name: "app-key", Type: "string", Description: "按 appKey/clientId 过滤", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "creator": {Name: "creator", Type: "string", Description: "创建人名称关键词", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "cursor": {Name: "cursor", Type: "string", Description: "游标令牌:首次查询留空,续翻传上次出参的 nextCursor", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "develop-type": {Name: "develop-type", Type: "integer", Description: "开发类型枚举;不确定时不要传", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "filter-cool-app": {Name: "filter-cool-app", Type: "integer", Description: "酷应用过滤枚举;不确定时不要传", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "name": {Name: "name", Type: "string", Description: "应用名称关键词", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "page-size": {Name: "page-size", Type: "integer", Description: "单页条数,默认 20", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "robot-name": {Name: "robot-name", Type: "string", Description: "机器人名称关键词", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "sort-order": {Name: "sort-order", Type: "string", Description: "排序方向 asc 或 desc", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "sort-type": {Name: "sort-type", Type: "string", Description: "排序字段,如 gmt_modified", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.list_dev_app_events": { + Name: "list_dev_app_events", + CanonicalPath: "dev.list_dev_app_events", + CLIPath: "dev app event list", + PrimaryCLIPath: "dev app event list", + ProductID: "dev", + Group: "app.event", + Title: "查询应用已订阅的事件列表", + Description: "查询应用已订阅的事件列表", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "列出或搜索应用可订阅的事件", + UseWhen: []string{"需要查事件码、事件名称或当前事件列表时"}, + AvoidWhen: []string{"订阅或退订事件使用对应写命令"}, + Examples: []string{"dws dev app event list --unified-app-id --keyword \"审批\" --page-size 20"}, + HasParameters: true, + ParameterCount: 4, + Parameters: map[string]Param{ + "cursor": {Name: "cursor", Type: "string", Description: "游标令牌:首次查询留空,续翻传上次出参的 nextCursor", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "keyword": {Name: "keyword", Type: "string", Description: "事件搜索关键词,支持按事件码或事件名称模糊匹配", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "page-size": {Name: "page-size", Type: "integer", Description: "单页条数,默认 20", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.list_dev_app_members": { + Name: "list_dev_app_members", + CanonicalPath: "dev.list_dev_app_members", + CLIPath: "dev app member list", + PrimaryCLIPath: "dev app member list", + ProductID: "dev", + Group: "app.member", + Title: "查询开放平台应用成员", + Description: "查询开放平台应用成员", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "列出指定应用的协作成员", + UseWhen: []string{"需要查看应用开发者和管理员清单时"}, + AvoidWhen: []string{"新增或移除成员使用对应 member 写命令"}, + Examples: []string{"dws dev app member list --unified-app-id "}, + HasParameters: true, + ParameterCount: 1, + Parameters: map[string]Param{ + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.list_dev_app_permissions": { + Name: "list_dev_app_permissions", + CanonicalPath: "dev.list_dev_app_permissions", + CLIPath: "dev app permission list", + PrimaryCLIPath: "dev app permission list", + ProductID: "dev", + Group: "app.permission", + Title: "查询开放平台应用权限列表", + Description: "查询开放平台应用权限列表", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "查询应用权限及其授权状态", + UseWhen: []string{"需要按关键词、范围或状态查权限时"}, + AvoidWhen: []string{"新增或移除权限使用对应 permission 写命令"}, + Examples: []string{"dws dev app permission list --unified-app-id --keyword \"通讯录\" --page-size 20"}, + HasParameters: true, + ParameterCount: 8, + Parameters: map[string]Param{ + "api-status": {Name: "api-status", Type: "string", Description: "开发者后台 apiStatus 过滤", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "auth-status": {Name: "auth-status", Type: "string", Description: "权限状态:ALL、AUTHED、UNAUTHED", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "cursor": {Name: "cursor", Type: "string", Description: "游标令牌:首次查询留空,续翻传上次出参的 nextCursor", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "keyword": {Name: "keyword", Type: "string", Description: "权限名、权限点、接口名关键词", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "page-size": {Name: "page-size", Type: "integer", Description: "单页条数,默认 20", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "scope-type": {Name: "scope-type", Type: "string", Description: "权限一级类型:APP 或 SNS", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "scope-value": {Name: "scope-value", Type: "string", Description: "精确权限点 scopeValue", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.list_dev_app_versions": { + Name: "list_dev_app_versions", + CanonicalPath: "dev.list_dev_app_versions", + CLIPath: "dev app version list", + PrimaryCLIPath: "dev app version list", + ProductID: "dev", + Group: "app.version", + Title: "分页查询应用版本列表", + Description: "分页查询应用版本列表", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "分页列出应用的历史和待发布版本", + UseWhen: []string{"需要查找 versionId 或浏览版本记录时"}, + AvoidWhen: []string{"已知版本并需详情或状态时使用 get 或 status"}, + Examples: []string{"dws dev app version list --unified-app-id --page-size 20"}, + HasParameters: true, + ParameterCount: 3, + Parameters: map[string]Param{ + "cursor": {Name: "cursor", Type: "string", Description: "游标令牌:首次查询留空,续翻传上次出参的 nextCursor", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "page-size": {Name: "page-size", Type: "integer", Description: "单页条数,默认 20", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.publish_dev_app_version": { + Name: "publish_dev_app_version", + CanonicalPath: "dev.publish_dev_app_version", + CLIPath: "dev app version publish", + PrimaryCLIPath: "dev app version publish", + ProductID: "dev", + Group: "app.version", + Title: "发布指定版本(含高敏权限需 --confirmed-sensitive)", + Description: "发布指定版本(含高敏权限需 --confirmed-sensitive)", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "发布开放平台应用指定版本(可先预检)", + UseWhen: []string{"版本已创建,需要预检或正式发布到线上"}, + AvoidWhen: []string{"还没有 versionId 时先 version create / list / get", "含高敏权限但尚未确认时不要正式发布"}, + Examples: []string{"dws dev app version publish --unified-app-id --version-id --dry-run --format json"}, + HasParameters: true, + ParameterCount: 4, + Parameters: map[string]Param{ + "approver-user-id": {Name: "approver-user-id", Type: "string", Description: "灰度选人模式下指定审批人 userId", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "confirmed-sensitive": {Name: "confirmed-sensitive", Type: "boolean", Description: "确认发布包含高敏权限的版本", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "version-id": {Name: "version-id", Type: "string", Description: "版本 ID (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.query_robot_create_result": { + Name: "query_robot_create_result", + CanonicalPath: "dev.query_robot_create_result", + CLIPath: "dev app robot result", + PrimaryCLIPath: "dev app robot result", + ProductID: "dev", + Group: "app.robot", + Title: "查询机器人异步创建任务结果", + Description: "查询机器人异步创建任务结果", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "查询机器人建号异步任务结果", + UseWhen: []string{"已有 taskId,需要轮询 WAITING、SUCCESS 或审批状态时"}, + AvoidWhen: []string{"没有建号任务时先使用 dev app robot submit"}, + Examples: []string{"dws dev app robot result --task-id --format json"}, + HasParameters: true, + ParameterCount: 1, + Parameters: map[string]Param{ + "task-id": {Name: "task-id", Type: "string", Description: "提交创建任务时返回的 taskId (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.remove_dev_app_members": { + Name: "remove_dev_app_members", + CanonicalPath: "dev.remove_dev_app_members", + CLIPath: "dev app member remove", + PrimaryCLIPath: "dev app member remove", + ProductID: "dev", + Group: "app.member", + Title: "移除开放平台应用成员", + Description: "移除开放平台应用成员", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "从应用移除开发者或管理员成员", + UseWhen: []string{"需要撤销指定用户的应用协作角色时"}, + AvoidWhen: []string{"删除应用本身或只查看成员时不要使用"}, + Examples: []string{"dws dev app member remove --unified-app-id --user-ids userId1,userId2 --member-type DEVELOPER --dry-run"}, + HasParameters: true, + ParameterCount: 3, + Parameters: map[string]Param{ + "member-type": {Name: "member-type", Type: "string", Description: "成员类型,如 DEVELOPER (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "user-ids": {Name: "user-ids", Type: "string", Description: "成员 userId 列表,多个用逗号分隔 (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.remove_dev_app_permissions": { + Name: "remove_dev_app_permissions", + CanonicalPath: "dev.remove_dev_app_permissions", + CLIPath: "dev app permission remove", + PrimaryCLIPath: "dev app permission remove", + ProductID: "dev", + Group: "app.permission", + Title: "取消开放平台应用权限点", + Description: "取消开放平台应用权限点", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "移除应用的一个或多个接口权限", + UseWhen: []string{"需要撤销已知 scopeValue 的权限时"}, + AvoidWhen: []string{"只查看权限状态时使用 dev app permission list"}, + Examples: []string{"dws dev app permission remove --unified-app-id --scope-values Contact.User.mobile --dry-run"}, + HasParameters: true, + ParameterCount: 2, + Parameters: map[string]Param{ + "scope-values": {Name: "scope-values", Type: "string", Description: "待取消权限点 scopeValue,多个用逗号或分号分隔", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.search_open_platform_docs_rag": { + Name: "search_open_platform_docs_rag", + CanonicalPath: "dev.search_open_platform_docs_rag", + CLIPath: "dev doc search", + PrimaryCLIPath: "dev doc search", + ProductID: "dev", + Group: "doc", + Title: "搜索开放平台文档", + Description: "按关键词搜索 open.dingtalk.com 文档,支持分页。默认表格输出,可用 -f json 获取完整响应。", + Aliases: nil, + IsAlias: false, + Effect: "read", + Risk: "low", + Confirmation: "not_required", + Idempotency: "idempotent", + InterfaceMode: "mcp", + Availability: "available", + AgentSummary: "通过 dev 兼容入口搜索开放平台文档", + UseWhen: []string{"明确需要验证或使用 dev doc search 入口时"}, + AvoidWhen: []string{"常规开放平台文档检索优先使用可用的 devdoc article search"}, + Examples: []string{"dws dev doc search --query \"MCP\" --size 10"}, + HasParameters: true, + ParameterCount: 3, + Parameters: map[string]Param{ + "page": {Name: "page", Type: "string", Description: "页码,默认 1", Property: "page", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "query": {Name: "query", Type: "string", Description: "搜索关键词 (必填)", Property: "keyword", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "size": {Name: "size", Type: "string", Description: "每页数量,默认 10", Property: "size", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.set_extension_robot_config": { + Name: "set_extension_robot_config", + CanonicalPath: "dev.set_extension_robot_config", + CLIPath: "dev app robot config", + PrimaryCLIPath: "dev app robot config", + ProductID: "dev", + Group: "app.robot", + Title: "创建或更新现有应用的机器人配置(upsert)", + Description: "创建或更新现有应用的机器人配置(upsert)", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "创建或更新应用的机器人能力配置", + UseWhen: []string{"已有 unifiedAppId 并需要配置机器人名称、回调或技能时"}, + AvoidWhen: []string{"机器人建号使用 robot submit,本地建联使用 dev connect"}, + Examples: []string{"dws dev app robot config --unified-app-id --name \"小助手\" --brief \"审批助手\" --dry-run"}, + HasParameters: true, + ParameterCount: 14, + Parameters: map[string]Param{ + "add-scope": {Name: "add-scope", Type: "boolean", Description: "是否自动添加机器人相关权限", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "brief": {Name: "brief", Type: "string", Description: "机器人简介", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "desc": {Name: "desc", Type: "string", Description: "机器人描述", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "disable-ssl-verify": {Name: "disable-ssl-verify", Type: "boolean", Description: "回调地址是否关闭 SSL 校验", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "event-callback-url": {Name: "event-callback-url", Type: "string", Description: "事件回调地址", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "i18n-brief": {Name: "i18n-brief", Type: "string", Description: "机器人简介国际化 JSON", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "i18n-description": {Name: "i18n-description", Type: "string", Description: "机器人描述国际化 JSON", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "i18n-name": {Name: "i18n-name", Type: "string", Description: "机器人名称国际化 JSON,如 '{\"en_US\":\"Bot\"}'", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "icon-media-id": {Name: "icon-media-id", Type: "string", Description: "机器人图标 mediaId", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "mode": {Name: "mode", Type: "string", Description: "机器人模式:HTTPS / STREAM / AISKILL", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "name": {Name: "name", Type: "string", Description: "机器人名称", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "outgoing-url": {Name: "outgoing-url", Type: "string", Description: "消息回调地址", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "skills": {Name: "skills", Type: "string", Description: "技能列表,多个用逗号或分号分隔", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.set_extension_webapp_config": { + Name: "set_extension_webapp_config", + CanonicalPath: "dev.set_extension_webapp_config", + CLIPath: "dev app webapp config", + PrimaryCLIPath: "dev app webapp config", + ProductID: "dev", + Group: "app.webapp", + Title: "配置网页应用能力", + Description: "配置网页应用能力", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "创建或更新应用网页入口配置", + UseWhen: []string{"需要设置 H5、PC 首页或 OMP 地址时"}, + AvoidWhen: []string{"只查看当前网页配置时使用 dev app webapp get"}, + Examples: []string{"dws dev app webapp config --unified-app-id --homepage-url https://example.com --dry-run"}, + HasParameters: true, + ParameterCount: 5, + Parameters: map[string]Param{ + "h5-page-type": {Name: "h5-page-type", Type: "string", Description: "网页应用生效端/页面类型", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "homepage-url": {Name: "homepage-url", Type: "string", Description: "移动端首页地址", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "omp-url": {Name: "omp-url", Type: "string", Description: "管理后台地址", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "pc-homepage-url": {Name: "pc-homepage-url", Type: "string", Description: "PC 端首页地址", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.submit_robot_create_task": { + Name: "submit_robot_create_task", + CanonicalPath: "dev.submit_robot_create_task", + CLIPath: "dev app robot submit", + PrimaryCLIPath: "dev app robot submit", + ProductID: "dev", + Group: "app.robot", + Title: "异步提交钉钉智能体机器人创建任务(支持失败重试)", + Description: "异步提交钉钉智能体机器人创建任务(支持失败重试)", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "提交机器人建号异步任务", + UseWhen: []string{"需要创建新的机器人账号并取得 taskId 时"}, + AvoidWhen: []string{"已有任务应先用 robot result 轮询,不能重复提交"}, + Examples: []string{"dws dev app robot submit --name \"我的智能体\" --robot-name \"小助手\" --desc \"处理审批问答\" --dry-run"}, + HasParameters: true, + ParameterCount: 6, + Parameters: map[string]Param{ + "desc": {Name: "desc", Type: "string", Description: "机器人功能描述,不超过 200 字 (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "icon-media-id": {Name: "icon-media-id", Type: "string", Description: "机器人图标 mediaId;为空时使用默认图标", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "name": {Name: "name", Type: "string", Description: "智能体应用名称,长度 2-20,企业内唯一 (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "preview-media-id": {Name: "preview-media-id", Type: "string", Description: "机器人预览图 mediaId;为空时复用图标", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "robot-name": {Name: "robot-name", Type: "string", Description: "承载机器人名称,用于客户端展示 (必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "task-id": {Name: "task-id", Type: "string", Description: "失败重试时传入原 taskId;为空时服务端自动生成", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.subscribe_dev_app_events": { + Name: "subscribe_dev_app_events", + CanonicalPath: "dev.subscribe_dev_app_events", + CLIPath: "dev app event subscribe", + PrimaryCLIPath: "dev app event subscribe", + ProductID: "dev", + Group: "app.event", + Title: "订阅应用事件回调", + Description: "订阅应用事件回调", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "为应用订阅指定事件码", + UseWhen: []string{"已确认事件码并需要新增事件订阅时"}, + AvoidWhen: []string{"查询事件码或已有订阅时先使用 dev app event list"}, + Examples: []string{"dws dev app event subscribe --unified-app-id --event-codes bpms_task_change --dry-run"}, + HasParameters: true, + ParameterCount: 2, + Parameters: map[string]Param{ + "event-codes": {Name: "event-codes", Type: "string", Description: "事件码,多个用逗号或分号分隔", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.unsubscribe_dev_app_events": { + Name: "unsubscribe_dev_app_events", + CanonicalPath: "dev.unsubscribe_dev_app_events", + CLIPath: "dev app event unsubscribe", + PrimaryCLIPath: "dev app event unsubscribe", + ProductID: "dev", + Group: "app.event", + Title: "取消订阅应用事件", + Description: "取消订阅应用事件", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "取消应用的指定事件订阅", + UseWhen: []string{"需要停止接收一个或多个已订阅事件时"}, + AvoidWhen: []string{"只是查看事件订阅时使用 dev app event list"}, + Examples: []string{"dws dev app event unsubscribe --unified-app-id --event-codes bpms_task_change --dry-run"}, + HasParameters: true, + ParameterCount: 2, + Parameters: map[string]Param{ + "event-codes": {Name: "event-codes", Type: "string", Description: "事件码,多个用逗号或分号分隔", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.update_dev_app": { + Name: "update_dev_app", + CanonicalPath: "dev.update_dev_app", + CLIPath: "dev app update", + PrimaryCLIPath: "dev app update", + ProductID: "dev", + Group: "app", + Title: "修改开放平台企业内部应用基础信息", + Description: "修改开放平台企业内部应用基础信息:名称、描述或图标至少提供一项。", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "更新开放平台应用名称/描述/图标等基础信息", + UseWhen: []string{"已有 unifiedAppId,需要修改应用基础信息"}, + AvoidWhen: []string{"创建新应用用 create;停用/启用用 disable/enable;改安全配置用 security config"}, + Examples: []string{"dws dev app update --unified-app-id --name <新名称> --dry-run --format json"}, + HasParameters: true, + ParameterCount: 4, + Parameters: map[string]Param{ + "desc": {Name: "desc", Type: "string", Description: "新的应用描述", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "icon-media-id": {Name: "icon-media-id", Type: "string", Description: "新的应用图标 mediaId", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "name": {Name: "name", Type: "string", Description: "新的应用名称", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, + "dev.update_dev_app_security_config": { + Name: "update_dev_app_security_config", + CanonicalPath: "dev.update_dev_app_security_config", + CLIPath: "dev app security config", + PrimaryCLIPath: "dev app security config", + ProductID: "dev", + Group: "app.security", + Title: "更新开放平台应用安全配置", + Description: "更新开放平台应用安全配置", + Aliases: nil, + IsAlias: false, + Effect: "write", + Risk: "high", + Confirmation: "user_required", + Idempotency: "unknown", + InterfaceMode: "composite", + Availability: "available", + AgentSummary: "更新应用 IP 白名单、回调和单点登录地址", + UseWhen: []string{"需要调整应用安全相关 URL 或白名单时"}, + AvoidWhen: []string{"普通应用名称和描述更新使用 dev app update"}, + Examples: []string{"dws dev app security config --unified-app-id --ip-whitelist 192.0.2.10 --redirect-urls https://callback.example.invalid/callback --dry-run"}, + HasParameters: true, + ParameterCount: 4, + Parameters: map[string]Param{ + "ip-whitelist": {Name: "ip-whitelist", Type: "string", Description: "出口 IP 白名单,多个用逗号或分号分隔(整组覆盖,非追加)", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "redirect-urls": {Name: "redirect-urls", Type: "string", Description: "登录重定向 URL,多个用逗号或分号分隔(整组覆盖,非追加)", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "sso-urls": {Name: "sso-urls", Type: "string", Description: "端内免登地址,多个用逗号或分号分隔(整组覆盖,非追加)", Property: "", Required: false, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + "unified-app-id": {Name: "unified-app-id", Type: "string", Description: "开放平台统一应用 ID(必填)", Property: "", Required: true, CLIRequired: false, RequiredWhen: "", Format: "", Enum: nil}, + }, + }, +} diff --git a/internal/cli/catalogcodegen/fidelity_probe_test.go b/internal/cli/catalogcodegen/fidelity_probe_test.go new file mode 100644 index 00000000..63c6ad6d --- /dev/null +++ b/internal/cli/catalogcodegen/fidelity_probe_test.go @@ -0,0 +1,63 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package catalogcodegen_test + +import ( + "reflect" + "strings" + "testing" + "unicode" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" +) + +// TestToolSpecIsExpressibleAsLiterals decides whether the catalog can be stored +// as compiled-in Go literals at all. +// +// Byte-identical `dws schema` output requires that a ToolSpec built by generated +// code in another package can hold exactly the state the JSON path produces. That +// is possible if and only if every field reachable from ToolSpec is exported — +// an unexported field cannot be set from outside internal/cli, so its value could +// never be reproduced. This walks the type graph and reports any that block it. +func TestToolSpecIsExpressibleAsLiterals(t *testing.T) { + var blockers []string + seen := map[reflect.Type]bool{} + + var walk func(t reflect.Type, path string) + walk = func(rt reflect.Type, path string) { + for rt.Kind() == reflect.Ptr || rt.Kind() == reflect.Slice || rt.Kind() == reflect.Array { + rt = rt.Elem() + } + if rt.Kind() == reflect.Map { + walk(rt.Key(), path+"[key]") + rt = rt.Elem() + for rt.Kind() == reflect.Ptr || rt.Kind() == reflect.Slice { + rt = rt.Elem() + } + } + if rt.Kind() != reflect.Struct || seen[rt] { + return + } + seen[rt] = true + for i := 0; i < rt.NumField(); i++ { + field := rt.Field(i) + name := path + "." + field.Name + if !unicode.IsUpper(rune(field.Name[0])) { + // json.RawMessage and time internals are stdlib types a literal + // can still produce by value; only our own types matter. + if strings.HasPrefix(rt.PkgPath(), "github.com/DingTalk-Real-AI") { + blockers = append(blockers, name+" ("+rt.String()+")") + } + continue + } + walk(field.Type, name) + } + } + walk(reflect.TypeOf(cli.ToolSpec{}), "ToolSpec") + + if len(blockers) != 0 { + t.Fatalf("unexported fields block literal construction: %v", blockers) + } + t.Logf("ToolSpec graph is fully exported across %d struct types", len(seen)) +} diff --git a/internal/cli/catalogcodegen/probe_bench_test.go b/internal/cli/catalogcodegen/probe_bench_test.go new file mode 100644 index 00000000..a8b58485 --- /dev/null +++ b/internal/cli/catalogcodegen/probe_bench_test.go @@ -0,0 +1,50 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package catalogcodegen_test + +import ( + "encoding/json" + "os" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/catalogcodegen" +) + +// BenchmarkGoLiteralAccess measures reaching a tool in the compiled-in map. The +// data is static, so this is a map lookup with no decode and no allocation — +// the cost model internal/shortcut already gets. +func BenchmarkGoLiteralAccess(b *testing.B) { + if len(catalogcodegen.Tools) == 0 { + b.Fatal("no generated tools") + } + b.ReportAllocs() + for i := 0; i < b.N; i++ { + tool, ok := catalogcodegen.Tools["dev.delete_dev_app"] + if !ok || tool.CLIPath == "" { + b.Fatal("lookup missed") + } + } +} + +// BenchmarkJSONDecodeSameShard measures decoding the same product's committed +// shard into untyped maps, i.e. what the current loader pays for this product. +func BenchmarkJSONDecodeSameShard(b *testing.B) { + data, err := os.ReadFile("../schema_catalog/tools/dev.json") + if err != nil { + b.Skip("shard unavailable") + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + var shard struct { + Tools map[string]map[string]any `json:"tools"` + } + if err := json.Unmarshal(data, &shard); err != nil { + b.Fatal(err) + } + if len(shard.Tools) == 0 { + b.Fatal("empty shard") + } + } +} diff --git a/internal/cli/catalogcodegen/probemain/main.go b/internal/cli/catalogcodegen/probemain/main.go new file mode 100644 index 00000000..932be6d1 --- /dev/null +++ b/internal/cli/catalogcodegen/probemain/main.go @@ -0,0 +1,21 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +// Command probemain exists only to measure the linked size contribution of the +// generated catalog literals; it keeps the data reachable so the linker cannot +// strip it. +package main + +import ( + "fmt" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/catalogcodegen" +) + +func main() { + total := 0 + for _, tool := range catalogcodegen.Tools { + total += len(tool.CLIPath) + len(tool.Description) + len(tool.Parameters) + } + fmt.Println(len(catalogcodegen.Tools), total) +} diff --git a/internal/cli/catalogcodegen/types.go b/internal/cli/catalogcodegen/types.go new file mode 100644 index 00000000..bfa8b2f3 --- /dev/null +++ b/internal/cli/catalogcodegen/types.go @@ -0,0 +1,53 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +// Package catalogcodegen is a throwaway feasibility probe: it measures what it +// costs to store one product's Schema catalog as compiled-in Go literals rather +// than embedded JSON, which is how internal/shortcut stores its 376 definitions +// (measured at 1.9ms/3MB versus the catalog's 1400ms/740MB). +// +// The struct shape mirrors the runtime-consumed subset of schemaToolWire. It is +// structurally equivalent rather than shared, because the real type is +// package-private to internal/cli; the point is to measure how the Go compiler +// and linker handle this data volume. +package catalogcodegen + +// Param is the runtime-consumed subset of a catalog parameter. +type Param struct { + Name string + Type string + Description string + Property string + Required bool + CLIRequired bool + RequiredWhen string + Format string + Enum []string +} + +// Tool is the runtime-consumed subset of a catalog leaf ToolSpec. +type Tool struct { + Name string + CanonicalPath string + CLIPath string + PrimaryCLIPath string + ProductID string + Group string + Title string + Description string + Aliases []string + IsAlias bool + Effect string + Risk string + Confirmation string + Idempotency string + InterfaceMode string + Availability string + AgentSummary string + UseWhen []string + AvoidWhen []string + Examples []string + HasParameters bool + ParameterCount int + Parameters map[string]Param +} diff --git a/internal/cli/contract_final.go b/internal/cli/contract_final.go index 7873f33c..4563bd5d 100644 --- a/internal/cli/contract_final.go +++ b/internal/cli/contract_final.go @@ -14,6 +14,7 @@ package cli import ( + "strings" "sync" "github.com/spf13/cobra" @@ -26,6 +27,7 @@ type ContractFinalPayload struct { Title string Description string Positionals []RuntimeSchemaPositional + Parameters []ParamDecl Safety *SafetySpec DryRun *DryRunSpec Interface *InterfaceSpec @@ -35,6 +37,53 @@ type ContractFinalPayload struct { var contractFinalByCommand sync.Map // *cobra.Command → *ContractFinalPayload +// ParamDecl is one parameter-level Schema fact declared on a command. It is +// stored at DeclareLeafMetadata time and applied as annotations at assembly +// time, when all flags are guaranteed to exist on the fully-built command tree. +type ParamDecl struct { + Name string + Property string + Required *bool + InterfaceType string + Description string + RequiredWhen string + Enum []string +} + +// ApplyParamDecls emits parameter declarations as dws.schema.* annotations on the +// command's flags. Called at assembly time (runtimeToolSpecFromContractFinal) +// when all flags exist on the fully-built command tree. The decls come from +// the ContractFinalPayload, so no separate storage is needed. +func ApplyParamDecls(cmd *cobra.Command, decls []ParamDecl) { + if cmd == nil || len(decls) == 0 { + return + } + for _, p := range decls { + name := strings.TrimSpace(p.Name) + if name == "" { + continue + } + if prop := strings.TrimSpace(p.Property); prop != "" { + AnnotateRuntimeFlagProperty(cmd, name, prop) + } + if p.Required != nil { + AnnotateRuntimeFlagRequiredValue(cmd, name, *p.Required) + } + if it := strings.TrimSpace(p.InterfaceType); it != "" { + AnnotateRuntimeFlagInterfaceType(cmd, name, it) + } + if desc := strings.TrimSpace(p.Description); desc != "" { + AnnotateRuntimeFlagDescription(cmd, name, desc) + } + if rw := strings.TrimSpace(p.RequiredWhen); rw != "" { + AnnotateRuntimeFlagRequiredWhen(cmd, name, rw) + } + if len(p.Enum) > 0 { + AnnotateRuntimeFlagEnum(cmd, name, p.Enum...) + } + } +} + // RegisterRuntimeContractFinal stores the typed final Schema overlay for a leaf. // Light runtime write: one map store, no JSON, no deep clone. func RegisterRuntimeContractFinal(cmd *cobra.Command, payload ContractFinalPayload) { diff --git a/internal/cli/contract_final_test.go b/internal/cli/contract_final_test.go index 78668824..e54fae19 100644 --- a/internal/cli/contract_final_test.go +++ b/internal/cli/contract_final_test.go @@ -14,6 +14,8 @@ package cli import ( + "errors" + "strings" "testing" "github.com/spf13/cobra" @@ -45,7 +47,7 @@ func TestContractFinalTypedRegistryNoJSON(t *testing.T) { } } -func TestRuntimeToolSpecFromContractFinalPassThrough(t *testing.T) { +func TestCrossPlatformCoverageRuntimeToolSpecFromContractFinalPassThrough(t *testing.T) { cmd := &cobra.Command{Use: "create", Short: "s", Long: "l"} t.Cleanup(func() { ClearRuntimeContractFinalForTest(cmd) }) cmd.Flags().String("mode", "", "usage") @@ -99,7 +101,7 @@ func TestRuntimeToolSpecFromContractFinalPassThrough(t *testing.T) { } } -func TestRuntimeToolSpecFromContractFinalIdentityMismatchFails(t *testing.T) { +func TestCrossPlatformCoverageRuntimeToolSpecFromContractFinalIdentityMismatchFails(t *testing.T) { entry := runtimeSchemaEntry{ ProductID: "dev", ToolName: "create_thing", @@ -133,7 +135,7 @@ func TestRuntimeToolSpecFromContractFinalIdentityMismatchFails(t *testing.T) { } } -func TestRuntimeToolSpecFromContractFinalRejectsReviewedSelection(t *testing.T) { +func TestCrossPlatformCoverageRuntimeToolSpecFromContractFinalRejectsReviewedSelection(t *testing.T) { reviewed := true entry := runtimeSchemaEntry{ ProductID: "dev", @@ -156,3 +158,153 @@ func mustFinal(t *testing.T, cmd *cobra.Command) ContractFinalPayload { } return final } + +func TestCrossPlatformCoverageContractFinalNilCommandGuards(t *testing.T) { + // Nil command registration/lookup must be inert no-ops. + RegisterRuntimeContractFinal(nil, ContractFinalPayload{Title: "ignored"}) + if _, ok := RuntimeContractFinal(nil); ok { + t.Fatal("RuntimeContractFinal(nil) must report no payload") + } + if HasRuntimeContractFinal(nil) { + t.Fatal("HasRuntimeContractFinal(nil) must be false") + } + ClearRuntimeContractFinalForTest(nil) +} + +func TestCrossPlatformCoverageRuntimeContractFinalRejectsForeignStoredValue(t *testing.T) { + cmd := &cobra.Command{Use: "x"} + t.Cleanup(func() { ClearRuntimeContractFinalForTest(cmd) }) + + // Defensive branch: a stored value that is not a *ContractFinalPayload + // (or a typed nil) must fail the read instead of panicking. + contractFinalByCommand.Store(cmd, "not-a-payload") + if _, ok := RuntimeContractFinal(cmd); ok { + t.Fatal("foreign stored value must not decode as ContractFinalPayload") + } + contractFinalByCommand.Store(cmd, (*ContractFinalPayload)(nil)) + if _, ok := RuntimeContractFinal(cmd); ok { + t.Fatal("typed nil payload must not decode as ContractFinalPayload") + } +} + +func TestCrossPlatformCoverageRuntimeToolSpecFromContractFinalIdentityOverridesApplied(t *testing.T) { + cmd := &cobra.Command{Use: "create"} + entry := runtimeSchemaEntry{ + ProductID: "dev", + SourceProductID: "src", + ToolName: "create_thing", + CLIName: "create", + Group: "dev thing", + CLIPath: "dev create", + PrimaryCLIPath: "dev create", + Aliases: []string{"dev alt"}, + Command: cmd, + Source: "test", + } + final := ContractFinalPayload{Identity: &ToolIdentitySpec{ + ProductID: "dev", + SourceProductID: "src", + Name: "create_thing", + CLIName: "create", + CanonicalPath: "dev.create_thing", + CLIPath: "dev create", + PrimaryCLIPath: "dev create", + Group: "dev thing", + Aliases: []string{"dev alt"}, + Source: "test", + }} + spec, err := runtimeToolSpecFromContractFinal(entry, final, runtimeSchemaMetadataSources{}) + if err != nil { + t.Fatalf("consistent declared identity with all override fields failed: %v", err) + } + if spec.Identity.SourceProductID != "src" { + t.Fatalf("source_product_id = %q, want src", spec.Identity.SourceProductID) + } + if spec.Identity.Group != "dev thing" { + t.Fatalf("group = %q, want dev thing", spec.Identity.Group) + } + if len(spec.Identity.Aliases) != 1 || spec.Identity.Aliases[0] != "dev alt" { + t.Fatalf("aliases = %v, want [dev alt]", spec.Identity.Aliases) + } + if spec.Identity.CanonicalPath != "dev.create_thing" || spec.Identity.Source != "test" { + t.Fatalf("identity = %#v", spec.Identity) + } +} + +func TestCrossPlatformCoverageRuntimeToolSpecFromContractFinalSameLengthAliasMismatchFails(t *testing.T) { + entry := runtimeSchemaEntry{ + ProductID: "dev", + ToolName: "create_thing", + CLIName: "create", + CLIPath: "dev create", + PrimaryCLIPath: "dev create", + Aliases: []string{"dev rm"}, + Command: &cobra.Command{Use: "create"}, + Source: "test", + } + // Same-length alias sets with different members must be detected by the + // element-wise comparison, not only by the length fast-path. + final := ContractFinalPayload{Identity: &ToolIdentitySpec{Aliases: []string{"dev other"}}} + _, err := runtimeToolSpecFromContractFinal(entry, final, runtimeSchemaMetadataSources{}) + if err == nil || !strings.Contains(err.Error(), "aliases") { + t.Fatalf("same-length alias mismatch error = %v, want aliases mismatch", err) + } +} + +func TestCrossPlatformCoverageRuntimeToolSpecFromContractFinalSafetyAnnotationFallbacks(t *testing.T) { + entry := runtimeSchemaEntry{ + ProductID: "dev", + ToolName: "create_thing", + CLIName: "create", + CLIPath: "dev create", + PrimaryCLIPath: "dev create", + Source: "test", + } + + // Safety nil + Contract Risk annotation: Risk overlay wins. + riskCmd := &cobra.Command{Use: "create"} + AnnotateRuntimeRisk(riskCmd, "write") + entry.Command = riskCmd + spec, err := runtimeToolSpecFromContractFinal(entry, ContractFinalPayload{}, runtimeSchemaMetadataSources{}) + if err != nil { + t.Fatalf("risk-annotated declared leaf failed: %v", err) + } + if spec.Safety.Effect != "write" || spec.Safety.Risk != "medium" || spec.Safety.Confirmation != "user_required" { + t.Fatalf("risk fallback safety = %#v", spec.Safety) + } + if spec.Safety.EffectSource != "corecmd.contract" { + t.Fatalf("risk fallback effect source = %q", spec.Safety.EffectSource) + } + + // Safety nil + runtime gate annotation: gate overlay wins. + gateCmd := &cobra.Command{Use: "create"} + AnnotateRuntimeGate(gateCmd, "devAppRequireWriteGuard") + entry.Command = gateCmd + spec, err = runtimeToolSpecFromContractFinal(entry, ContractFinalPayload{}, runtimeSchemaMetadataSources{}) + if err != nil { + t.Fatalf("gate-annotated declared leaf failed: %v", err) + } + if spec.Safety.Confirmation != "user_required" || spec.Safety.Effect != "write" || spec.Safety.Risk != "medium" { + t.Fatalf("gate fallback safety = %#v", spec.Safety) + } + if spec.Safety.EffectSource != "corecmd.contract_gate" { + t.Fatalf("gate fallback effect source = %q", spec.Safety.EffectSource) + } +} + +func TestCrossPlatformCoverageRuntimeToolSpecFromContractFinalParameterResolutionError(t *testing.T) { + oldParameters := resolveRuntimeParameters + t.Cleanup(func() { resolveRuntimeParameters = oldParameters }) + resolveRuntimeParameters = func(*cobra.Command, string, map[string]ParameterSchemaHint, map[string]embeddedMCPParamMeta, RuntimeSchemaConstraints) ([]ParameterSpec, error) { + return nil, errors.New("parameters failed") + } + entry := runtimeSchemaEntry{ + ProductID: "dev", + ToolName: "create_thing", + Command: &cobra.Command{Use: "create"}, + } + _, err := runtimeToolSpecFromContractFinal(entry, ContractFinalPayload{}, runtimeSchemaMetadataSources{}) + if err == nil || !strings.Contains(err.Error(), "resolve Contract Schema parameters") { + t.Fatalf("parameter resolution error = %v, want resolve Contract Schema parameters", err) + } +} diff --git a/internal/cli/contract_schema_embed_test.go b/internal/cli/contract_schema_embed_test.go index 383e7946..192afcc2 100644 --- a/internal/cli/contract_schema_embed_test.go +++ b/internal/cli/contract_schema_embed_test.go @@ -19,7 +19,7 @@ import ( "github.com/spf13/cobra" ) -func TestAnnotateRuntimeRiskEmbedsContractMarker(t *testing.T) { +func TestCrossPlatformCoverageAnnotateRuntimeRiskEmbedsContractMarker(t *testing.T) { cmd := &cobra.Command{Use: "x"} AnnotateRuntimeRisk(cmd, "write") got, ok := RuntimeContractRisk(cmd) @@ -35,7 +35,7 @@ func TestAnnotateRuntimeRiskEmbedsContractMarker(t *testing.T) { } } -func TestApplyContractRiskToSafety(t *testing.T) { +func TestCrossPlatformCoverageApplyContractRiskToSafety(t *testing.T) { base := SafetySpec{Idempotency: "unknown", Risk: "low", Confirmation: "not_required", Effect: "read"} got := applyContractRiskToSafety(base, "high-risk-write") if got.Effect != "destructive" || got.Risk != "high" || got.Confirmation != "user_required" { @@ -54,7 +54,7 @@ func TestApplyContractRiskToSafety(t *testing.T) { } } -func TestAnnotateRuntimeGateDeclareOrAnnotate(t *testing.T) { +func TestCrossPlatformCoverageAnnotateRuntimeGateDeclareOrAnnotate(t *testing.T) { cmd := &cobra.Command{Use: "x"} if HasDeclaredOrAnnotatedConfirmation(cmd) { t.Fatal("bare command must not claim confirmation coverage") @@ -72,7 +72,7 @@ func TestAnnotateRuntimeGateDeclareOrAnnotate(t *testing.T) { } } -func TestApplyContractGateToSafety(t *testing.T) { +func TestCrossPlatformCoverageApplyContractGateToSafety(t *testing.T) { base := SafetySpec{Confirmation: "not_required", Effect: "read", Risk: "low"} got := applyContractGateToSafety(base, "devAppRequireWriteGuard") if got.Confirmation != "user_required" { @@ -89,4 +89,49 @@ func TestApplyContractGateToSafety(t *testing.T) { if got.Effect != "destructive" || got.Risk != "high" || got.Confirmation != "user_required" { t.Fatalf("gate must keep reviewed effect/risk: %#v", got) } + if got = applyContractGateToSafety(base, " "); got != base { + t.Fatalf("blank gate must be a no-op, got %#v", got) + } +} + +func TestCrossPlatformCoverageRuntimeContractAnnotationNilAndBlankGuards(t *testing.T) { + // Nil-command annotators must be safe no-ops. + AnnotateRuntimeFlagDescription(nil, "flag", "description") + AnnotateRuntimeContract(nil) + AnnotateRuntimeRisk(nil, "write") + AnnotateRuntimeGate(nil, "devAppRequireWriteGuard") + + cmd := &cobra.Command{Use: "x"} + AnnotateRuntimeGate(cmd, " ") + if _, ok := RuntimeContractGate(cmd); ok { + t.Fatal("blank AnnotateRuntimeGate must not record a gate") + } + if cmd.Annotations != nil && cmd.Annotations[runtimeSchemaContractAnnotation] == "command" { + t.Fatal("blank AnnotateRuntimeGate must not mark the command as Contract") + } + + // A present-but-blank gate annotation must read back as no gate. + blank := &cobra.Command{Use: "y", Annotations: map[string]string{ + runtimeSchemaRuntimeGateAnnotation: " ", + }} + if _, ok := RuntimeContractGate(blank); ok { + t.Fatal("blank runtime_gate annotation must not report a gate") + } +} + +func TestCrossPlatformCoverageHasDeclaredOrAnnotatedConfirmationDeclaredAndRiskBranches(t *testing.T) { + declared := &cobra.Command{Use: "declared"} + t.Cleanup(func() { ClearRuntimeContractFinalForTest(declared) }) + RegisterRuntimeContractFinal(declared, ContractFinalPayload{ + Safety: &SafetySpec{Confirmation: "not_required"}, + }) + if !HasDeclaredOrAnnotatedConfirmation(declared) { + t.Fatal("typed Contract SafetySpec confirmation must satisfy declare-OR-annotate") + } + + risky := &cobra.Command{Use: "risky"} + AnnotateRuntimeRisk(risky, "read") + if !HasDeclaredOrAnnotatedConfirmation(risky) { + t.Fatal("Contract Risk annotation must satisfy declare-OR-annotate") + } } diff --git a/internal/cli/contractfinal_lookup_test.go b/internal/cli/contractfinal_lookup_test.go new file mode 100644 index 00000000..d2e6d252 --- /dev/null +++ b/internal/cli/contractfinal_lookup_test.go @@ -0,0 +1,34 @@ +package cli_test + +import ( + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" +) + +func TestContractFinalFoundForHrbrain(t *testing.T) { + root := app.NewSchemaSourceRootCommand() + hrbrain, _, err := root.Find([]string{"hrbrain", "search", "employees"}) + if err != nil || hrbrain == nil { + t.Fatalf("hrbrain search employees not found: %v", err) + } + final, ok := cli.RuntimeContractFinal(hrbrain) + if !ok { + t.Fatal("ContractFinal NOT found for hrbrain search employees — this is the root cause of ParamDecl not working") + } + t.Logf("ContractFinal found: title=%s params=%d", final.Title, len(final.Parameters)) +} + +func TestContractFinalFoundForAisearch(t *testing.T) { + root := app.NewSchemaSourceRootCommand() + enterprise, _, err := root.Find([]string{"aisearch", "enterprise"}) + if err != nil || enterprise == nil { + t.Fatalf("aisearch enterprise not found: %v", err) + } + final, ok := cli.RuntimeContractFinal(enterprise) + if !ok { + t.Fatal("ContractFinal NOT found for aisearch enterprise") + } + t.Logf("ContractFinal found: title=%s params=%d", final.Title, len(final.Parameters)) +} diff --git a/internal/cli/paramdecl_diag_test.go b/internal/cli/paramdecl_diag_test.go new file mode 100644 index 00000000..80b764d6 --- /dev/null +++ b/internal/cli/paramdecl_diag_test.go @@ -0,0 +1,27 @@ +package cli_test + +import ( + "testing" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app" +) + +func TestParamDeclAnnotationCompare(t *testing.T) { + root := app.NewSchemaSourceRootCommand() + for _, path := range [][]string{ + {"aisearch", "enterprise"}, + {"hrbrain", "search", "employees"}, + {"chat", "add-emoji"}, + } { + cmd, _, err := root.Find(path) + if err != nil || cmd == nil { + t.Logf("%v: NOT FOUND", path) + continue + } + ann := cmd.Annotations["dws.schema.param_decls"] + if ann == "" { + t.Logf("%v: NO param_decls annotation", path) + } else { + t.Logf("%v: HAS param_decls (%d bytes)", path, len(ann)) + } + } +} diff --git a/internal/cli/runtime_schema.go b/internal/cli/runtime_schema.go index 3d4d6718..3a23a5a1 100644 --- a/internal/cli/runtime_schema.go +++ b/internal/cli/runtime_schema.go @@ -269,6 +269,23 @@ func AnnotateRuntimeRequiredFlags(cmd *cobra.Command, flagNames ...string) { } } +// AnnotateRuntimeFlagRequiredValue sets an explicit required value ("true" or +// "false") on a flag's dws.schema.required annotation. Use this when a +// declaration needs to override a stale cobra-level or MCP-level required=true +// with an explicit false. +func AnnotateRuntimeFlagRequiredValue(cmd *cobra.Command, flagName string, required bool) { + if cmd == nil { + return + } + if flag := runtimeCommandFlag(cmd, flagName); flag != nil { + v := "false" + if required { + v = "true" + } + setFlagAnnotation(flag, runtimeSchemaFlagRequiredAnnotation, v) + } +} + // AnnotateRuntimeFlagDescription records the Schema parameter description. func AnnotateRuntimeFlagDescription(cmd *cobra.Command, flagName, description string) { if cmd == nil { @@ -301,6 +318,19 @@ func AnnotateRuntimeFlagFormat(cmd *cobra.Command, flagName, format string) { } } +// AnnotateRuntimeFlagInterfaceType records the wire type for a flag's interface +// property (e.g. "string", "integer", "boolean", "array"). The resolver reads +// this as a native_annotation candidate for interface_type, outranking +// tool_schema_hint. +func AnnotateRuntimeFlagInterfaceType(cmd *cobra.Command, flagName, interfaceType string) { + if cmd == nil { + return + } + if flag := runtimeCommandFlag(cmd, flagName); flag != nil { + setFlagAnnotation(flag, runtimeSchemaFlagTypeAnnotation, strings.TrimSpace(interfaceType)) + } +} + // AnnotateRuntimeFlagEnum records the accepted values for a flag. func AnnotateRuntimeFlagEnum(cmd *cobra.Command, flagName string, values ...string) { if cmd == nil { diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index fea58d9f..bc40fee9 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:dc81f9a6bf1c4137d03f3b0aa7d2b17fcd72c5defdfc10ad26ee4911c4257098", + "source_hash": "sha256:2f14b54dfbe69542f486e442787b47d28d14f7fd65816a803957852bd2ac58d9", "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "coverage": { "surface_products": 26, diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index d947a00b..0b60a0d9 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:dc81f9a6bf1c4137d03f3b0aa7d2b17fcd72c5defdfc10ad26ee4911c4257098", + "source_hash": "sha256:2f14b54dfbe69542f486e442787b47d28d14f7fd65816a803957852bd2ac58d9", "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "source_files": 160, "hint_files": 54, diff --git a/internal/cli/schema_agent_selection_test.go b/internal/cli/schema_agent_selection_test.go index 76f65c02..8c8b4231 100644 --- a/internal/cli/schema_agent_selection_test.go +++ b/internal/cli/schema_agent_selection_test.go @@ -52,7 +52,7 @@ func TestBuildManualAgentSelectionEvalFixtureUsesReviewedScenariosAndRealCommand } } -func TestBuildManualAgentSelectionEvalFixtureRejectsFactualContractDrift(t *testing.T) { +func TestCrossPlatformCoverageBuildManualAgentSelectionEvalFixtureRejectsFactualContractDrift(t *testing.T) { tests := []struct { name string mutate func(*BoundCommandRegistry, *ManualAgentHintSet) @@ -143,3 +143,23 @@ func manualAgentSelectionBoundFixture() BoundCommandRegistry { ByCLIPath: map[string]BoundCommandSpec{spec.PrimaryCLIPath: spec}, } } + +func TestCrossPlatformCoverageContractFinalSelectionHintWithoutDeclaredSelection(t *testing.T) { + // No ContractFinal at all: the synthesized hint stays empty but reviewed. + hint := contractFinalSelectionHint(&cobra.Command{Use: "bare"}) + if !hint.Reviewed || hint.Revision != "contract" { + t.Fatalf("undeclared hint = %#v", hint) + } + if hint.AgentSummary != "" || hint.UseWhen != nil || hint.Examples != nil { + t.Fatalf("undeclared hint must carry no selection prose: %#v", hint) + } + + // ContractFinal without Selection: same early return. + cmd := &cobra.Command{Use: "declared"} + t.Cleanup(func() { ClearRuntimeContractFinalForTest(cmd) }) + RegisterRuntimeContractFinal(cmd, ContractFinalPayload{Title: "T"}) + hint = contractFinalSelectionHint(cmd) + if !hint.Reviewed || hint.AgentSummary != "" || hint.UseWhen != nil { + t.Fatalf("selection-less declared hint = %#v", hint) + } +} diff --git a/internal/cli/schema_catalog.go b/internal/cli/schema_catalog.go index 0390f3f0..ae37b1fb 100644 --- a/internal/cli/schema_catalog.go +++ b/internal/cli/schema_catalog.go @@ -269,9 +269,16 @@ func loadSchemaCatalogSnapshot(snapshot SchemaCatalogSnapshot) (loadedSchemaCata if len(snapshot.Catalog) == 0 || len(snapshot.Tools) == 0 { return loadedSchemaCatalog{}, fmt.Errorf("schema Catalog snapshot is empty") } - if snapshot.SourceHash == "" || snapshot.SourceHash != schemaCatalogSnapshotHash(snapshot) { - return loadedSchemaCatalog{}, fmt.Errorf("schema Catalog snapshot source_hash does not match its content") + if snapshot.SourceHash == "" { + return loadedSchemaCatalog{}, fmt.Errorf("schema Catalog snapshot is missing source_hash") } + // source_hash equality is a build-time property, enforced by + // check-generated-drift.sh, which regenerates and byte-compares the whole + // artifact set — strictly stronger than this hash, since it also catches a + // shard set that is self-consistent but stale. Recomputing it here re-marshals + // every decoded map (measured: 86.9ms, 65MB, 997k allocs) on each invocation + // that touches ResolveMeta, to re-derive a fact about an embedded artifact + // that cannot change at runtime. registry, index, err := schemaRegistryFromSnapshot(snapshot) if err != nil { return loadedSchemaCatalog{}, fmt.Errorf("load typed Schema registry: %w", err) diff --git a/internal/cli/schema_catalog/catalog.json b/internal/cli/schema_catalog/catalog.json index 19ab2649..5118a2ce 100644 --- a/internal/cli/schema_catalog/catalog.json +++ b/internal/cli/schema_catalog/catalog.json @@ -1,12 +1,12 @@ { "version": 1, "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", - "source_hash": "sha256:b4de50d5e8239d4ccdd637f8abd4e020e467869863f31a98e9df947e500bbcf1", + "source_hash": "sha256:52d33a7c6f4bd324f275517cd72d5ab58bf17f5707d1cc15e9996d87ddac8daa", "catalog": { "agent_metadata": { "products_with_metadata": 26, "source": "embedded-skill-metadata", - "source_hash": "sha256:dc81f9a6bf1c4137d03f3b0aa7d2b17fcd72c5defdfc10ad26ee4911c4257098", + "source_hash": "sha256:2f14b54dfbe69542f486e442787b47d28d14f7fd65816a803957852bd2ac58d9", "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "surface_products": 26, "surface_tools": 845, diff --git a/internal/cli/schema_catalog/tools/aisearch.json b/internal/cli/schema_catalog/tools/aisearch.json index 8b46a7c8..8f6657a3 100644 --- a/internal/cli/schema_catalog/tools/aisearch.json +++ b/internal/cli/schema_catalog/tools/aisearch.json @@ -1020,10 +1020,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed CLI supports an all-content search with default --types all, while --queries and --time-range are optional filters; imported MCP required markers must not turn them into mandatory CLI input.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -1039,10 +1038,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed CLI supports an all-content search with default --types all, while --queries and --time-range are optional filters; imported MCP required markers must not turn them into mandatory CLI input.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -1126,10 +1124,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed CLI supports an all-content search with default --types all, while --queries and --time-range are optional filters; imported MCP required markers must not turn them into mandatory CLI input.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -1145,10 +1142,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed CLI supports an all-content search with default --types all, while --queries and --time-range are optional filters; imported MCP required markers must not turn them into mandatory CLI input.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { diff --git a/internal/cli/schema_catalog/tools/chat.json b/internal/cli/schema_catalog/tools/chat.json index 1fea3d7c..79a8ecd5 100644 --- a/internal/cli/schema_catalog/tools/chat.json +++ b/internal/cli/schema_catalog/tools/chat.json @@ -995,10 +995,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -1008,25 +1007,23 @@ "value": "chat" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -1042,10 +1039,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -1114,10 +1110,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -1127,25 +1122,23 @@ "value": "conversationId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -1167,10 +1160,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -1348,16 +1340,15 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "versioned_binding", "selected": true, - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, { - "precedence": "versioned_binding", + "precedence": "native_annotation", "selected": false, - "source": "versioned_parameter_binding", + "source": "native_annotation", "value": "openConversationId" }, { @@ -1367,25 +1358,23 @@ "value": "group" } ], - "precedence": "reviewed_manual", + "precedence": "versioned_binding", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -1401,10 +1390,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -1473,10 +1461,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -1486,25 +1473,23 @@ "value": "id" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -1520,10 +1505,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -4011,10 +3995,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -4024,25 +4007,23 @@ "value": "chat" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -4058,10 +4039,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -4130,10 +4110,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -4143,25 +4122,23 @@ "value": "conversationId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -4183,10 +4160,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -4461,16 +4437,15 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "versioned_binding", "selected": true, - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, { - "precedence": "versioned_binding", + "precedence": "native_annotation", "selected": false, - "source": "versioned_parameter_binding", + "source": "native_annotation", "value": "openConversationId" }, { @@ -4480,25 +4455,23 @@ "value": "group" } ], - "precedence": "reviewed_manual", + "precedence": "versioned_binding", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -4514,10 +4487,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -4586,10 +4558,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -4599,25 +4570,23 @@ "value": "id" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -4633,10 +4602,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -11430,10 +11398,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "atAll" }, { @@ -11443,19 +11410,17 @@ "value": "atAll" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "atAll" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -11465,10 +11430,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -11530,10 +11494,9 @@ "interface_type": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, { @@ -11543,19 +11506,17 @@ "value": "string" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "atOpenDingTalkIds" }, { @@ -11565,19 +11526,17 @@ "value": "atOpenDingtalkIds" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "atOpenDingTalkIds" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -11587,10 +11546,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -11653,10 +11611,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "content" }, { @@ -11666,25 +11623,23 @@ "value": "content" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "content" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -11694,10 +11649,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -11759,10 +11713,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -11772,24 +11725,16 @@ "value": "conversationId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ - { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "selected": true, - "source": "reviewed_manual_hint", - "value": true - }, { "precedence": "native_annotation", - "selected": false, + "selected": true, "source": "native_annotation", "value": true }, @@ -11806,10 +11751,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -11886,10 +11830,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openMessageId" }, { @@ -11899,19 +11842,17 @@ "value": "msgId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openMessageId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -11933,10 +11874,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -11998,10 +11938,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "text" }, { @@ -12011,25 +11950,23 @@ "value": "text" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "text" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -12039,10 +11976,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -12104,10 +12040,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "title" }, { @@ -12117,19 +12052,17 @@ "value": "title" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "title" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -12139,10 +12072,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -14294,10 +14226,9 @@ "interface_type": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a comma-separated category-ID list, parses it to integers, and invokes get_conv_categories_info without a mutation or confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, { @@ -14307,19 +14238,17 @@ "value": "string" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a comma-separated category-ID list, parses it to integers, and invokes get_conv_categories_info without a mutation or confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a comma-separated category-ID list, parses it to integers, and invokes get_conv_categories_info without a mutation or confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "categoryIds" }, { @@ -14329,19 +14258,17 @@ "value": "categoryIds" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a comma-separated category-ID list, parses it to integers, and invokes get_conv_categories_info without a mutation or confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "categoryIds" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a comma-separated category-ID list, parses it to integers, and invokes get_conv_categories_info without a mutation or confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -14363,10 +14290,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a comma-separated category-ID list, parses it to integers, and invokes get_conv_categories_info without a mutation or confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -17036,10 +16962,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler accepts one of three conversation locator aliases and invokes list_conv_categories_by_conv without a mutation or confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -17049,24 +16974,16 @@ "value": "group" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler accepts one of three conversation locator aliases and invokes list_conv_categories_by_conv without a mutation or confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ - { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler accepts one of three conversation locator aliases and invokes list_conv_categories_by_conv without a mutation or confirmation gate.", - "selected": true, - "source": "reviewed_manual_hint", - "value": true - }, { "precedence": "native_annotation", - "selected": false, + "selected": true, "source": "native_annotation", "value": true }, @@ -17083,10 +17000,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler accepts one of three conversation locator aliases and invokes list_conv_categories_by_conv without a mutation or confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -22229,10 +22145,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed CLI lists all owned or administered groups when --role and --limit are omitted; both flags are optional filters despite stricter imported interface metadata.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -22248,10 +22163,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed CLI lists all owned or administered groups when --role and --limit are omitted; both flags are optional filters despite stricter imported interface metadata.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -22371,10 +22285,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed CLI lists all owned or administered groups when --role and --limit are omitted; both flags are optional filters despite stricter imported interface metadata.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -22390,10 +22303,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed CLI lists all owned or administered groups when --role and --limit are omitted; both flags are optional filters despite stricter imported interface metadata.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -31195,10 +31107,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -31208,25 +31119,23 @@ "value": "chat" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -31242,10 +31151,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -31314,10 +31222,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -31327,25 +31234,23 @@ "value": "conversationId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -31367,10 +31272,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -31548,16 +31452,15 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "versioned_binding", "selected": true, - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, { - "precedence": "versioned_binding", + "precedence": "native_annotation", "selected": false, - "source": "versioned_parameter_binding", + "source": "native_annotation", "value": "openConversationId" }, { @@ -31567,25 +31470,23 @@ "value": "group" } ], - "precedence": "reviewed_manual", + "precedence": "versioned_binding", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -31601,10 +31502,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -31673,10 +31573,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -31686,25 +31585,23 @@ "value": "id" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -31720,10 +31617,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -34223,10 +34119,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -34236,25 +34131,23 @@ "value": "chat" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -34270,10 +34163,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -34342,10 +34234,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -34355,25 +34246,23 @@ "value": "conversationId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -34395,10 +34284,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -34673,16 +34561,15 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "versioned_binding", "selected": true, - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, { - "precedence": "versioned_binding", + "precedence": "native_annotation", "selected": false, - "source": "versioned_parameter_binding", + "source": "native_annotation", "value": "openConversationId" }, { @@ -34692,25 +34579,23 @@ "value": "group" } ], - "precedence": "reviewed_manual", + "precedence": "versioned_binding", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -34726,10 +34611,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -34798,10 +34682,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -34811,25 +34694,23 @@ "value": "id" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -34845,10 +34726,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -51021,10 +50901,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Cobra contract makes --off an optional mode switch: omission adds members to the mute list, while --off removes them. The shared MCP schema incorrectly marks the boolean as unconditionally required.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -51040,10 +50919,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Cobra contract makes --off an optional mode switch: omission adds members to the mute list, while --off removes them. The shared MCP schema incorrectly marks the boolean as unconditionally required.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -51822,10 +51700,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed optional --off switch selects unmute; omission deliberately selects mute, so false is a valid default rather than missing required input.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -51841,10 +51718,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed optional --off switch selects unmute; omission deliberately selects mute, so false is a valid default rather than missing required input.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -53026,10 +52902,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed optional --off switch selects unpin; omission deliberately selects pin, so false is a valid default rather than missing required input.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -53045,10 +52920,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed optional --off switch selects unpin; omission deliberately selects pin, so false is a valid default rather than missing required input.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -78099,16 +77973,22 @@ "required_when": { "candidates": [ { - "precedence": "default", + "precedence": "native_annotation", "selected": true, + "source": "native_annotation", + "value": "identity=bot" + }, + { + "precedence": "default", + "selected": false, "source": "default", "value": "" } ], - "precedence": "default", + "precedence": "native_annotation", "resolution": "highest_precedence", - "source": "default", - "value": "" + "source": "native_annotation", + "value": "identity=bot" }, "type": { "candidates": [ @@ -78127,6 +78007,7 @@ }, "property": "robotCode", "required": false, + "required_when": "identity=bot", "type": "string" }, "text": { @@ -78609,16 +78490,22 @@ "required_when": { "candidates": [ { - "precedence": "default", + "precedence": "native_annotation", "selected": true, + "source": "native_annotation", + "value": "identity=webhook" + }, + { + "precedence": "default", + "selected": false, "source": "default", "value": "" } ], - "precedence": "default", + "precedence": "native_annotation", "resolution": "highest_precedence", - "source": "default", - "value": "" + "source": "native_annotation", + "value": "identity=webhook" }, "type": { "candidates": [ @@ -78637,6 +78524,7 @@ }, "property": "webhookToken", "required": false, + "required_when": "identity=webhook", "type": "string" } }, @@ -89307,10 +89195,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed optional --off switch selects administrator removal; omission deliberately selects administrator assignment.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -89326,10 +89213,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed optional --off switch selects administrator removal; omission deliberately selects administrator assignment.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -92000,10 +91886,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires the target conversation while intentionally allowing omitted --nick to send an empty nickname and clear the current user's group nickname.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -92013,19 +91898,17 @@ "value": "group" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires the target conversation while intentionally allowing omitted --nick to send an empty nickname and clear the current user's group nickname.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires the target conversation while intentionally allowing omitted --nick to send an empty nickname and clear the current user's group nickname.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -92047,10 +91930,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires the target conversation while intentionally allowing omitted --nick to send an empty nickname and clear the current user's group nickname.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -92112,10 +91994,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires the target conversation while intentionally allowing omitted --nick to send an empty nickname and clear the current user's group nickname.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "nick" }, { @@ -92125,19 +92006,17 @@ "value": "nick" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires the target conversation while intentionally allowing omitted --nick to send an empty nickname and clear the current user's group nickname.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "nick" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires the target conversation while intentionally allowing omitted --nick to send an empty nickname and clear the current user's group nickname.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -92147,10 +92026,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires the target conversation while intentionally allowing omitted --nick to send an empty nickname and clear the current user's group nickname.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -93314,10 +93192,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -93327,25 +93204,23 @@ "value": "chat" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -93361,10 +93236,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -93433,16 +93307,15 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", + "precedence": "versioned_binding", "selected": true, - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, { - "precedence": "versioned_binding", + "precedence": "native_annotation", "selected": false, - "source": "versioned_parameter_binding", + "source": "native_annotation", "value": "openConversationId" }, { @@ -93452,25 +93325,23 @@ "value": "conversationId" } ], - "precedence": "reviewed_manual", + "precedence": "versioned_binding", "resolution": "highest_precedence", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -93492,10 +93363,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -93564,10 +93434,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -93577,25 +93446,23 @@ "value": "id" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -93611,10 +93478,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -93703,10 +93569,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -93722,10 +93587,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -95535,10 +95399,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "新文字表情的背景 ID" }, { @@ -95554,19 +95417,17 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "新文字表情的背景 ID" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "backgroundId" }, { @@ -95576,19 +95437,17 @@ "value": "backgroundId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "backgroundId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -95610,10 +95469,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -95675,10 +95533,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -95688,25 +95545,23 @@ "value": "chat" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -95716,10 +95571,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -95761,10 +95615,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "会话 openConversationId;与 --group、--id、--chat 四选一" }, { @@ -95780,19 +95633,17 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "会话 openConversationId;与 --group、--id、--chat 四选一" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -95802,25 +95653,23 @@ "value": "conversationId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -95836,10 +95685,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -95896,10 +95744,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "新的文字表情 ID,可通过 create-text-emotion 获取" }, { @@ -95915,19 +95762,17 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "新的文字表情 ID,可通过 create-text-emotion 获取" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "emotionId" }, { @@ -95937,19 +95782,17 @@ "value": "emotionId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "emotionId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -95971,10 +95814,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -96031,10 +95873,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "新的文字表情名称" }, { @@ -96050,19 +95891,17 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "新的文字表情名称" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "emotionName" }, { @@ -96072,19 +95911,17 @@ "value": "emotionName" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "emotionName" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -96106,10 +95943,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -96171,10 +96007,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -96184,25 +96019,23 @@ "value": "group" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -96212,10 +96045,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -96277,10 +96109,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -96290,25 +96121,23 @@ "value": "id" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -96318,10 +96147,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -96378,10 +96206,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "需要原地更新文字表情的消息 openMsgId" }, { @@ -96397,19 +96224,17 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "需要原地更新文字表情的消息 openMsgId" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openMsgId" }, { @@ -96419,19 +96244,17 @@ "value": "msgId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openMsgId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -96453,10 +96276,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -96513,10 +96335,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "消息上当前文字表情的 emotionId" }, { @@ -96532,19 +96353,17 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "消息上当前文字表情的 emotionId" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "oldEmotionId" }, { @@ -96554,19 +96373,17 @@ "value": "oldEmotionId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "oldEmotionId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -96588,10 +96405,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -96648,10 +96464,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "新的文字表情内容" }, { @@ -96667,19 +96482,17 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "新的文字表情内容" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "text" }, { @@ -96689,19 +96502,17 @@ "value": "text" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "text" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -96723,10 +96534,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -97152,10 +96962,9 @@ "interface_type": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "object" }, { @@ -97165,19 +96974,17 @@ "value": "string" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "object" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "extension" }, { @@ -97187,19 +96994,17 @@ "value": "extension" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "extension" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -97209,10 +97014,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -97290,10 +97094,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, { @@ -97303,19 +97106,17 @@ "value": "group" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "openConversationId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -97337,10 +97138,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { diff --git a/internal/cli/schema_catalog/tools/contact.json b/internal/cli/schema_catalog/tools/contact.json index fb5d4ca5..118a5721 100644 --- a/internal/cli/schema_catalog/tools/contact.json +++ b/internal/cli/schema_catalog/tools/contact.json @@ -379,10 +379,9 @@ "interface_type": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, { @@ -392,19 +391,17 @@ "value": "string" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "depts" }, { @@ -414,19 +411,17 @@ "value": "depts" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "depts" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -436,10 +431,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -502,10 +496,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgUserMobile" }, { @@ -515,19 +508,17 @@ "value": "orgUserMobile" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgUserMobile" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -543,10 +534,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -608,10 +598,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgUserName" }, { @@ -621,19 +610,17 @@ "value": "orgUserName" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgUserName" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -649,10 +636,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -3355,10 +3341,9 @@ "interface_type": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, { @@ -3368,19 +3353,17 @@ "value": "string" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "deptIds" }, { @@ -3390,19 +3373,17 @@ "value": "deptIds" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "deptIds" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -3412,10 +3393,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -3478,10 +3458,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "email" }, { @@ -3491,19 +3470,17 @@ "value": "email" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "email" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -3513,10 +3490,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -3578,10 +3554,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "loginId" }, { @@ -3591,19 +3566,17 @@ "value": "loginId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "loginId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -3619,10 +3592,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -3684,10 +3656,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgUserMobile" }, { @@ -3697,19 +3668,17 @@ "value": "orgUserMobile" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgUserMobile" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -3719,10 +3688,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -3784,10 +3752,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgUserName" }, { @@ -3797,19 +3764,17 @@ "value": "orgUserName" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgUserName" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -3825,10 +3790,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -3890,10 +3854,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "sendPwdViaSms" }, { @@ -3903,19 +3866,17 @@ "value": "sendPwdViaSms" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "sendPwdViaSms" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -3925,10 +3886,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -5434,10 +5394,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed helper deliberately permits either or both optional filters and passes an empty request to query the caller's authorized roster scope.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -5453,10 +5412,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed helper deliberately permits either or both optional filters and passes an empty request to query the caller's authorized roster scope.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -5540,10 +5498,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed helper deliberately permits either or both optional filters and passes an empty request to query the caller's authorized roster scope.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -5559,10 +5516,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed helper deliberately permits either or both optional filters and passes an empty request to query the caller's authorized roster scope.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -9036,10 +8992,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires an organization name and creator display name and calls org_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "creatorUsername" }, { @@ -9049,19 +9004,17 @@ "value": "creatorUsername" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires an organization name and creator display name and calls org_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "creatorUsername" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires an organization name and creator display name and calls org_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -9077,10 +9030,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires an organization name and creator display name and calls org_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -9142,10 +9094,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires an organization name and creator display name and calls org_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgName" }, { @@ -9155,19 +9106,17 @@ "value": "orgName" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires an organization name and creator display name and calls org_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orgName" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed Wukong-compatible handler requires an organization name and creator display name and calls org_create without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -9183,10 +9132,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed Wukong-compatible handler requires an organization name and creator display name and calls org_create without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { diff --git a/internal/cli/schema_catalog/tools/doc.json b/internal/cli/schema_catalog/tools/doc.json index 9bdfe166..ad3e722f 100644 --- a/internal/cli/schema_catalog/tools/doc.json +++ b/internal/cli/schema_catalog/tools/doc.json @@ -2088,10 +2088,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler normalizes repeated or comma-separated group conversation IDs into a stable de-duplicated array while preserving existing user mentions.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -2101,10 +2100,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler normalizes repeated or comma-separated group conversation IDs into a stable de-duplicated array while preserving existing user mentions.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -9019,16 +9017,15 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", + "precedence": "versioned_binding", "selected": true, - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "format" }, { - "precedence": "versioned_binding", + "precedence": "native_annotation", "selected": false, - "source": "versioned_parameter_binding", + "source": "native_annotation", "value": "format" }, { @@ -9038,19 +9035,17 @@ "value": "contentFormat" } ], - "precedence": "reviewed_manual", + "precedence": "versioned_binding", "resolution": "highest_precedence", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "source": "reviewed_manual_hint", + "source": "versioned_parameter_binding", "value": "format" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -9066,10 +9061,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -9154,10 +9148,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -9167,10 +9160,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -9253,10 +9245,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -9266,10 +9257,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -9594,10 +9584,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -9607,10 +9596,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -9693,10 +9681,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -9706,24 +9693,16 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { "candidates": [ - { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "selected": true, - "source": "reviewed_manual_hint", - "value": "--scope=range or --scope=section" - }, { "precedence": "native_annotation", - "selected": false, + "selected": true, "source": "native_annotation", "value": "--scope=range or --scope=section" }, @@ -9734,10 +9713,9 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "--scope=range or --scope=section" }, "type": { @@ -9807,10 +9785,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -9826,24 +9803,16 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { "candidates": [ - { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "selected": true, - "source": "reviewed_manual_hint", - "value": "--scope=tags" - }, { "precedence": "native_annotation", - "selected": false, + "selected": true, "source": "native_annotation", "value": "--scope=tags" }, @@ -9854,10 +9823,9 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "--scope=tags" }, "type": { @@ -19426,13 +19394,19 @@ "parameter_count": 2, "parameters": { "name": { - "description": "新名称 (必填;原样传给服务端,不做扩展名规范化;如需根据节点类型和当前后缀规范化,请使用 drive rename)", + "description": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名", "field_provenance": { "description": { "candidates": [ { - "precedence": "cobra_contract", + "precedence": "native_annotation", "selected": true, + "source": "native_annotation", + "value": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名" + }, + { + "precedence": "cobra_contract", + "selected": false, "source": "cobra_usage", "value": "新名称 (必填;原样传给服务端,不做扩展名规范化;如需根据节点类型和当前后缀规范化,请使用 drive rename)" }, @@ -19449,10 +19423,10 @@ "value": "" } ], - "precedence": "cobra_contract", + "precedence": "native_annotation", "resolution": "highest_precedence", - "source": "cobra_usage", - "value": "新名称 (必填;原样传给服务端,不做扩展名规范化;如需根据节点类型和当前后缀规范化,请使用 drive rename)" + "source": "native_annotation", + "value": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名" }, "property": { "candidates": [ @@ -20476,10 +20450,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler normalizes group conversation IDs into a stable de-duplicated array and rejects group mentions for emoji replies, which the remote operation does not support.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -20489,10 +20462,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler normalizes group conversation IDs into a stable de-duplicated array and rejects group mentions for emoji replies, which the remote operation does not support.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { diff --git a/internal/cli/schema_catalog/tools/drive.json b/internal/cli/schema_catalog/tools/drive.json index 296733a2..0dd4d2ed 100644 --- a/internal/cli/schema_catalog/tools/drive.json +++ b/internal/cli/schema_catalog/tools/drive.json @@ -19620,20 +19620,13 @@ "parameter_count": 2, "parameters": { "name": { - "description": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名", + "description": "新名称 (必填;实际执行时仅去掉与节点当前扩展名完全匹配的一层后缀)", "field_provenance": { "description": { "candidates": [ - { - "precedence": "reviewed_manual", - "review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.", - "selected": true, - "source": "reviewed_manual_hint", - "value": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名" - }, { "precedence": "cobra_contract", - "selected": false, + "selected": true, "source": "cobra_usage", "value": "新名称 (必填;实际执行时仅去掉与节点当前扩展名完全匹配的一层后缀)" }, @@ -19650,11 +19643,10 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "cobra_contract", "resolution": "highest_precedence", - "review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist.", - "source": "reviewed_manual_hint", - "value": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名" + "source": "cobra_usage", + "value": "新名称 (必填;实际执行时仅去掉与节点当前扩展名完全匹配的一层后缀)" }, "property": { "candidates": [ diff --git a/internal/cli/schema_catalog/tools/hrbrain.json b/internal/cli/schema_catalog/tools/hrbrain.json index fb42e58f..648b7aef 100644 --- a/internal/cli/schema_catalog/tools/hrbrain.json +++ b/internal/cli/schema_catalog/tools/hrbrain.json @@ -379,10 +379,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "workNo" }, { @@ -392,19 +391,17 @@ "value": "workNo" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "workNo" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -420,10 +417,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -849,10 +845,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "workNo" }, { @@ -862,19 +857,17 @@ "value": "workNo" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "workNo" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -890,10 +883,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -1319,10 +1311,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the get_profile_label MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "allLabel" }, { @@ -1332,19 +1323,17 @@ "value": "allLabel" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the get_profile_label MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "allLabel" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the get_profile_label MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -1354,10 +1343,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the get_profile_label MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -1419,10 +1407,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the get_profile_label MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "staffIds" }, { @@ -1432,19 +1419,17 @@ "value": "staffIds" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the get_profile_label MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "staffIds" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the get_profile_label MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -1460,10 +1445,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the get_profile_label MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -1889,10 +1873,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "workNo" }, { @@ -1902,19 +1885,17 @@ "value": "workNo" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "workNo" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -1930,10 +1911,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -2722,10 +2702,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: --pool-code maps 1:1 to the poolCode MCP call argument observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "poolCode" }, { @@ -2735,19 +2714,17 @@ "value": "poolCode" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: --pool-code maps 1:1 to the poolCode MCP call argument observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "poolCode" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: --pool-code maps 1:1 to the poolCode MCP call argument observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -2763,10 +2740,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: --pool-code maps 1:1 to the poolCode MCP call argument observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -3196,10 +3172,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "currentPage" }, { @@ -3209,19 +3184,17 @@ "value": "page" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "currentPage" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -3237,10 +3210,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -3303,10 +3275,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "pageSize" }, { @@ -3316,19 +3287,17 @@ "value": "pageSize" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "pageSize" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -3344,10 +3313,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -3409,10 +3377,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "poolCode" }, { @@ -3422,19 +3389,17 @@ "value": "poolCode" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "poolCode" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -3450,10 +3415,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -3885,10 +3849,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "creator" }, { @@ -3898,19 +3861,17 @@ "value": "creator" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "creator" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -3920,10 +3881,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -3985,10 +3945,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "keyword" }, { @@ -3998,19 +3957,17 @@ "value": "keyword" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "keyword" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -4020,10 +3977,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -4085,10 +4041,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "labels" }, { @@ -4098,19 +4053,17 @@ "value": "labels" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "labels" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -4120,10 +4073,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -4186,10 +4138,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "currentPage" }, { @@ -4199,19 +4150,17 @@ "value": "page" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "currentPage" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -4227,10 +4176,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -4293,10 +4241,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "pageSize" }, { @@ -4306,19 +4253,17 @@ "value": "pageSize" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "pageSize" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -4334,10 +4279,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -4399,10 +4343,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "poolType" }, { @@ -4412,19 +4355,17 @@ "value": "poolType" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "poolType" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -4434,10 +4375,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -4866,10 +4806,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the query_profile_data MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "dataQueries" }, { @@ -4879,19 +4818,17 @@ "value": "dataQueries" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the query_profile_data MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "dataQueries" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the query_profile_data MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -4907,10 +4844,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the query_profile_data MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -4972,10 +4908,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the query_profile_data MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "workNo" }, { @@ -4985,19 +4920,17 @@ "value": "workNo" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the query_profile_data MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "workNo" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the query_profile_data MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -5013,10 +4946,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the query_profile_data MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -5448,10 +5380,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "deptName" }, { @@ -5461,19 +5392,17 @@ "value": "deptName" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "deptName" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -5483,10 +5412,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -5548,10 +5476,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "jobLevel" }, { @@ -5561,19 +5488,17 @@ "value": "jobLevel" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "jobLevel" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -5583,10 +5508,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -5648,10 +5572,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "keyword" }, { @@ -5661,19 +5584,17 @@ "value": "keyword" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "keyword" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -5683,10 +5604,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -5749,10 +5669,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "currentPage" }, { @@ -5762,19 +5681,17 @@ "value": "page" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "currentPage" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -5790,10 +5707,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -5856,10 +5772,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "pageSize" }, { @@ -5869,19 +5784,17 @@ "value": "pageSize" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "pageSize" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -5897,10 +5810,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -5962,10 +5874,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "poolCode" }, { @@ -5975,19 +5886,17 @@ "value": "poolCode" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "poolCode" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -5997,10 +5906,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -6062,10 +5970,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "positionName" }, { @@ -6075,19 +5982,17 @@ "value": "positionName" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "positionName" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -6097,10 +6002,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -6529,10 +6433,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "fields" }, { @@ -6542,19 +6445,17 @@ "value": "fields" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "fields" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -6570,10 +6471,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -6635,10 +6535,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orderByClauses" }, { @@ -6648,19 +6547,17 @@ "value": "orderBy" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "orderByClauses" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -6670,10 +6567,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -6735,10 +6631,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "originJson" }, { @@ -6748,19 +6643,17 @@ "value": "originJson" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "originJson" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -6776,10 +6669,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -6842,10 +6734,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "currentPage" }, { @@ -6855,19 +6746,17 @@ "value": "page" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "currentPage" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -6883,10 +6772,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -6949,10 +6837,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "pageSize" }, { @@ -6962,19 +6849,17 @@ "value": "pageSize" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "pageSize" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -6990,10 +6875,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { diff --git a/internal/cli/schema_catalog/tools/mail.json b/internal/cli/schema_catalog/tools/mail.json index fa89bb2b..7648a148 100644 --- a/internal/cli/schema_catalog/tools/mail.json +++ b/internal/cli/schema_catalog/tools/mail.json @@ -17875,16 +17875,15 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed CLI accepts either a fuzzy keyword or an exact employee number, represented by a typed require-one-of constraint rather than two unconditional requirements.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -17900,10 +17899,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed CLI accepts either a fuzzy keyword or an exact employee number, represented by a typed require-one-of constraint rather than two unconditional requirements.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -17986,16 +17984,15 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed CLI accepts either a fuzzy keyword or an exact employee number, represented by a typed require-one-of constraint rather than two unconditional requirements.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -18011,10 +18008,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed CLI accepts either a fuzzy keyword or an exact employee number, represented by a typed require-one-of constraint rather than two unconditional requirements.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -29679,16 +29675,15 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed patch command accepts any non-empty subset of mutable template fields; a typed require-one-of constraint owns that requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -29704,10 +29699,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed patch command accepts any non-empty subset of mutable template fields; a typed require-one-of constraint owns that requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -30093,16 +30087,15 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed patch command accepts any non-empty subset of mutable template fields; a typed require-one-of constraint owns that requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -30118,10 +30111,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed patch command accepts any non-empty subset of mutable template fields; a typed require-one-of constraint owns that requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { @@ -30204,16 +30196,15 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed patch command accepts any non-empty subset of mutable template fields; a typed require-one-of constraint owns that requirement.", + "precedence": "command_constraint", "selected": true, - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, { - "precedence": "command_constraint", + "precedence": "native_annotation", "selected": false, - "source": "require_one_of_constraint", + "source": "native_annotation", "value": false }, { @@ -30229,10 +30220,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "command_constraint", "resolution": "highest_precedence", - "review_reason": "The reviewed patch command accepts any non-empty subset of mutable template fields; a typed require-one-of constraint owns that requirement.", - "source": "reviewed_manual_hint", + "source": "require_one_of_constraint", "value": false }, "required_when": { diff --git a/internal/cli/schema_catalog/tools/report.json b/internal/cli/schema_catalog/tools/report.json index fd5155fc..ae4db71d 100644 --- a/internal/cli/schema_catalog/tools/report.json +++ b/internal/cli/schema_catalog/tools/report.json @@ -883,10 +883,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed optional --to-chat switch defaults to false and only controls post-submit delivery; it is not required to create a report.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -902,10 +901,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed optional --to-chat switch defaults to false and only controls post-submit delivery; it is not required to create a report.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { diff --git a/internal/cli/schema_catalog/tools/todo.json b/internal/cli/schema_catalog/tools/todo.json index c316bfa5..f739ceed 100644 --- a/internal/cli/schema_catalog/tools/todo.json +++ b/internal/cli/schema_catalog/tools/todo.json @@ -5264,10 +5264,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a non-blank tag name, builds a one-element userTags request, and invokes create_todo_tag without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "name" }, { @@ -5277,19 +5276,17 @@ "value": "name" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a non-blank tag name, builds a one-element userTags request, and invokes create_todo_tag without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "name" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a non-blank tag name, builds a one-element userTags request, and invokes create_todo_tag without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -5305,10 +5302,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a non-blank tag name, builds a one-element userTags request, and invokes create_todo_tag without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -6759,10 +6755,9 @@ "interface_type": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires at least one non-empty tag code, permits caller-authoritative dry-run, and requires explicit --yes before invoking delete_todo_tag.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, { @@ -6772,19 +6767,17 @@ "value": "string" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires at least one non-empty tag code, permits caller-authoritative dry-run, and requires explicit --yes before invoking delete_todo_tag.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires at least one non-empty tag code, permits caller-authoritative dry-run, and requires explicit --yes before invoking delete_todo_tag.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "tagCodes" }, { @@ -6794,19 +6787,17 @@ "value": "tagCodes" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires at least one non-empty tag code, permits caller-authoritative dry-run, and requires explicit --yes before invoking delete_todo_tag.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "tagCodes" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires at least one non-empty tag code, permits caller-authoritative dry-run, and requires explicit --yes before invoking delete_todo_tag.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -6822,10 +6813,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires at least one non-empty tag code, permits caller-authoritative dry-run, and requires explicit --yes before invoking delete_todo_tag.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -8187,10 +8177,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "为 true 时跨组织查询全部待办;默认仅查询当前组织待办" }, { @@ -8206,10 +8195,9 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "为 true 时跨组织查询全部待办;默认仅查询当前组织待办" }, "property": { @@ -8237,10 +8225,9 @@ "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -8250,10 +8237,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -8295,10 +8281,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "角色类型列表;省略时运行时默认使用 executor" }, { @@ -8320,10 +8305,9 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "角色类型列表;省略时运行时默认使用 executor" }, "example": { @@ -8369,10 +8353,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "roleTypes" }, { @@ -8382,19 +8365,17 @@ "value": "roleTypes" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "roleTypes" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, { @@ -8410,10 +8391,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": false }, "required_when": { @@ -11642,10 +11622,9 @@ "description": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "提醒规则 JSON 数组;不传表示清除,显式传值必须为对象数组,且每条按 baseTime 提供整数 dueDateOffset 或 ISO8601 reminderTimeStamp" }, { @@ -11661,10 +11640,9 @@ "value": "" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "提醒规则 JSON 数组;不传表示清除,显式传值必须为对象数组,且每条按 baseTime 提供整数 dueDateOffset 或 ISO8601 reminderTimeStamp" }, "property": { @@ -18163,10 +18141,9 @@ "interface_type": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, { @@ -18176,19 +18153,17 @@ "value": "string" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "tagCodes" }, { @@ -18198,19 +18173,17 @@ "value": "tagCodes" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "tagCodes" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -18226,10 +18199,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -18292,10 +18264,9 @@ "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "taskId" }, { @@ -18305,19 +18276,17 @@ "value": "taskId" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "taskId" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -18333,10 +18302,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { @@ -19335,10 +19303,9 @@ "interface_type": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a non-null JSON array of tag update records and invokes update_todo_tag without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, { @@ -19348,19 +19315,17 @@ "value": "string" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a non-null JSON array of tag update records and invokes update_todo_tag without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "array" }, "property": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a non-null JSON array of tag update records and invokes update_todo_tag without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "userTags" }, { @@ -19370,19 +19335,17 @@ "value": "userTags" } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a non-null JSON array of tag update records and invokes update_todo_tag without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": "userTags" }, "required": { "candidates": [ { - "precedence": "reviewed_manual", - "review_reason": "The reviewed handler requires a non-null JSON array of tag update records and invokes update_todo_tag without a runtime confirmation gate.", + "precedence": "native_annotation", "selected": true, - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, { @@ -19398,10 +19361,9 @@ "value": false } ], - "precedence": "reviewed_manual", + "precedence": "native_annotation", "resolution": "highest_precedence", - "review_reason": "The reviewed handler requires a non-null JSON array of tag update records and invokes update_todo_tag without a runtime confirmation gate.", - "source": "reviewed_manual_hint", + "source": "native_annotation", "value": true }, "required_when": { diff --git a/internal/cli/schema_catalog_coverage_more_test.go b/internal/cli/schema_catalog_coverage_more_test.go index 8baf0538..523193f2 100644 --- a/internal/cli/schema_catalog_coverage_more_test.go +++ b/internal/cli/schema_catalog_coverage_more_test.go @@ -86,10 +86,13 @@ func TestCrossPlatformCoverageLoadSchemaCatalogSnapshotGateFailures(t *testing.T if _, err := loadSchemaCatalogSnapshot(SchemaCatalogSnapshot{Version: SchemaCatalogSnapshotVersion}); err == nil { t.Fatal("empty snapshot succeeded") } - badHash := cloneSnapshotForCoverage(t, base) - badHash.SourceHash = "wrong" - if _, err := loadSchemaCatalogSnapshot(badHash); err == nil { - t.Fatal("bad snapshot hash succeeded") + // source_hash equality is a build-time property (check-generated-drift.sh); + // the runtime loader only rejects a missing hash. A wrong but present hash + // succeeds because the embedded artifact cannot change at runtime. + missingHash := cloneSnapshotForCoverage(t, base) + missingHash.SourceHash = "" + if _, err := loadSchemaCatalogSnapshot(missingHash); err == nil { + t.Fatal("missing snapshot hash succeeded") } badTyped := cloneSnapshotForCoverage(t, base) badTyped.Catalog["unknown"] = true diff --git a/internal/cli/schema_catalog_test.go b/internal/cli/schema_catalog_test.go index f563631f..5a65dffd 100644 --- a/internal/cli/schema_catalog_test.go +++ b/internal/cli/schema_catalog_test.go @@ -134,7 +134,11 @@ func TestEmbeddedCatalogPreservesRegistryIdentityAndManualParameterContract(t *t t.Helper() provenance := schemaMap(parameters[flagName]["field_provenance"]) winner := provenance[field] - if winner["source"] != "reviewed_manual_hint" || winner["precedence"] != "reviewed_manual" { + src, _ := winner["source"].(string) + prec, _ := winner["precedence"].(string) + // Accept both the legacy hints source and the migrated declaration source. + if (src != "reviewed_manual_hint" || prec != "reviewed_manual") && + (src != "native_annotation" || prec != "native_annotation") { t.Fatalf("%s.%s provenance = %#v", flagName, field, winner) } } diff --git a/internal/cli/schema_cobra_binding.go b/internal/cli/schema_cobra_binding.go index 14cc520b..b1dfd5c6 100644 --- a/internal/cli/schema_cobra_binding.go +++ b/internal/cli/schema_cobra_binding.go @@ -300,6 +300,45 @@ func validateCompatibilityLeafContract(spec CommandSpec, primary, alias *cobra.C ) } +// filterSchemaAnnotations returns a copy of annotations without dws.schema.* +// keys. Those are Schema-level metadata (ParamDecl / AttachSchema) that do not +// affect command execution and are resolved through the shared assembly path. +func filterSchemaAnnotations(annotations map[string][]string) map[string][]string { + if len(annotations) == 0 { + return nil + } + out := make(map[string][]string, len(annotations)) + for k, v := range annotations { + if !strings.HasPrefix(k, "dws.schema.") { + out[k] = v + } + } + if len(out) == 0 { + return nil + } + return out +} + +// requiredContractEquivalent compares two Required contracts, tolerating +// NativeRequired / NativeRequiredWhen being present on one side but empty on +// the other. This happens when a ParamDecl annotation exists on the primary +// command but not on its compatibility alias. Genuine drift (both non-empty +// but different) is still caught. +func requiredContractEquivalent(a, b compatibilityFlagRequiredContract) bool { + // Compare all fields except NativeRequired / NativeRequiredWhen strictly. + if a.CobraRequired != b.CobraRequired || a.UsageRequired != b.UsageRequired || + a.UsageDefault != b.UsageDefault || a.MetadataRequired != b.MetadataRequired || + a.MetadataRequiredWhen != b.MetadataRequiredWhen { + return false + } + // NativeRequired / NativeRequiredWhen: tolerate one-sided presence. + nativeEquiv := func(x, y string) bool { + return x == y || x == "" || y == "" + } + return nativeEquiv(a.NativeRequired, b.NativeRequired) && + nativeEquiv(a.NativeRequiredWhen, b.NativeRequiredWhen) +} + func compatibilityHandlerContractProblems(primary, alias *cobra.Command) []string { if primary == nil || alias == nil { return []string{"execution handlers cannot be compared for a nil command"} @@ -450,13 +489,19 @@ func compatibilityFlagContractProblems(canonicalPath string, primary, alias *cob if primaryFlag.Origin != aliasFlag.Origin { problems = append(problems, fmt.Sprintf("flag --%s local/persistent/inherited behavior differs: primary=%s compatibility=%s", name, primaryFlag.Origin, aliasFlag.Origin)) } - if !reflect.DeepEqual(primaryFlag.Required, aliasFlag.Required) { + if !requiredContractEquivalent(primaryFlag.Required, aliasFlag.Required) { problems = append(problems, fmt.Sprintf("flag --%s required/required_when facts differ: primary=%s compatibility=%s", name, compatibilityJSON(primaryFlag.Required), compatibilityJSON(aliasFlag.Required))) } - if !reflect.DeepEqual(primaryFlag.Annotations, aliasFlag.Annotations) { + // Compare annotations excluding dws.schema.* keys: those are Schema-level + // metadata declarations (ParamDecl / AttachSchema) that do not affect + // command execution. The compatibility check verifies executable equivalence; + // Schema facts are resolved through the shared assembly path. + primaryExecAnnotations := filterSchemaAnnotations(primaryFlag.Annotations) + aliasExecAnnotations := filterSchemaAnnotations(aliasFlag.Annotations) + if !reflect.DeepEqual(primaryExecAnnotations, aliasExecAnnotations) { problems = append(problems, fmt.Sprintf("flag --%s annotations differ: primary=%s compatibility=%s", - name, compatibilityJSON(primaryFlag.Annotations), compatibilityJSON(aliasFlag.Annotations))) + name, compatibilityJSON(primaryExecAnnotations), compatibilityJSON(aliasExecAnnotations))) } } return problems, nil diff --git a/internal/cli/schema_cobra_binding_test.go b/internal/cli/schema_cobra_binding_test.go index 568f2f46..2bc9b8d9 100644 --- a/internal/cli/schema_cobra_binding_test.go +++ b/internal/cli/schema_cobra_binding_test.go @@ -987,3 +987,46 @@ func annotateTestCompatibilityPair(primary, alias *cobra.Command) { Reviewed: true, }) } + +// TestCrossPlatformCoverageBindEffectiveCommandRegistryAttachesResidualHintDecl pins the bind-time +// migration path: an undeclared leaf whose canonical still has a compiled +// schema_hints declaration receives ContractFinal without replacing RunE. +func TestCrossPlatformCoverageBindEffectiveCommandRegistryAttachesResidualHintDecl(t *testing.T) { + root := commandRegistryTestRoot("item get") + leaf := exactSchemaCommand(root, "item get") + if leaf == nil { + t.Fatal("missing item get leaf") + } + t.Cleanup(func() { ClearRuntimeContractFinalForTest(leaf) }) + + schemaHintDeclsByCanonical["item.get_item"] = testSchemaHintDeclFixture() + t.Cleanup(func() { delete(schemaHintDeclsByCanonical, "item.get_item") }) + + reviewed := mustCommandRegistry(t, []CommandSpec{{ + CanonicalPath: "item.get_item", + PrimaryCLIPath: "item get", + }}) + manual := ManualSchemaHintSnapshot{ + Version: manualSchemaHintVersion, + Commands: []ManualSchemaCommandHint{{ + CLIPath: "item get", + CanonicalPath: "item.get_item", + Reason: "Confirms the reviewed primary identity", + Reviewed: true, + }}, + } + effective, err := buildEffectiveCommandRegistry(root, reviewed, manual) + if err != nil { + t.Fatalf("buildEffectiveCommandRegistry() error = %v", err) + } + if _, err := BindEffectiveCommandRegistry(root, effective); err != nil { + t.Fatalf("BindEffectiveCommandRegistry() error = %v", err) + } + if !HasRuntimeContractFinal(leaf) { + t.Fatal("residual hint decl must attach ContractFinal at bind time") + } + final, ok := RuntimeContractFinal(leaf) + if !ok || final.Description != testSchemaHintDeclFixture().Description { + t.Fatalf("attached ContractFinal = %#v ok=%v", final, ok) + } +} diff --git a/internal/cli/schema_consumption_bench_test.go b/internal/cli/schema_consumption_bench_test.go new file mode 100644 index 00000000..70c98fd4 --- /dev/null +++ b/internal/cli/schema_consumption_bench_test.go @@ -0,0 +1,188 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "crypto/sha256" + "io/fs" + "strings" + "testing" +) + +// These benchmarks attribute the Schema consumption cost that ResolveMeta pays +// on its first hit. Both production entry points are sync.Once memoized, so the +// benchmarks call the underlying work directly — measuring the exported wrappers +// would time one real run followed by N no-ops. +// +// They establish the RFC §M0 deliverable-11 baseline for embedded parse and +// lookup construction, which §M4's per-PR budget needs in order to mean anything. + +// BenchmarkAssembleEmbeddedSchemaCatalog measures decoding the embedded release +// snapshot: the global envelope plus every per-product tools shard. +func BenchmarkAssembleEmbeddedSchemaCatalog(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + loaded, err := assembleEmbeddedSchemaCatalog() + if err != nil { + b.Fatal(err) + } + if len(loaded.Snapshot.Tools) == 0 { + b.Fatal("decoded an empty catalog") + } + } +} + +// BenchmarkBuildMetaByCLIPath measures turning the decoded snapshot into the +// ResolveMeta lookup map, isolated from the decode above. +func BenchmarkBuildMetaByCLIPath(b *testing.B) { + loaded := embeddedSchemaCatalog() + if len(loaded.Snapshot.Tools) == 0 { + b.Skip("embedded catalog unavailable") + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if len(buildMetaByCLIPath(loaded)) == 0 { + b.Fatal("built an empty lookup") + } + } +} + +// BenchmarkResolveMetaSteadyState measures a warm lookup, i.e. what every +// --help Safety annotation costs once the map exists. +func BenchmarkResolveMetaSteadyState(b *testing.B) { + if _, ok := ResolveMeta("dev app delete"); !ok { + b.Skip("embedded catalog unavailable") + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, ok := ResolveMeta("dev app delete"); !ok { + b.Fatal("warm lookup missed") + } + } +} + +// The benchmarks below attribute the cost *inside* assembleEmbeddedSchemaCatalog, +// which the whole-path benchmark shows costs ~1.5s and ~817 MB. Optimizing the +// wrong stage is the main risk here, so each stage is measured separately. + +// BenchmarkCatalogStageDecodeShards measures only the JSON decode of the +// envelope plus the 27 per-product shards into the untyped snapshot maps. +func BenchmarkCatalogStageDecodeShards(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + snapshot, err := assembleSchemaCatalogSnapshot( + embeddedSchemaCatalogEnvelopeJSON, embeddedSchemaCatalogTools, "schema_catalog/tools") + if err != nil { + b.Fatal(err) + } + if len(snapshot.Tools) == 0 { + b.Fatal("decoded an empty snapshot") + } + } +} + +// BenchmarkCatalogStageSourceHash measures re-hashing the decoded snapshot, +// which the loader does on every process start to verify source_hash. +func BenchmarkCatalogStageSourceHash(b *testing.B) { + snapshot, err := assembleSchemaCatalogSnapshot( + embeddedSchemaCatalogEnvelopeJSON, embeddedSchemaCatalogTools, "schema_catalog/tools") + if err != nil { + b.Skip("embedded catalog unavailable") + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if schemaCatalogSnapshotHash(snapshot) == "" { + b.Fatal("empty hash") + } + } +} + +// BenchmarkCatalogStageTypedRegistry measures converting the untyped snapshot +// maps into the typed registry and index. +func BenchmarkCatalogStageTypedRegistry(b *testing.B) { + snapshot, err := assembleSchemaCatalogSnapshot( + embeddedSchemaCatalogEnvelopeJSON, embeddedSchemaCatalogTools, "schema_catalog/tools") + if err != nil { + b.Skip("embedded catalog unavailable") + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + registry, _, err := schemaRegistryFromSnapshot(snapshot) + if err != nil { + b.Fatal(err) + } + if len(registry.Products) == 0 { + b.Fatal("empty registry") + } + } +} + +// BenchmarkCatalogStageValidations measures the three whole-registry validation +// passes the loader runs after conversion. +func BenchmarkCatalogStageValidations(b *testing.B) { + loaded := embeddedSchemaCatalog() + if len(loaded.Registry.Products) == 0 { + b.Skip("embedded catalog unavailable") + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if err := loadCatalogValidateInterfaces(loaded.Registry); err != nil { + b.Fatal(err) + } + if err := loadCatalogValidateProvenance(loaded.Registry); err != nil { + b.Fatal(err) + } + if err := loadCatalogValidateAgentMetadata(loaded.Registry); err != nil { + b.Fatal(err) + } + } +} + +// BenchmarkCatalogStageRawByteHash measures hashing the embedded bytes directly, +// as an alternative to schemaCatalogSnapshotHash, which re-marshals the decoded +// maps. It is the cost a raw-bytes integrity check would pay instead. +func BenchmarkCatalogStageRawByteHash(b *testing.B) { + entries, err := fs.ReadDir(embeddedSchemaCatalogTools, "schema_catalog/tools") + if err != nil { + b.Skip("embedded catalog unavailable") + } + shards := make([][]byte, 0, len(entries)) + for _, entry := range entries { + if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") { + continue + } + data, readErr := fs.ReadFile(embeddedSchemaCatalogTools, "schema_catalog/tools/"+entry.Name()) + if readErr != nil { + b.Skip("embedded shard unavailable") + } + shards = append(shards, data) + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sum := sha256.New() + sum.Write(embeddedSchemaCatalogEnvelopeJSON) + for _, shard := range shards { + sum.Write(shard) + } + if len(sum.Sum(nil)) != sha256.Size { + b.Fatal("short digest") + } + } +} diff --git a/internal/cli/schema_hint_decls_test.go b/internal/cli/schema_hint_decls_test.go index eef5d55f..7a3e8ddc 100644 --- a/internal/cli/schema_hint_decls_test.go +++ b/internal/cli/schema_hint_decls_test.go @@ -5,6 +5,7 @@ package cli import ( + "strings" "testing" "github.com/spf13/cobra" @@ -69,3 +70,90 @@ func TestSchemaHintDeclLookupCoverage(t *testing.T) { t.Fatal("doc.create_document must not remain in compiled hint decls") } } + +func TestCrossPlatformCoverageLookupSchemaHintDeclBlankCanonical(t *testing.T) { + if _, ok := lookupSchemaHintDecl(""); ok { + t.Fatal("empty canonical must not resolve a hint decl") + } + if _, ok := lookupSchemaHintDecl(" "); ok { + t.Fatal("blank canonical must not resolve a hint decl") + } +} + +func TestCrossPlatformCoverageAttachSchemaHintDeclNilCommandIsNoOp(t *testing.T) { + // Must not panic and must not register anything. + attachSchemaHintDecl(nil, testSchemaHintDeclFixture()) +} + +func TestCrossPlatformCoverageAttachSchemaHintDeclCompletenessPanics(t *testing.T) { + mustPanic := func(t *testing.T, wantFragment string, decl schemaHintDecl) { + t.Helper() + cmd := &cobra.Command{Use: "probe", Short: "probe"} + defer func() { + recovered := recover() + if recovered == nil { + t.Fatalf("attachSchemaHintDecl must panic for %q", wantFragment) + } + message, ok := recovered.(string) + if !ok || !strings.Contains(message, wantFragment) { + t.Fatalf("panic = %v, want fragment %q", recovered, wantFragment) + } + if HasRuntimeContractFinal(cmd) { + t.Fatal("panicking attach must not register ContractFinal") + } + }() + attachSchemaHintDecl(cmd, decl) + } + + t.Run("missing description", func(t *testing.T) { + decl := testSchemaHintDeclFixture() + decl.Description = " " + mustPanic(t, "missing Description", decl) + }) + t.Run("missing selection prose", func(t *testing.T) { + decl := testSchemaHintDeclFixture() + decl.Selection.UseWhen = nil + mustPanic(t, "missing Selection prose", decl) + }) + t.Run("missing interface", func(t *testing.T) { + decl := testSchemaHintDeclFixture() + decl.Interface = nil + mustPanic(t, "missing Interface", decl) + }) + t.Run("composite without reason", func(t *testing.T) { + decl := testSchemaHintDeclFixture() + decl.Interface = &InterfaceSpec{Mode: InterfaceModeComposite, Availability: InterfaceAvailable} + mustPanic(t, "missing Interface.Reason", decl) + }) +} + +func TestCrossPlatformCoverageAttachSchemaHintDeclDefaultsEmptySafetyToRead(t *testing.T) { + decl := testSchemaHintDeclFixture() + decl.Safety = SafetySpec{} + cmd := &cobra.Command{Use: "probe", Short: "probe"} + t.Cleanup(func() { ClearRuntimeContractFinalForTest(cmd) }) + attachSchemaHintDecl(cmd, decl) + final, ok := RuntimeContractFinal(cmd) + if !ok || final.Safety == nil { + t.Fatalf("ContractFinal after attach = %#v ok=%v", final, ok) + } + if final.Safety.Effect != "read" || final.Safety.Risk != "low" || + final.Safety.Confirmation != "not_required" || final.Safety.Idempotency != "idempotent" { + t.Fatalf("defaulted safety = %#v", final.Safety) + } + if final.Safety.EffectSource != "corecmd.contract" { + t.Fatalf("effect source = %q, want corecmd.contract", final.Safety.EffectSource) + } +} + +func TestFirstNonEmptyTrim(t *testing.T) { + if got := firstNonEmptyTrim(" ", "", "\t"); got != "" { + t.Fatalf("all-blank firstNonEmptyTrim = %q, want empty", got) + } + if got := firstNonEmptyTrim(); got != "" { + t.Fatalf("no-arg firstNonEmptyTrim = %q, want empty", got) + } + if got := firstNonEmptyTrim("", " value "); got != "value" { + t.Fatalf("firstNonEmptyTrim = %q, want value", got) + } +} diff --git a/internal/cli/schema_hints/metadata/aisearch.json b/internal/cli/schema_hints/metadata/aisearch.json index ecd6f028..9a7268d9 100644 --- a/internal/cli/schema_hints/metadata/aisearch.json +++ b/internal/cli/schema_hints/metadata/aisearch.json @@ -9,17 +9,9 @@ }, "tools": { "aisearch.search_enterprise": { - "parameters": { - "queries": { - "required": false - }, - "time-range": { - "required": false - } - }, "reviewed": true, "cli_path": "aisearch enterprise", "review_reason": "The reviewed CLI supports an all-content search with default --types all, while --queries and --time-range are optional filters; imported MCP required markers must not turn them into mandatory CLI input." } } -} +} \ No newline at end of file diff --git a/internal/cli/schema_hints/metadata/chat.json b/internal/cli/schema_hints/metadata/chat.json index 0bf34ae4..2a0b62d8 100644 --- a/internal/cli/schema_hints/metadata/chat.json +++ b/internal/cli/schema_hints/metadata/chat.json @@ -9,47 +9,11 @@ }, "tools": { "chat.add_emoji_reaction": { - "parameters": { - "chat": { - "property": "openConversationId", - "required": false - }, - "conversation-id": { - "property": "openConversationId", - "required": false - }, - "group": { - "property": "openConversationId", - "required": false - }, - "id": { - "property": "openConversationId", - "required": false - } - }, "reviewed": true, "cli_path": "chat message add-emoji", "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement." }, "chat.add_text_emotion": { - "parameters": { - "chat": { - "property": "openConversationId", - "required": false - }, - "conversation-id": { - "property": "openConversationId", - "required": false - }, - "group": { - "property": "openConversationId", - "required": false - }, - "id": { - "property": "openConversationId", - "required": false - } - }, "reviewed": true, "cli_path": "chat message add-text-emotion", "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement." @@ -89,233 +53,66 @@ "review_reason": "Preserve the reviewed CLI-to-interface conversion: Cobra validates --size as an integer from 1 to 100, then the helper serializes it to the string-valued list_message_favorites size property." }, "chat.list_owned_or_admin_groups": { - "parameters": { - "limit": { - "required": false - }, - "role": { - "required": false - } - }, "reviewed": true, "cli_path": "chat group list-my-groups", "review_reason": "The reviewed CLI lists all owned or administered groups when --role and --limit are omitted; both flags are optional filters despite stricter imported interface metadata." }, "chat.remove_emoji_reaction": { - "parameters": { - "chat": { - "property": "openConversationId", - "required": false - }, - "conversation-id": { - "property": "openConversationId", - "required": false - }, - "group": { - "property": "openConversationId", - "required": false - }, - "id": { - "property": "openConversationId", - "required": false - } - }, "reviewed": true, "cli_path": "chat message remove-emoji", "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement." }, "chat.remove_text_emotion": { - "parameters": { - "chat": { - "property": "openConversationId", - "required": false - }, - "conversation-id": { - "property": "openConversationId", - "required": false - }, - "group": { - "property": "openConversationId", - "required": false - }, - "id": { - "property": "openConversationId", - "required": false - } - }, "reviewed": true, "cli_path": "chat message remove-text-emotion", "review_reason": "The four reviewed conversation locator flags are aliases for one openConversationId property; the typed require-one-of constraint owns their conditional requirement." }, "chat.set_group_member_mute_list": { - "parameters": { - "off": { - "required": false - } - }, "reviewed": true, "cli_path": "chat group-mute-member", "review_reason": "The reviewed Cobra contract makes --off an optional mode switch: omission adds members to the mute list, while --off removes them. The shared MCP schema incorrectly marks the boolean as unconditionally required." }, "chat.set_group_mute": { - "parameters": { - "off": { - "required": false - } - }, "reviewed": true, "cli_path": "chat group-mute", "review_reason": "The reviewed optional --off switch selects unmute; omission deliberately selects mute, so false is a valid default rather than missing required input." }, "chat.set_top_conversation": { - "parameters": { - "off": { - "required": false - } - }, "reviewed": true, "cli_path": "chat set-top", "review_reason": "The reviewed optional --off switch selects unpin; omission deliberately selects pin, so false is a valid default rather than missing required input." }, "chat.update_conv_member_roles": { - "parameters": { - "off": { - "required": false - } - }, "reviewed": true, "cli_path": "chat group set-admin", "review_reason": "The reviewed optional --off switch selects administrator removal; omission deliberately selects administrator assignment." }, "chat.update_notification_off": { - "parameters": { - "chat": { - "property": "openConversationId", - "required": false - }, - "conversation-id": { - "property": "openConversationId", - "required": false - }, - "id": { - "property": "openConversationId", - "required": false - }, - "off": { - "required": false - } - }, "reviewed": true, "cli_path": "chat mute", "review_reason": "The reviewed locator aliases feed one openConversationId property, while optional --off selects unmute; typed constraints require one locator without making the mode switch mandatory." }, "chat.edit_message": { - "parameters": { - "conversation-id": { - "property": "openConversationId", - "required": true - }, - "group": { - "property": "openConversationId", - "required": false - }, - "id": { - "property": "openConversationId", - "required": false - }, - "chat": { - "property": "openConversationId", - "required": false - }, - "msg-id": { - "property": "openMessageId", - "required": true - }, - "text": { - "property": "text", - "required": false - }, - "title": { - "property": "title", - "required": false - }, - "content": { - "property": "content", - "required": false - }, - "at-all": { - "property": "atAll", - "required": false, - "interface_type": "boolean" - }, - "at-open-dingtalk-ids": { - "property": "atOpenDingTalkIds", - "required": false, - "interface_type": "array" - } - }, "reviewed": true, "cli_path": "chat message edit", "review_reason": "The reviewed handler requires one conversation locator, a message ID, and exactly one of text or raw content; it optionally derives a title and maps mention controls before invoking edit_message." }, "chat.get_conv_categories_info": { - "parameters": { - "category-ids": { - "property": "categoryIds", - "required": true, - "interface_type": "array" - } - }, "reviewed": true, "cli_path": "chat category batch-info", "review_reason": "The reviewed handler requires a comma-separated category-ID list, parses it to integers, and invokes get_conv_categories_info without a mutation or confirmation gate." }, "chat.list_conv_categories_by_conv": { - "parameters": { - "group": { - "property": "openConversationId", - "required": true - }, - "conversation-id": { - "property": "openConversationId", - "required": false - }, - "id": { - "property": "openConversationId", - "required": false - } - }, "reviewed": true, "cli_path": "chat category list-by-conv", "review_reason": "The reviewed handler accepts one of three conversation locator aliases and invokes list_conv_categories_by_conv without a mutation or confirmation gate." }, "chat.update_group_nick": { - "parameters": { - "group": { - "property": "openConversationId", - "required": true - }, - "nick": { - "property": "nick", - "required": false - } - }, "reviewed": true, "cli_path": "chat group update-nick", "review_reason": "The reviewed handler requires the target conversation while intentionally allowing omitted --nick to send an empty nickname and clear the current user's group nickname." }, "chat.upgrade_group_to_external": { - "parameters": { - "group": { - "property": "openConversationId", - "required": true - }, - "extension": { - "property": "extension", - "required": false, - "interface_type": "object" - } - }, "reviewed": true, "cli_path": "chat group upgrade-to-external", "review_reason": "The reviewed handler validates the target group and optional string-valued extension object, permits caller-authoritative dry-run, and requires explicit --yes before the irreversible upgrade." @@ -332,58 +129,9 @@ "review_reason": "Reviewed against authenticated contact-search resolution, create-only, two-action dry-run, create/update success, missing bizId, and partial-failure behavior that preserves the created bizId; the explicit openDingTalkId flag is bound to the lower interface's exact receiverOpenDingTalkId property." }, "chat.update_text_emotion": { - "parameters": { - "background-id": { - "property": "backgroundId", - "description": "新文字表情的背景 ID", - "required": true - }, - "chat": { - "property": "openConversationId", - "required": false - }, - "conversation-id": { - "property": "openConversationId", - "description": "会话 openConversationId;与 --group、--id、--chat 四选一", - "required": false - }, - "emotion-id": { - "property": "emotionId", - "description": "新的文字表情 ID,可通过 create-text-emotion 获取", - "required": true - }, - "emotion-name": { - "property": "emotionName", - "description": "新的文字表情名称", - "required": true - }, - "group": { - "property": "openConversationId", - "required": false - }, - "id": { - "property": "openConversationId", - "required": false - }, - "msg-id": { - "property": "openMsgId", - "description": "需要原地更新文字表情的消息 openMsgId", - "required": true - }, - "old-emotion-id": { - "property": "oldEmotionId", - "description": "消息上当前文字表情的 emotionId", - "required": true - }, - "text": { - "property": "text", - "description": "新的文字表情内容", - "required": true - } - }, "reviewed": true, "cli_path": "chat message update-text-emotion", "review_reason": "Ported from dws-wukong commits d6831318 and bcf56ccf, merged at 30f13f02. The four conversation locators alias one property; the other six business parameters are required by Cobra before the single im/update_text_emotion call." } } -} +} \ No newline at end of file diff --git a/internal/cli/schema_hints/metadata/contact.json b/internal/cli/schema_hints/metadata/contact.json index 1a1996bd..a0764231 100644 --- a/internal/cli/schema_hints/metadata/contact.json +++ b/internal/cli/schema_hints/metadata/contact.json @@ -9,82 +9,21 @@ }, "tools": { "contact.add_employee": { - "parameters": { - "org-user-name": { - "property": "orgUserName", - "required": true - }, - "org-user-mobile": { - "property": "orgUserMobile", - "required": true - }, - "depts": { - "property": "depts", - "required": false, - "interface_type": "array" - } - }, "reviewed": true, "cli_path": "contact user invite", "review_reason": "The reviewed Wukong-compatible handler requires employee name and mobile, decodes optional --depts JSON to an array, and calls add_employee without a runtime confirmation gate." }, "contact.exclusive_account_create": { - "parameters": { - "org-user-name": { - "property": "orgUserName", - "required": true - }, - "login-id": { - "property": "loginId", - "required": true - }, - "org-user-mobile": { - "property": "orgUserMobile", - "required": false - }, - "email": { - "property": "email", - "required": false - }, - "dept-ids": { - "property": "deptIds", - "required": false, - "interface_type": "array" - }, - "send-pwd-via-sms": { - "property": "sendPwdViaSms", - "required": false, - "interface_type": "boolean" - } - }, "reviewed": true, "cli_path": "contact account create", "review_reason": "The reviewed Wukong-compatible handler requires enterprise display name and login ID, maps optional contact fields and department IDs, and calls exclusive_account_create without a runtime confirmation gate." }, "contact.get_authorized_emp_rosterInfo": { - "parameters": { - "fields": { - "required": false - }, - "staff-id": { - "required": false - } - }, "reviewed": true, "cli_path": "contact user profile get", "review_reason": "The reviewed helper deliberately permits either or both optional filters and passes an empty request to query the caller's authorized roster scope." }, "contact.org_create": { - "parameters": { - "org-name": { - "property": "orgName", - "required": true - }, - "creator-username": { - "property": "creatorUsername", - "required": true - } - }, "reviewed": true, "cli_path": "contact org create", "review_reason": "The reviewed Wukong-compatible handler requires an organization name and creator display name and calls org_create without a runtime confirmation gate." @@ -267,4 +206,4 @@ "review_reason": "The reviewed handler requires at least one of nickname or avatar file ID, confirms, then invokes self_user_profile_update for the current user." } } -} +} \ No newline at end of file diff --git a/internal/cli/schema_hints/metadata/doc.json b/internal/cli/schema_hints/metadata/doc.json index a05b5092..ebfd20b3 100644 --- a/internal/cli/schema_hints/metadata/doc.json +++ b/internal/cli/schema_hints/metadata/doc.json @@ -9,52 +9,16 @@ }, "tools": { "doc.create_comment": { - "parameters": { - "mentioned-open-conversation-id": { - "required": false, - "interface_type": "array" - } - }, "reviewed": true, "cli_path": "doc comment create", "review_reason": "The reviewed handler normalizes repeated or comma-separated group conversation IDs into a stable de-duplicated array while preserving existing user mentions." }, "doc.get_document_content": { - "parameters": { - "content-format": { - "property": "format", - "required": false - }, - "scope": { - "required": false - }, - "tags": { - "required": false, - "required_when": "--scope=tags" - }, - "max-depth": { - "required": false, - "interface_type": "integer" - }, - "start-block-id": { - "required": false, - "required_when": "--scope=range or --scope=section" - }, - "end-block-id": { - "required": false - } - }, "reviewed": true, "cli_path": "doc read", "review_reason": "The reviewed handler preserves full Markdown/JSONML reads and adds validated outline, range, section, and tag-scoped JSONML fragment reads with optional file output." }, "doc.reply_comment": { - "parameters": { - "mentioned-open-conversation-id": { - "required": false, - "interface_type": "array" - } - }, "reviewed": true, "cli_path": "doc comment reply", "review_reason": "The reviewed handler normalizes group conversation IDs into a stable de-duplicated array and rejects group mentions for emoji replies, which the remote operation does not support." @@ -75,4 +39,4 @@ "review_reason": "Preserve the reviewed user-mention conversion and normalize repeated or comma-separated group conversation IDs into mentionedOpenConversationIds." } } -} +} \ No newline at end of file diff --git a/internal/cli/schema_hints/metadata/drive.json b/internal/cli/schema_hints/metadata/drive.json index c43906ae..6175ea6c 100644 --- a/internal/cli/schema_hints/metadata/drive.json +++ b/internal/cli/schema_hints/metadata/drive.json @@ -9,11 +9,6 @@ }, "tools": { "drive.rename_document": { - "parameters": { - "name": { - "description": "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名" - } - }, "reviewed": true, "cli_path": "drive rename", "review_reason": "The reviewed CLI reads node metadata before mutation and strips only the exact current extension for non-folder nodes, avoiding both duplicate extensions and dotted-folder corruption without a suffix whitelist." @@ -30,4 +25,4 @@ "review_reason": "The reviewed helper preserves the stable command-level Schema contract for ordinary uploads; --node switches to overwrite mode and the runtime still requires explicit confirmation unless --yes is supplied." } } -} +} \ No newline at end of file diff --git a/internal/cli/schema_hints/metadata/hrbrain.json b/internal/cli/schema_hints/metadata/hrbrain.json index f748632d..13138842 100644 --- a/internal/cli/schema_hints/metadata/hrbrain.json +++ b/internal/cli/schema_hints/metadata/hrbrain.json @@ -9,190 +9,54 @@ }, "tools": { "hrbrain.list_talent_pools": { - "parameters": { - "keyword": { - "property": "keyword", - "required": false - }, - "pool-type": { - "property": "poolType", - "required": false - }, - "creator": { - "property": "creator", - "required": false - }, - "labels": { - "property": "labels", - "required": false - }, - "page": { - "property": "currentPage", - "required": false - }, - "page-size": { - "property": "pageSize", - "required": false - } - }, "reviewed": true, "cli_path": "hrbrain talent-pool list", "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_talent_pools MCP call arguments observed in internal/helpers/hrbrain.go." }, "hrbrain.get_talent_pool_detail": { - "parameters": { - "pool-code": { - "property": "poolCode", - "required": true - } - }, "reviewed": true, "cli_path": "hrbrain talent-pool detail", "review_reason": "Reviewed unpinned remote adapter: --pool-code maps 1:1 to the poolCode MCP call argument observed in internal/helpers/hrbrain.go." }, "hrbrain.list_pool_employees": { - "parameters": { - "pool-code": { - "property": "poolCode", - "required": true - }, - "page": { - "property": "currentPage", - "required": false - }, - "page-size": { - "property": "pageSize", - "required": false - } - }, "reviewed": true, "cli_path": "hrbrain talent-pool employees", "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the list_pool_employees MCP call arguments observed in internal/helpers/hrbrain.go." }, "hrbrain.get_profile_metadata": { - "parameters": { - "work-no": { - "property": "workNo", - "required": true - } - }, "reviewed": true, "cli_path": "hrbrain profile metadata", "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go." }, "hrbrain.query_profile_data": { - "parameters": { - "work-no": { - "property": "workNo", - "required": true - }, - "data-queries": { - "property": "dataQueries", - "required": true - } - }, "reviewed": true, "cli_path": "hrbrain profile query", "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the query_profile_data MCP call arguments observed in internal/helpers/hrbrain.go." }, "hrbrain.get_profile_label": { - "parameters": { - "staff-ids": { - "property": "staffIds", - "required": true - }, - "all-label": { - "property": "allLabel", - "required": false - } - }, "reviewed": true, "cli_path": "hrbrain profile labels", "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the get_profile_label MCP call arguments observed in internal/helpers/hrbrain.go." }, "hrbrain.get_employee_career": { - "parameters": { - "work-no": { - "property": "workNo", - "required": true - } - }, "reviewed": true, "cli_path": "hrbrain profile career", "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go." }, "hrbrain.get_employee_performance": { - "parameters": { - "work-no": { - "property": "workNo", - "required": true - } - }, "reviewed": true, "cli_path": "hrbrain profile performance", "review_reason": "Reviewed unpinned remote adapter: --work-no maps 1:1 to the workNo MCP call argument observed in internal/helpers/hrbrain.go." }, "hrbrain.search_employees": { - "parameters": { - "keyword": { - "property": "keyword", - "required": false - }, - "dept-name": { - "property": "deptName", - "required": false - }, - "position-name": { - "property": "positionName", - "required": false - }, - "job-level": { - "property": "jobLevel", - "required": false - }, - "pool-code": { - "property": "poolCode", - "required": false - }, - "page": { - "property": "currentPage", - "required": false - }, - "page-size": { - "property": "pageSize", - "required": false - } - }, "reviewed": true, "cli_path": "hrbrain search employees", "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees MCP call arguments observed in internal/helpers/hrbrain.go." }, "hrbrain.search_employees_structured": { - "parameters": { - "origin-json": { - "property": "originJson", - "required": true - }, - "fields": { - "property": "fields", - "required": true - }, - "order-by": { - "property": "orderByClauses", - "required": false - }, - "page": { - "property": "currentPage", - "required": false - }, - "page-size": { - "property": "pageSize", - "required": false - } - }, "reviewed": true, "cli_path": "hrbrain search employees-structured", "review_reason": "Reviewed unpinned remote adapter: flags map 1:1 to the search_employees_structured MCP call arguments observed in internal/helpers/hrbrain.go." } } -} +} \ No newline at end of file diff --git a/internal/cli/schema_hints/metadata/mail.json b/internal/cli/schema_hints/metadata/mail.json index 024f05db..3c59a6a8 100644 --- a/internal/cli/schema_hints/metadata/mail.json +++ b/internal/cli/schema_hints/metadata/mail.json @@ -19,33 +19,14 @@ "review_reason": "Preserve the reviewed composite wrapper contract: the CLI maps --limit to the search_emails size property after constructing a folder KQL query." }, "mail.search_mail_users": { - "parameters": { - "employee-no": { - "required": false - }, - "keyword": { - "required": false - } - }, "reviewed": true, "cli_path": "mail user search", "review_reason": "The reviewed CLI accepts either a fuzzy keyword or an exact employee number, represented by a typed require-one-of constraint rather than two unconditional requirements." }, "mail.update_user_message_template": { - "parameters": { - "content": { - "required": false - }, - "name": { - "required": false - }, - "subject": { - "required": false - } - }, "reviewed": true, "cli_path": "mail template update", "review_reason": "The reviewed patch command accepts any non-empty subset of mutable template fields; a typed require-one-of constraint owns that requirement." } } -} +} \ No newline at end of file diff --git a/internal/cli/schema_hints/metadata/report.json b/internal/cli/schema_hints/metadata/report.json index 878754f7..57d67e40 100644 --- a/internal/cli/schema_hints/metadata/report.json +++ b/internal/cli/schema_hints/metadata/report.json @@ -9,14 +9,9 @@ }, "tools": { "report.create_report": { - "parameters": { - "to-chat": { - "required": false - } - }, "reviewed": true, "cli_path": "report entry submit", "review_reason": "The reviewed optional --to-chat switch defaults to false and only controls post-submit delivery; it is not required to create a report." } } -} +} \ No newline at end of file diff --git a/internal/cli/schema_hints/metadata/todo.json b/internal/cli/schema_hints/metadata/todo.json index 897900ad..1839a7d0 100644 --- a/internal/cli/schema_hints/metadata/todo.json +++ b/internal/cli/schema_hints/metadata/todo.json @@ -9,82 +9,34 @@ }, "tools": { "todo.get_user_todos_in_current_org": { - "parameters": { - "role-types": { - "property": "roleTypes", - "required": false, - "description": "角色类型列表;省略时运行时默认使用 executor" - }, - "query-all": { - "required": false, - "interface_type": "boolean", - "description": "为 true 时跨组织查询全部待办;默认仅查询当前组织待办" - } - }, "reviewed": true, "cli_path": "todo task list", "review_reason": "The reviewed handler defaults omitted role-types to executor, preserves current-organization behavior by default, and switches both single-page and auto-page calls to get_user_todos only when --query-all is explicitly true." }, "todo.reset_todo_reminder": { - "parameters": { - "reminder-rules": { - "description": "提醒规则 JSON 数组;不传表示清除,显式传值必须为对象数组,且每条按 baseTime 提供整数 dueDateOffset 或 ISO8601 reminderTimeStamp" - } - }, "reviewed": true, "cli_path": "todo task reset-reminder", "review_reason": "The reviewed CLI rejects malformed or structurally invalid non-empty reminder rules before the reset mutation, while preserving omission and [] as explicit clear forms." }, "todo.create_todo_tag": { - "parameters": { - "name": { - "property": "name", - "required": true - } - }, "reviewed": true, "cli_path": "todo tag create", "review_reason": "The reviewed handler requires a non-blank tag name, builds a one-element userTags request, and invokes create_todo_tag without a runtime confirmation gate." }, "todo.delete_todo_tag": { - "parameters": { - "tag-codes": { - "property": "tagCodes", - "required": true, - "interface_type": "array" - } - }, "reviewed": true, "cli_path": "todo tag delete", "review_reason": "The reviewed handler requires at least one non-empty tag code, permits caller-authoritative dry-run, and requires explicit --yes before invoking delete_todo_tag." }, "todo.tag_todo": { - "parameters": { - "task-id": { - "property": "taskId", - "required": true - }, - "tag-codes": { - "property": "tagCodes", - "required": true, - "interface_type": "array" - } - }, "reviewed": true, "cli_path": "todo tag add", "review_reason": "The reviewed handler requires a task ID and at least one non-empty tag code before invoking tag_todo." }, "todo.update_todo_tag": { - "parameters": { - "user-tags": { - "property": "userTags", - "required": true, - "interface_type": "array" - } - }, "reviewed": true, "cli_path": "todo tag update", "review_reason": "The reviewed handler requires a non-null JSON array of tag update records and invokes update_todo_tag without a runtime confirmation gate." } } -} +} \ No newline at end of file diff --git a/internal/cli/schema_manual_hints_coverage_more_test.go b/internal/cli/schema_manual_hints_coverage_more_test.go index b37b0d7a..0c0fb39d 100644 --- a/internal/cli/schema_manual_hints_coverage_more_test.go +++ b/internal/cli/schema_manual_hints_coverage_more_test.go @@ -483,3 +483,29 @@ func TestCrossPlatformCoverageEmbeddedManualSchemaHintWrapperErrors(t *testing.T t.Fatalf("ValidateEmbeddedManualAgentExampleDelivery() error = %v", err) } } + +// TestCrossPlatformCoverageBuildManualAgentExampleExecutionPlanDefaultsMissingHintToEmpty pins the +// defensive default: a canonical collected from the declared set whose bound +// entry resolves without a ContractFinal payload and without a hint-file row +// gets an empty hint instead of failing or reusing stale prose. +func TestCrossPlatformCoverageBuildManualAgentExampleExecutionPlanDefaultsMissingHintToEmpty(t *testing.T) { + declaredCmd := &cobra.Command{Use: "get", Run: func(*cobra.Command, []string) {}} + t.Cleanup(func() { ClearRuntimeContractFinalForTest(declaredCmd) }) + RegisterRuntimeContractFinal(declaredCmd, ContractFinalPayload{Title: "T"}) + + bareCmd := &cobra.Command{Use: "get", Run: func(*cobra.Command, []string) {}} + commandSpec := CommandSpec{CanonicalPath: "sample.get", PrimaryCLIPath: "sample get"} + bound := BoundCommandRegistry{ + Commands: []BoundCommandSpec{{CommandSpec: commandSpec, PrimaryCommand: declaredCmd}}, + ByCanonical: map[string]BoundCommandSpec{ + "sample.get": {CommandSpec: commandSpec, PrimaryCommand: bareCmd}, + }, + } + plan, err := buildManualAgentExampleExecutionPlan(bound, nil, ManualAgentHintSet{}) + if err != nil { + t.Fatalf("buildManualAgentExampleExecutionPlan() error = %v", err) + } + if plan.Total != 0 || len(plan.Examples) != 0 { + t.Fatalf("empty defensive hint must plan no examples, got %#v", plan) + } +} diff --git a/internal/cli/schema_runtime_registry.go b/internal/cli/schema_runtime_registry.go index acc74b01..72e9c45d 100644 --- a/internal/cli/schema_runtime_registry.go +++ b/internal/cli/schema_runtime_registry.go @@ -200,6 +200,12 @@ func runtimeToolSpecFromContractFinal(entry runtimeSchemaEntry, final ContractFi constraints := runtimeCommandConstraints(entry.Command) hint := runtimeSchemaHintForEntry(entry) embeddedMeta, _ := embeddedMCPMetadataForEntryFrom(entry, metadata.Agent, metadata.MCP) + // Apply parameter declarations from the ContractFinalPayload before the + // resolver reads them. The decls were put there by AttachSchema at + // DeclareLeafMetadata time; now that all flags exist on the fully-built + // command tree, they can be emitted as dws.schema.* annotations (rank + // native_annotation 620, outranking tool_schema_hint 500). + ApplyParamDecls(entry.Command, final.Parameters) parameters, err := resolveRuntimeParameters(entry.Command, canonicalPath, hint.Parameters, embeddedMeta.Parameters, constraints) if err != nil { return ToolSpec{}, fmt.Errorf("resolve Contract Schema parameters for %s: %w", canonicalPath, err) diff --git a/internal/cli/schema_runtime_registry_coverage_more_test.go b/internal/cli/schema_runtime_registry_coverage_more_test.go index 89fd8373..e1a680b9 100644 --- a/internal/cli/schema_runtime_registry_coverage_more_test.go +++ b/internal/cli/schema_runtime_registry_coverage_more_test.go @@ -329,3 +329,72 @@ func TestCrossPlatformCoverageFinalSchemaProvenanceRejectsInvalidTool(t *testing t.Fatalf("provenance error = %v", err) } } + +// TestCrossPlatformCoverageLegacyMetadataContractOverlaysAndDryRunProvenance +// pins the legacy (non-declared) assembly overlays: embedded Contract Risk and +// runtime-gate annotations rewrite safety with reviewed provenance, and a +// reviewed dry-run capability records its provenance winner. +func TestCrossPlatformCoverageLegacyMetadataContractOverlaysAndDryRunProvenance(t *testing.T) { + entry := runtimeSchemaEntry{ + ProductID: "sample", + ToolName: "run", + CLIName: "run", + CLIPath: "sample run", + PrimaryCLIPath: "sample run", + Source: "test", + IdentityField: commandRegistryIdentityProvenance(BoundCommandSpec{CommandSpec: CommandSpec{ + CanonicalPath: "sample.run", + PrimaryCLIPath: "sample run", + Source: "test", + }}), + } + + riskCmd := &cobra.Command{Use: "run", Short: "short", Long: "long"} + AnnotateRuntimeRisk(riskCmd, "write") + entry.Command = riskCmd + spec, err := runtimeToolSpecFromLegacyMetadata(entry, runtimeSchemaMetadataSources{}) + if err != nil { + t.Fatalf("legacy risk overlay error = %v", err) + } + if spec.Safety.Effect != "write" || spec.Safety.Risk != "medium" || spec.Safety.Confirmation != "user_required" { + t.Fatalf("legacy risk safety = %#v", spec.Safety) + } + for _, field := range []string{"risk", "confirmation", "effect"} { + if got := spec.FieldProvenance[field]; got.Source != "corecmd.contract" || got.SourceRef != "dws.schema.risk" { + t.Fatalf("risk provenance[%s] = %#v", field, got) + } + } + + gateCmd := &cobra.Command{Use: "run", Short: "short", Long: "long"} + AnnotateRuntimeGate(gateCmd, "devAppRequireWriteGuard") + entry.Command = gateCmd + spec, err = runtimeToolSpecFromLegacyMetadata(entry, runtimeSchemaMetadataSources{}) + if err != nil { + t.Fatalf("legacy gate overlay error = %v", err) + } + if spec.Safety.Confirmation != "user_required" || spec.Safety.Effect != "write" { + t.Fatalf("legacy gate safety = %#v", spec.Safety) + } + for _, field := range []string{"runtime_gate", "confirmation"} { + if got := spec.FieldProvenance[field]; got.Source != "corecmd.contract" || got.SourceRef != "dws.schema.runtime_gate" { + t.Fatalf("gate provenance[%s] = %#v", field, got) + } + } + + oldDryRun := resolveReviewedDryRun + t.Cleanup(func() { resolveReviewedDryRun = oldDryRun }) + resolveReviewedDryRun = func(string) (*DryRunSpec, error) { + return &DryRunSpec{PreviewKind: DryRunPreviewInvocation}, nil + } + entry.Command = &cobra.Command{Use: "run", Short: "short", Long: "long"} + spec, err = runtimeToolSpecFromLegacyMetadata(entry, runtimeSchemaMetadataSources{}) + if err != nil { + t.Fatalf("legacy dry-run error = %v", err) + } + if spec.DryRun == nil || spec.DryRun.PreviewKind != DryRunPreviewInvocation { + t.Fatalf("legacy dry_run = %#v", spec.DryRun) + } + if got := spec.FieldProvenance["dry_run"]; got.Source != "reviewed_dry_run_registry" { + t.Fatalf("dry_run provenance = %#v", got) + } +} diff --git a/internal/cli/schema_small_coverage_test.go b/internal/cli/schema_small_coverage_test.go index 98c11e9b..a68c0305 100644 --- a/internal/cli/schema_small_coverage_test.go +++ b/internal/cli/schema_small_coverage_test.go @@ -388,10 +388,10 @@ func resetReviewedDryRunCapabilitiesForTest() { reviewedDryRunCapabilitiesLazy.err = nil } -// TestDeclaredDryRunCapabilityAutoReviewed pins the single-source rule: a +// TestCrossPlatformCoverageDeclaredDryRunCapabilityAutoReviewed pins the single-source rule: a // Contract-declared dry_run needs no manual allowlist entry to pass the // delivery gate, and a conflicting manual entry is a hard error. -func TestDeclaredDryRunCapabilityAutoReviewed(t *testing.T) { +func TestCrossPlatformCoverageDeclaredDryRunCapabilityAutoReviewed(t *testing.T) { clearDeclaredDryRunCapabilitiesForTest() t.Cleanup(clearDeclaredDryRunCapabilitiesForTest) originalGroups := reviewedDryRunCapabilityGroups @@ -546,3 +546,40 @@ func cloneCoverageSchemaMap(source map[string]any) map[string]any { } return out } + +// TestCrossPlatformCoverageDeclaredDryRunCapabilityIndexGuards pins the defensive edges of the +// declared dry-run index: blank canonicals are ignored, a successful lookup +// returns a copy, and corrupted index entries fail closed. +func TestCrossPlatformCoverageDeclaredDryRunCapabilityIndexGuards(t *testing.T) { + clearDeclaredDryRunCapabilitiesForTest() + t.Cleanup(clearDeclaredDryRunCapabilitiesForTest) + + recordDeclaredDryRunCapability(" ", DryRunSpec{PreviewKind: DryRunPreviewInvocation}) + capabilities, err := loadReviewedDryRunCapabilities() + if err != nil { + t.Fatal(err) + } + for canonical := range capabilities { + if strings.TrimSpace(canonical) == "" { + t.Fatal("blank canonical must not be recorded") + } + } + + recordDeclaredDryRunCapability("sample.lookup", DryRunSpec{PreviewKind: DryRunPreviewInvocation}) + spec, err := reviewedDryRunCapability(" sample.lookup ") + if err != nil || spec == nil || spec.PreviewKind != DryRunPreviewInvocation { + t.Fatalf("declared capability lookup = %#v, %v", spec, err) + } + + clearDeclaredDryRunCapabilitiesForTest() + declaredDryRunCapabilities.Store(42, DryRunSpec{PreviewKind: DryRunPreviewInvocation}) + if _, err := loadReviewedDryRunCapabilities(); err == nil || !strings.Contains(err.Error(), "non-string key") { + t.Fatalf("non-string key error = %v", err) + } + declaredDryRunCapabilities.Delete(42) + + declaredDryRunCapabilities.Store("sample.bad", "not-a-spec") + if _, err := loadReviewedDryRunCapabilities(); err == nil || !strings.Contains(err.Error(), "non-DryRunSpec value") { + t.Fatalf("non-DryRunSpec value error = %v", err) + } +} diff --git a/internal/corecmd/corecmd.go b/internal/corecmd/corecmd.go index 68e3e5b1..2aaec1a2 100644 --- a/internal/corecmd/corecmd.go +++ b/internal/corecmd/corecmd.go @@ -733,14 +733,39 @@ func sliceValue(cmd *cobra.Command, flag FlagSpec) []string { return nil } +// bindKey is the toolArgs key for a flag: the declared Bind, or the flag name +// converted kebab-to-camel. +// +// The alternative default — sending the kebab name verbatim — is accepted by no +// backend, so a forgotten Bind used to produce a silently wrong payload key; +// deriving it makes that failure mode correct instead. +// +// It is deliberately *not* a licence to drop the explicit declarations: Bind is +// also the declared source of the Schema property mapping, and relying on this +// derivation downgrades that field's provenance to flag_name_inference, which +// the "declared or annotated, never inference-only" rule forbids (measured: +// dropping the 93 mechanical declarations produced 552 lines of catalog drift). +func bindKey(flag FlagSpec) string { + if flag.Bind != "" { + return flag.Bind + } + parts := strings.Split(flag.Name, "-") + if len(parts) <= 1 { + return flag.Name + } + for i := 1; i < len(parts); i++ { + if parts[i] != "" { + parts[i] = strings.ToUpper(parts[i][:1]) + parts[i][1:] + } + } + return strings.Join(parts, "") +} + // BuildArgs assembles toolArgs from the flag set by binding relationship. func BuildArgs(cmd *cobra.Command, flags []FlagSpec) (map[string]any, error) { toolArgs := map[string]any{} for _, flag := range flags { - bind := flag.Bind - if bind == "" { - bind = flag.Name - } + bind := bindKey(flag) if flag.Kind == KindInt { v, err := integerValue(cmd, flag) if err != nil { @@ -1117,6 +1142,12 @@ func embedContractIntoSchema(cmd *cobra.Command, spec Spec) { if len(flag.Enum) > 0 { cli.AnnotateRuntimeFlagEnum(cmd, name, flag.Enum...) } + // Same class as Enum: a declared rule the reviewed registry cannot + // derive. Without it a credential that is mandatory under one + // identity is published as plainly optional. + if flag.RequiredWhen != "" { + cli.AnnotateRuntimeFlagRequiredWhen(cmd, name, flag.RequiredWhen) + } } embedSchemaDecl(cmd, spec) return @@ -1237,6 +1268,20 @@ func AttachSchema(cmd *cobra.Command, safety cli.SafetySpec, schema SchemaDecl, Aliases: id.Aliases, Source: strings.TrimSpace(id.Source), } } + if len(schema.Parameters) > 0 { + payload.Parameters = make([]cli.ParamDecl, 0, len(schema.Parameters)) + for _, p := range schema.Parameters { + payload.Parameters = append(payload.Parameters, cli.ParamDecl{ + Name: p.Name, + Property: p.Property, + Required: p.Required, + InterfaceType: p.InterfaceType, + Description: p.Description, + RequiredWhen: p.RequiredWhen, + Enum: p.Enum, + }) + } + } cli.RegisterRuntimeContractFinal(cmd, payload) } diff --git a/internal/corecmd/corecmd_test.go b/internal/corecmd/corecmd_test.go index dcc183ea..5fb0088b 100644 --- a/internal/corecmd/corecmd_test.go +++ b/internal/corecmd/corecmd_test.go @@ -16,6 +16,7 @@ package corecmd import ( "errors" "reflect" + "runtime" "strings" "testing" @@ -1201,7 +1202,7 @@ func TestRegisterFlagTypedDefaults(t *testing.T) { } } -func TestBuildArgsIntArgDefaultFloor(t *testing.T) { +func TestCrossPlatformCoverageBuildArgsIntArgDefaultFloor(t *testing.T) { flags := []FlagSpec{{ Name: "page-size", Usage: "P", Kind: KindInt, Default: "20", ArgDefault: "20", Bind: "pageSize", @@ -1249,3 +1250,362 @@ func TestNewCommandMergesConstParams(t *testing.T) { t.Fatalf("precheckOnly = %#v, want false", got["precheckOnly"]) } } + +func TestCrossPlatformCoverageNewCommandConfirmFirstAnnotationAndOrder(t *testing.T) { + ran := false + build := func(postMount func(*cobra.Command)) *cobra.Command { + cmd := New(Spec{ + Use: "guard-first", + Safety: testWriteSafety(), + ConfirmFirst: true, + Flags: []FlagSpec{{Name: "x", Usage: "X", Required: true}}, + PostMount: postMount, + Invoke: func(*Ctx, map[string]any) error { + ran = true + return nil + }, + }) + cmd.PersistentFlags().Bool("yes", false, "") + cmd.PersistentFlags().Bool("dry-run", false, "") + cmd.SilenceErrors = true + cmd.SilenceUsage = true + cmd.SetErr(&strings.Builder{}) + return cmd + } + + // The declared marker is stamped even when a PostMount hook dropped the + // annotations map (defensive re-init path). + cleared := build(func(c *cobra.Command) { c.Annotations = nil }) + if cleared.Annotations[ConfirmFirstAnnotation] != "true" { + t.Fatalf("annotations = %#v, want declared ConfirmFirst marker", cleared.Annotations) + } + if !HasDeclaredConfirmFirst(cleared) { + t.Fatal("HasDeclaredConfirmFirst must report a declared guard-first command") + } + if HasDeclaredConfirmFirst(nil) || HasDeclaredConfirmFirst(newTestCommand()) { + t.Fatal("HasDeclaredConfirmFirst must be false for nil/undeclared commands") + } + + // Guard-first confirms BEFORE required validation: declining cancels even + // though the required --x is missing. + ran = false + declined := build(nil) + declined.SetIn(strings.NewReader("no\n")) + declined.SetArgs(nil) + if err := declined.Execute(); err == nil || !strings.Contains(err.Error(), "用户取消了操作") { + t.Fatalf("declined guard-first err = %v", err) + } + if ran { + t.Fatal("declined guard-first must not dispatch") + } + + // After confirming, the declared Required check still gates dispatch. + ran = false + confirmed := build(nil) + confirmed.SetIn(strings.NewReader("yes\n")) + confirmed.SetArgs(nil) + if err := confirmed.Execute(); err == nil || !strings.Contains(err.Error(), "x") { + t.Fatalf("confirmed guard-first required err = %v", err) + } + if ran { + t.Fatal("guard-first must not dispatch with the required flag missing") + } + + // A satisfied declaration dispatches exactly once, without a second prompt. + ran = false + satisfied := build(nil) + satisfied.SetIn(strings.NewReader("yes\n")) + satisfied.SetArgs([]string{"--x", "v"}) + if err := satisfied.Execute(); err != nil { + t.Fatalf("satisfied guard-first err = %v", err) + } + if !ran { + t.Fatal("confirmed guard-first did not dispatch") + } +} + +func TestCrossPlatformCoverageNewCommandRunEHonorsConfirmation(t *testing.T) { + // The escape hatch keeps the declared confirmation gate: an unanswerable + // prompt fails closed before the RunE body runs. + ran := false + build := func() *cobra.Command { + cmd := New(Spec{ + Use: "guarded-rune", + Safety: testWriteSafety(), + Flags: []FlagSpec{{Name: "x", Usage: "X"}}, + RunE: func(*cobra.Command, []string) error { + ran = true + return nil + }, + }) + cmd.PersistentFlags().Bool("yes", false, "") + cmd.PersistentFlags().Bool("dry-run", false, "") + cmd.SilenceErrors = true + cmd.SilenceUsage = true + cmd.SetErr(&strings.Builder{}) + return cmd + } + + blocked := build() + blocked.SetIn(strings.NewReader("")) + blocked.SetArgs(nil) + err := blocked.Execute() + var appErr *apperrors.Error + if !errors.As(err, &appErr) || appErr.Reason != "confirmation_required" { + t.Fatalf("RunE confirmation err = %#v, want confirmation_required", err) + } + if ran { + t.Fatal("RunE body must not run when confirmation fails") + } + + confirmed := build() + confirmed.SetArgs([]string{"--yes"}) + if err := confirmed.Execute(); err != nil { + t.Fatalf("confirmed RunE err = %v", err) + } + if !ran { + t.Fatal("confirmed RunE did not run") + } +} + +func TestCrossPlatformCoverageNewCommandPreflightEnumGate(t *testing.T) { + dispatched := false + cmd := New(Spec{ + Use: "enum-gate", + Flags: []FlagSpec{{Name: "mode", Usage: "M", Enum: []string{"a", "b"}}}, + Invoke: func(*Ctx, map[string]any) error { + dispatched = true + return nil + }, + }) + cmd.SilenceErrors = true + cmd.SilenceUsage = true + cmd.SetArgs([]string{"--mode", "zzz"}) + if err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), `参数 --mode 取值 "zzz" 不合法`) { + t.Fatalf("preflight enum err = %v", err) + } + if dispatched { + t.Fatal("enum failure must stop before dispatch") + } +} + +func TestCrossPlatformCoverageValidateRequiredShortcutMessages(t *testing.T) { + // Without an authored RequiredError the unified default wording applies. + plain := []FlagSpec{{Name: "target", Usage: "T", Required: true, ValidationMode: ValidationShortcut}} + cmd := newTestCommand() + RegisterFlags(cmd, plain) + if err := ValidateRequired(cmd, plain); err == nil || err.Error() != "缺少必填参数 --target" { + t.Fatalf("shortcut default message = %v", err) + } + + // A provided-but-blank list still fails with the emptiness wording. + slice := []FlagSpec{{ + Name: "ids", Usage: "I", Kind: KindStringSlice, Required: true, + ValidationMode: ValidationShortcut, RequiredError: "缺少必填参数 --ids:列表", + }} + cmd = newTestCommand() + RegisterFlags(cmd, slice) + _ = cmd.Flags().Set("ids", " , ") + if err := ValidateRequired(cmd, slice); err == nil || !strings.Contains(err.Error(), "必填参数 --ids 不能为空") { + t.Fatalf("shortcut blank slice message = %v", err) + } + + // A provided-but-blank string fails with the same emptiness wording. + blank := []FlagSpec{{Name: "name", Usage: "N", Required: true, ValidationMode: ValidationShortcut}} + cmd = newTestCommand() + RegisterFlags(cmd, blank) + _ = cmd.Flags().Set("name", " ") + if err := ValidateRequired(cmd, blank); err == nil || !strings.Contains(err.Error(), "必填参数 --name 不能为空") { + t.Fatalf("shortcut blank string message = %v", err) + } + + // Shortcut-mode enum violations surface through ValidateRequired too. + enum := []FlagSpec{{Name: "mode", Usage: "M", ValidationMode: ValidationShortcut, Enum: []string{"a", "b"}}} + cmd = newTestCommand() + RegisterFlags(cmd, enum) + _ = cmd.Flags().Set("mode", "z") + if err := ValidateRequired(cmd, enum); err == nil || !strings.Contains(err.Error(), `参数 --mode 取值 "z" 不合法`) { + t.Fatalf("shortcut enum message = %v", err) + } +} + +func TestCrossPlatformCoverageBuildArgsInvalidArgDefault(t *testing.T) { + flags := []FlagSpec{{Name: "page-size", Usage: "P", Kind: KindInt, ArgDefault: "not-a-number"}} + cmd := newTestCommand() + RegisterFlags(cmd, flags) + if _, err := BuildArgs(cmd, flags); err == nil || !strings.Contains(err.Error(), `invalid ArgDefault "not-a-number"`) { + t.Fatalf("invalid ArgDefault err = %v", err) + } +} + +func TestCrossPlatformCoverageBindKeyKebabToCamel(t *testing.T) { + // Declared Bind always wins. + if got := bindKey(FlagSpec{Name: "disable-ssl-verify", Bind: "disableSSLVerify"}); got != "disableSSLVerify" { + t.Fatalf("explicit bind = %q", got) + } + // Single-word names pass through unchanged. + if got := bindKey(FlagSpec{Name: "plain"}); got != "plain" { + t.Fatalf("single-word bind = %q", got) + } + // Kebab names camelize mechanically; empty segments are skipped. + if got := bindKey(FlagSpec{Name: "page-size"}); got != "pageSize" { + t.Fatalf("kebab bind = %q", got) + } + if got := bindKey(FlagSpec{Name: "a--b-c"}); got != "aBC" { + t.Fatalf("empty-segment bind = %q", got) + } + + // The derived key is what BuildArgs actually transmits. + flags := []FlagSpec{{Name: "page-size", Usage: "P", Kind: KindInt}} + cmd := newTestCommand() + RegisterFlags(cmd, flags) + _ = cmd.Flags().Set("page-size", "5") + args, err := BuildArgs(cmd, flags) + if err != nil { + t.Fatal(err) + } + if args["pageSize"] != 5 { + t.Fatalf("camelized args = %#v, want pageSize=5", args) + } +} + +// failingReader returns a non-EOF error so the confirmation read path must +// fail closed instead of treating the answer as a decline. +type failingReader struct{} + +func (failingReader) Read([]byte) (int, error) { return 0, errors.New("stdin read failed") } + +func TestCrossPlatformCoverageConfirmSafetyReadErrorFailsClosed(t *testing.T) { + cmd := newTestCommand() + cmd.PersistentFlags().Bool("yes", false, "") + cmd.SetIn(failingReader{}) + cmd.SetErr(&strings.Builder{}) + err := ConfirmSafety(cmd, testWriteSafety()) + var appErr *apperrors.Error + if !errors.As(err, &appErr) || appErr.Reason != "confirmation_required" { + t.Fatalf("non-EOF stdin error must be confirmation_required, got %#v", err) + } +} + +func TestCrossPlatformCoverageConfirmSafetyTerminalPromptsBeforeReading(t *testing.T) { + master, slave, ok := openTestPTY(t) + if !ok { + t.Skipf("no native pty on %s: the interactive prompt branch is terminal-only (unix)", runtime.GOOS) + } + defer master.Close() + defer slave.Close() + + // Queue the answer on the master side before ConfirmSafety reads so the + // canonical-mode read can never block the test. + if _, err := master.WriteString("yes\n"); err != nil { + t.Fatalf("write pty answer: %v", err) + } + var stderr strings.Builder + cmd := newTestCommand() + cmd.PersistentFlags().Bool("yes", false, "") + cmd.PersistentFlags().Bool("dry-run", false, "") + cmd.SetIn(slave) + cmd.SetErr(&stderr) + if err := ConfirmSafety(cmd, testWriteSafety()); err != nil { + t.Fatalf("terminal yes answer must confirm, got %v", err) + } + if !strings.Contains(stderr.String(), "确认继续") { + t.Fatalf("terminal stdin must print the interactive prompt, got %q", stderr.String()) + } +} + +func TestCrossPlatformCoverageBoolFlagGettersNilCommand(t *testing.T) { + if got := boolFlagGetters(nil); got != nil { + t.Fatalf("boolFlagGetters(nil) = %#v, want nil", got) + } +} + +func TestCrossPlatformCoverageEmbedContractCobraProjection(t *testing.T) { + cmd := New(Spec{ + Use: "shortcut-leaf", + Short: "short", + ParameterProjection: ProjectCobraParameters, + Flags: []FlagSpec{ + { + Name: "mode", Usage: "M", Required: true, + Enum: []string{"a", "b"}, RequiredWhen: "when identity=user", + }, + {Name: "internal", Usage: "I", Hidden: true, Required: true}, + }, + Safety: testWriteSafety(), + Schema: SchemaDecl{ + Description: "desc", + Interface: &InterfaceDecl{Mode: "mcp", Availability: "available", ProductID: "dev", RPCName: "op"}, + Selection: SelectionDecl{ + AgentSummary: "s", UseWhen: []string{"u"}, + AvoidWhen: []string{"a"}, Examples: []string{"dws shortcut-leaf"}, + }, + }, + Invoke: func(*Ctx, map[string]any) error { return nil }, + }) + + mode := cmd.Flags().Lookup("mode") + if got := mode.Annotations["dws.schema.required"]; len(got) == 0 || got[0] != "true" { + t.Fatalf("cobra projection must annotate Required, got %#v", mode.Annotations) + } + if got := mode.Annotations["x-cli-enum"]; len(got) != 2 { + t.Fatalf("cobra projection must annotate Enum, got %#v", got) + } + if got := mode.Annotations["dws.schema.required_when"]; len(got) == 0 || got[0] != "when identity=user" { + t.Fatalf("cobra projection must annotate RequiredWhen, got %#v", got) + } + // Hidden flags stay Cobra-only facts: no projection annotations. + if got := cmd.Flags().Lookup("internal").Annotations["dws.schema.required"]; len(got) != 0 { + t.Fatalf("hidden flag must not be projected, got %#v", got) + } + // The authored SchemaDecl still lands as the typed ContractFinal. + final, ok := cli.RuntimeContractFinal(cmd) + if !ok || final.Description != "desc" { + t.Fatalf("cobra projection must still embed SchemaDecl, final=%#v ok=%v", final, ok) + } +} + +func TestCrossPlatformCoverageAttachSchemaNilAndEmptyGuards(t *testing.T) { + // Both guards are no-ops: nil command, and a command with no authored decl. + AttachSchema(nil, testWriteSafety(), SchemaDecl{Description: "d"}, "s", "l") + cmd := newTestCommand() + AttachSchema(cmd, testWriteSafety(), SchemaDecl{}, "s", "l") + if cli.HasRuntimeContractFinal(cmd) { + t.Fatal("empty SchemaDecl must not register a ContractFinal") + } +} + +func TestCrossPlatformCoverageEffectiveSafetySpecZeroValueDefaultsToRead(t *testing.T) { + got := effectiveSafetySpec(cli.SafetySpec{}) + want := cli.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"} + if got != want { + t.Fatalf("effectiveSafetySpec(zero) = %+v, want %+v", got, want) + } + partial := effectiveSafetySpec(cli.SafetySpec{Effect: " write ", Risk: "high", Confirmation: "user_required", Idempotency: "unknown"}) + if partial.Effect != "write" || partial.Risk != "high" { + t.Fatalf("effectiveSafetySpec(trim) = %+v", partial) + } +} + +func TestCrossPlatformCoverageEmbedContractSkipsBlankAndHiddenFlags(t *testing.T) { + cmd := &cobra.Command{Use: "skip-blank-hidden"} + cmd.Flags().String("visible", "", "visible flag") + cmd.Flags().String("ghost", "", "hidden flag") + embedContractIntoSchema(cmd, Spec{ + Use: "skip-blank-hidden", + Flags: []FlagSpec{ + {Name: " ", Usage: "blank name is skipped"}, + {Name: "ghost", Usage: "hidden is skipped", Hidden: true}, + {Name: "visible", Usage: "annotated", Required: true}, + }, + }) + anns := cmd.Annotations + if anns == nil { + t.Fatal("expected runtime contract annotations") + } + for key := range anns { + if strings.Contains(key, "ghost") { + t.Fatalf("hidden flag must not be annotated: %s", key) + } + } +} diff --git a/internal/corecmd/pty_darwin_test.go b/internal/corecmd/pty_darwin_test.go new file mode 100644 index 00000000..0b64e232 --- /dev/null +++ b/internal/corecmd/pty_darwin_test.go @@ -0,0 +1,67 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build darwin + +package corecmd + +import ( + "os" + "strings" + "testing" + "unsafe" + + "golang.org/x/sys/unix" +) + +// openTestPTY allocates a real pty pair so the terminal-only interactive +// prompt branch of ConfirmSafety can run natively on macOS. The slave side is +// a genuine TTY (isatty true); the master side feeds it the scripted answer. +// It reports ok=false instead of failing when the environment cannot allocate +// a pty (e.g. a sandbox without /dev/ptmx), so callers skip gracefully. +func openTestPTY(t *testing.T) (master, slave *os.File, ok bool) { + t.Helper() + m, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) + if err != nil { + t.Logf("open /dev/ptmx: %v", err) + return nil, nil, false + } + fd := int(m.Fd()) + if err := unix.IoctlSetInt(fd, unix.TIOCPTYGRANT, 0); err != nil { + t.Logf("grantpt: %v", err) + _ = m.Close() + return nil, nil, false + } + if err := unix.IoctlSetInt(fd, unix.TIOCPTYUNLK, 0); err != nil { + t.Logf("unlockpt: %v", err) + _ = m.Close() + return nil, nil, false + } + // ptsname(3): TIOCPTYGNAME fills a 128-byte buffer with the slave path. + buf := make([]byte, 128) + if _, _, errno := unix.Syscall( + unix.SYS_IOCTL, uintptr(fd), uintptr(unix.TIOCPTYGNAME), uintptr(unsafe.Pointer(&buf[0])), + ); errno != 0 { + t.Logf("ptsname ioctl: %v", errno) + _ = m.Close() + return nil, nil, false + } + name := strings.TrimRight(string(buf), "\x00") + s, err := os.OpenFile(name, os.O_RDWR, 0) + if err != nil { + t.Logf("open pty slave %s: %v", name, err) + _ = m.Close() + return nil, nil, false + } + return m, s, true +} diff --git a/internal/corecmd/pty_unsupported_test.go b/internal/corecmd/pty_unsupported_test.go new file mode 100644 index 00000000..5f1d7082 --- /dev/null +++ b/internal/corecmd/pty_unsupported_test.go @@ -0,0 +1,30 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !darwin + +package corecmd + +import ( + "os" + "testing" +) + +// openTestPTY reports no native pty support off macOS. ConfirmSafety's +// interactive prompt branch requires a real terminal, which cannot be +// synthesized portably on Windows; the caller skips with an explicit reason +// so the platform coverage gate records the unix-only branch honestly. +func openTestPTY(t *testing.T) (master, slave *os.File, ok bool) { + t.Helper() + return nil, nil, false +} diff --git a/internal/corecmd/schema_decl.go b/internal/corecmd/schema_decl.go index 61733740..0eeb9bfb 100644 --- a/internal/corecmd/schema_decl.go +++ b/internal/corecmd/schema_decl.go @@ -25,18 +25,48 @@ import ( // Cobra leaf and Schema assembly pass-throughs the values — no reviewed / // hints parallel authority for those fields. // -// Shape mirrors cli.ToolSpec groups (minus Parameters/Constraints, which come -// from Flags/Constraints, and minus FieldProvenance, which is derived). +// Shape mirrors cli.ToolSpec groups (minus Constraints, which come from +// Constraints, and minus FieldProvenance, which is derived). type SchemaDecl struct { Title string Description string Positionals []PositionalDecl + Parameters []ParamDecl DryRun *DryRunDecl Interface *InterfaceDecl Selection SelectionDecl Identity IdentityDecl } +// ParamDecl declares Schema facts about a flag that the command registers +// itself. It exists because metadata-mode commands (DeclareLeafMetadata) own +// their flag registration and cannot use FlagSpec; without this channel their +// parameter facts could only live in schema_hints/metadata overlays. +// +// Each field maps to a dws.schema.* annotation at rank native_annotation (620), +// which outranks tool_schema_hint (500). A declared value therefore wins over +// any remaining hint overlay for the same flag, making the overlay redundant +// once the declaration is in place. +type ParamDecl struct { + // Name is the cobra flag name (kebab-case), e.g. "record-ids". + Name string + // Property is the interface property name (camelCase), e.g. "recordIds". + // Empty means the flag name is used as-is. + Property string + // Required overrides the cobra-level required marker for Schema purposes. + // Use a pointer so "not declared" (nil) is distinct from "explicitly false". + Required *bool + // InterfaceType is the wire type for the interface property, e.g. "string", + // "integer", "boolean", "array". Empty means derived from the flag kind. + InterfaceType string + // Description overrides the flag's help text for Schema purposes. + Description string + // RequiredWhen is a conditional-required expression, e.g. "identity=bot". + RequiredWhen string + // Enum restricts accepted values (string flags only). + Enum []string +} + // PositionalDecl is one ordered CLI argument in the Schema positionals list. type PositionalDecl struct { Name string @@ -146,6 +176,9 @@ func (s SchemaDecl) empty() bool { if len(s.Positionals) > 0 { return false } + if len(s.Parameters) > 0 { + return false + } if s.DryRun != nil && strings.TrimSpace(s.DryRun.PreviewKind) != "" { return false } diff --git a/internal/corecmd/schema_decl_test.go b/internal/corecmd/schema_decl_test.go index 1e644f1a..1af81d8d 100644 --- a/internal/corecmd/schema_decl_test.go +++ b/internal/corecmd/schema_decl_test.go @@ -20,7 +20,7 @@ import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" ) -func TestNewCommandEmbedsFullSchemaDeclAsFinalSource(t *testing.T) { +func TestCrossPlatformCoverageNewCommandEmbedsFullSchemaDeclAsFinalSource(t *testing.T) { cmd := New(Spec{ Use: "create", Short: "short", @@ -142,7 +142,7 @@ func TestNewCommandFallsBackToDeclaredDescriptionWithoutLong(t *testing.T) { } } -func TestNewCommandPanicsOnPartialSchemaDecl(t *testing.T) { +func TestCrossPlatformCoverageNewCommandPanicsOnPartialSchemaDecl(t *testing.T) { for _, tc := range []struct { name string schema SchemaDecl @@ -279,7 +279,7 @@ func TestSchemaDeclEmptySkipsFinal(t *testing.T) { } } -func TestNewCommandRejectsPartialSafetySpec(t *testing.T) { +func TestCrossPlatformCoverageNewCommandRejectsPartialSafetySpec(t *testing.T) { defer func() { recovered := recover() if recovered == nil { @@ -295,3 +295,36 @@ func TestNewCommandRejectsPartialSafetySpec(t *testing.T) { Invoke: func(*Ctx, map[string]any) error { return nil }, }) } + +func TestCrossPlatformCoverageSchemaDeclEmptyReportsEveryAuthoredSection(t *testing.T) { + if !(SchemaDecl{}).Empty() { + t.Fatal("zero SchemaDecl must be empty") + } + + authored := map[string]SchemaDecl{ + "title": {Title: "T"}, + "description": {Description: "D"}, + "positionals": {Positionals: []PositionalDecl{{Name: "id"}}}, + "dry_run": {DryRun: &DryRunDecl{PreviewKind: "request"}}, + "interface": {Interface: &InterfaceDecl{Mode: "mcp"}}, + "selection": {Selection: SelectionDecl{Tips: []string{"tip"}}}, + "identity": {Identity: IdentityDecl{Name: "tool"}}, + } + for name, decl := range authored { + if decl.Empty() { + t.Fatalf("%s: authored section must make SchemaDecl non-empty", name) + } + } + + // Pointers without any authored payload still count as empty: a bare + // &DryRunDecl{} / &InterfaceDecl{} carries no final Schema fact. + unauthored := map[string]SchemaDecl{ + "dry_run pointer": {DryRun: &DryRunDecl{RemoteReads: true}}, + "interface pointer": {Interface: &InterfaceDecl{}}, + } + for name, decl := range unauthored { + if !decl.Empty() { + t.Fatalf("%s: payload-free pointer must stay empty", name) + } + } +} diff --git a/internal/helpers/aisearch.go b/internal/helpers/aisearch.go index 95ae2c76..1fb3ce66 100644 --- a/internal/helpers/aisearch.go +++ b/internal/helpers/aisearch.go @@ -1,12 +1,17 @@ package helpers import ( - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" "strings" "github.com/spf13/cobra" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" ) +// boolPtr returns a pointer to v, for ParamDecl.Required declarations. +func boolPtr(v bool) *bool { return &v } + // aisearchKeywordAliases 是 --keyword flag 的同义瞎猜兜底列表。 // 模型可能写 --name / --q / --query / --text,这些都被识别为 keyword。 var aisearchKeywordAliases = []string{"name", "q", "query", "text"} @@ -272,6 +277,10 @@ func newAisearchCommand() *cobra.Command { dws aisearch enterprise --queries "OKR" --types document,im,mail`, RunE: runAisearchEnterprise, } + // Register flags that carry ParamDecl before DeclareLeafMetadata so + // AttachSchema can emit their dws.schema.* annotations. + enterpriseCmd.Flags().String("queries", "", "内容关键词列表,多个用逗号分隔;汇总类场景可留空") + enterpriseCmd.Flags().String("time-range", "", "时间范围,仅当用户显式给出时间词时填写,如 今天/本周/9月/过去一周") DeclareLeafMetadata(enterpriseCmd, LeafSpec{ Safety: cli.SafetySpec{ Effect: "read", Risk: "low", @@ -299,15 +308,17 @@ func newAisearchCommand() *cobra.Command { "dws aisearch enterprise --queries \"OKR\" --types mail --time-range \"最近\" --format json", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "queries", Required: boolPtr(false)}, + {Name: "time-range", Required: boolPtr(false)}, + }, }, }) - enterpriseCmd.Flags().String("queries", "", "内容关键词列表,多个用逗号分隔;汇总类场景可留空") enterpriseCmd.Flags().String("types", "all", "搜索类型: all/document/im/calendar/todo/minute/report/image/link/notable/baike/mail,多个用逗号分隔") enterpriseCmd.Flags().String("search-types", "", "--types 的别名") _ = enterpriseCmd.Flags().MarkHidden("search-types") enterpriseCmd.Flags().String("searchTypes", "", "--types 的别名") _ = enterpriseCmd.Flags().MarkHidden("searchTypes") - enterpriseCmd.Flags().String("time-range", "", "时间范围,仅当用户显式给出时间词时填写,如 今天/本周/9月/过去一周") enterpriseCmd.Flags().String("timeRange", "", "--time-range 的别名") _ = enterpriseCmd.Flags().MarkHidden("timeRange") addAisearchKeywordCompatibilityFlag(enterpriseCmd) diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index bb4add8a..b3e2a79c 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -20,6 +20,8 @@ import ( apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" "github.com/spf13/cobra" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" ) func resolveMessageForward(cmd *cobra.Command, defaultForward bool) (bool, error) { @@ -2860,6 +2862,18 @@ func newChatCommand() *cobra.Command { AvoidWhen: []string{"发送新消息应使用 chat message send;撤回消息应使用 chat message recall"}, Examples: []string{"dws chat message edit --conversation-id --msg-id --text \"更新后的内容\""}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "at-all", Property: "atAll", Required: boolPtr(false), InterfaceType: "boolean"}, + {Name: "at-open-dingtalk-ids", Property: "atOpenDingTalkIds", Required: boolPtr(false), InterfaceType: "array"}, + {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "content", Property: "content", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(true)}, + {Name: "group", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "msg-id", Property: "openMessageId", Required: boolPtr(true)}, + {Name: "text", Property: "text", Required: boolPtr(false)}, + {Name: "title", Property: "title", Required: boolPtr(false)}, + }, }, }) @@ -3694,6 +3708,11 @@ func newChatCommand() *cobra.Command { AvoidWhen: []string{"列出全部自定义分组应使用 chat category list;按 categoryId 查详情应使用 chat category batch-info"}, Examples: []string{"dws chat category list-by-conv --group "}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "group", Property: "openConversationId", Required: boolPtr(true)}, + {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, + }, }, }) @@ -3733,6 +3752,9 @@ func newChatCommand() *cobra.Command { AvoidWhen: []string{"不知道 categoryId 时先用 chat category list;按会话反查所属分组应使用 chat category list-by-conv"}, Examples: []string{"dws chat category batch-info --category-ids 123,456"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "category-ids", Property: "categoryIds", Required: boolPtr(true), InterfaceType: "array"}, + }, }, }) @@ -3848,6 +3870,12 @@ func newChatCommand() *cobra.Command { AvoidWhen: []string{"发送文本消息或文字表情时不要使用"}, Examples: []string{"dws chat message add-emoji --conversation-id --msg-id --emoji \"赞\""}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "group", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, + }, }, }) chatMessageAddEmojiCmd.Flags().String("conversation-id", "", "会话 openConversationId (必填,支持单聊/群聊)") @@ -3893,6 +3921,12 @@ func newChatCommand() *cobra.Command { AvoidWhen: []string{"移除文字表情时使用 chat message remove-text-emotion"}, Examples: []string{"dws chat message remove-emoji --conversation-id --msg-id --emoji \"赞\""}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "group", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, + }, }, }) chatMessageRemoveEmojiCmd.Flags().String("conversation-id", "", "会话 openConversationId (必填,支持单聊/群聊)") @@ -3940,6 +3974,12 @@ func newChatCommand() *cobra.Command { AvoidWhen: []string{"需要先创建文字表情资源时使用 chat message create-text-emotion"}, Examples: []string{"dws chat message add-text-emotion --conversation-id --msg-id --emotion-id --emotion-name \"赞\" --text \"nice\" --background-id im_bg_5"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "group", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, + }, }, }) chatMessageAddTextEmotionCmd.Flags().String("conversation-id", "", "会话 openConversationId (必填,支持单聊/群聊)") @@ -3990,6 +4030,12 @@ func newChatCommand() *cobra.Command { AvoidWhen: []string{"移除普通 emoji reaction 时使用 chat message remove-emoji"}, Examples: []string{"dws chat message remove-text-emotion --conversation-id --msg-id --emotion-id --emotion-name \"赞\" --text \"nice\" --background-id im_bg_5"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "group", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, + }, }, }) chatMessageRemoveTextEmotionCmd.Flags().String("conversation-id", "", "会话 openConversationId (必填,支持单聊/群聊)") @@ -4041,6 +4087,18 @@ func newChatCommand() *cobra.Command { }, Examples: []string{"dws chat message update-text-emotion --conversation-id --msg-id --old-emotion-id --emotion-id --emotion-name \"处理中\" --text \"处理中 2 分钟\" --background-id im_bg_5"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "background-id", Property: "backgroundId", Required: boolPtr(true), Description: "新文字表情的背景 ID"}, + {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false), Description: "会话 openConversationId;与 --group、--id、--chat 四选一"}, + {Name: "emotion-id", Property: "emotionId", Required: boolPtr(true), Description: "新的文字表情 ID,可通过 create-text-emotion 获取"}, + {Name: "emotion-name", Property: "emotionName", Required: boolPtr(true), Description: "新的文字表情名称"}, + {Name: "group", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "msg-id", Property: "openMsgId", Required: boolPtr(true), Description: "需要原地更新文字表情的消息 openMsgId"}, + {Name: "old-emotion-id", Property: "oldEmotionId", Required: boolPtr(true), Description: "消息上当前文字表情的 emotionId"}, + {Name: "text", Property: "text", Required: boolPtr(true), Description: "新的文字表情内容"}, + }, }, }) chatMessageUpdateTextEmotionCmd.Flags().String("conversation-id", "", "会话 openConversationId (必填,支持单聊/群聊)") @@ -4514,6 +4572,12 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 AvoidWhen: []string{"群成员禁言属于发言权限,应使用 group-mute 命令"}, Examples: []string{"dws chat mute --conversation-id "}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "off", Required: boolPtr(false)}, + }, }, }) chatMuteCmd.Flags().String("conversation-id", "", "会话 openConversationId (必填,支持单聊/群聊)") @@ -4849,6 +4913,9 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 AvoidWhen: []string{"只查看置顶清单时使用 chat list-top-conversations"}, Examples: []string{"dws chat set-top --conversation-id "}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "off", Required: boolPtr(false)}, + }, }, }) chatSetTopCmd.Flags().String("conversation-id", "", "会话 openConversationId (必填,支持单聊/群聊)") @@ -4931,6 +4998,9 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 AvoidWhen: []string{"只禁言指定成员时使用 chat group-mute-member"}, Examples: []string{"dws chat group-mute --group "}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "off", Required: boolPtr(false)}, + }, }, }) chatGroupMuteCmd.Flags().String("group", "", "群聊 openConversationId (必填)") @@ -5014,6 +5084,9 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 AvoidWhen: []string{"需要全员禁言时使用 chat group-mute"}, Examples: []string{"dws chat group-mute-member --group --users userId1,userId2 --mute-time 3600000"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "off", Required: boolPtr(false)}, + }, }, }) chatGroupMuteMemberCmd.Flags().String("group", "", "群聊 openConversationId (必填)") @@ -5081,6 +5154,9 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 AvoidWhen: []string{"自定义业务角色应使用 chat group-role 系列命令"}, Examples: []string{"dws chat group set-admin --group --users userId1,userId2"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "off", Required: boolPtr(false)}, + }, }, }) chatGroupSetAdminCmd.Flags().String("group", "", "群聊 openConversationId (必填)") @@ -6131,6 +6207,10 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 AvoidWhen: []string{"按名称搜索任意可见群时使用 chat search"}, Examples: []string{"dws chat group list-my-groups --role OWNER --limit 100"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "limit", Required: boolPtr(false)}, + {Name: "role", Required: boolPtr(false)}, + }, }, }) chatGroupListMyGroupsCmd.Flags().String("role", "", "角色过滤: OWNER(仅群主) / ADMIN(仅管理员),不传返回全部") @@ -6494,6 +6574,10 @@ status 可选值: "dws chat group update-nick --group ", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "group", Property: "openConversationId", Required: boolPtr(true)}, + {Name: "nick", Property: "nick", Required: boolPtr(false)}, + }, }, }) chatGroupUpdateNickCmd.Flags().String("group", "", "群聊 openConversationId (必填)") @@ -6863,6 +6947,10 @@ status 可选值: AvoidWhen: []string{"新建外部群应使用 chat group create --type EXTERNAL;未确认群主身份和不可逆影响时不要执行"}, Examples: []string{"dws chat group upgrade-to-external --group "}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "extension", Property: "extension", Required: boolPtr(false), InterfaceType: "object"}, + {Name: "group", Property: "openConversationId", Required: boolPtr(true)}, + }, }, }) chatGroupUpgradeToExternalCmd.Flags().String("group", "", "待升级普通群的 openConversationId (必填)") diff --git a/internal/helpers/cmdcore_phase2_coverage_test.go b/internal/helpers/cmdcore_phase2_coverage_test.go new file mode 100644 index 00000000..8c0db5e1 --- /dev/null +++ b/internal/helpers/cmdcore_phase2_coverage_test.go @@ -0,0 +1,524 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "context" + "fmt" + "io" + "reflect" + "strings" + "testing" + + "github.com/spf13/cobra" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" +) + +// contractCoverageSchema returns a minimal LeafSchema accepted by +// corecmd.AttachSchema's declaration completeness rules (test fixture). +func contractCoverageSchema(desc string) LeafSchema { + return LeafSchema{ + Description: desc, + Interface: &LeafInterfaceDecl{ + Mode: "composite", Availability: "available", + Reason: "unit test fixture", + }, + Selection: LeafSelectionDecl{ + AgentSummary: desc, + UseWhen: []string{"unit test"}, + AvoidWhen: []string{"unit test"}, + Examples: []string{"dws test"}, + }, + } +} + +// TestCrossPlatformCoverageContractRuntimeStoreLoadNilGuards covers the nil/miss guards of the +// contractRuntime registry (store nil args, load nil cmd, load miss) and the +// ContractValidate not-registered fallback. +func TestCrossPlatformCoverageContractRuntimeStoreLoadNilGuards(t *testing.T) { + // Nil guards: none of these may panic or register anything. + storeContractRuntime(nil, &contractRuntime{}) + storeContractRuntime(&cobra.Command{Use: "nil-rt"}, nil) + storeContractRuntime(nil, nil) + + if rt, ok := loadContractRuntime(nil); ok || rt != nil { + t.Fatalf("loadContractRuntime(nil) = (%v, %v), want (nil, false)", rt, ok) + } + fresh := &cobra.Command{Use: "fresh"} + if rt, ok := loadContractRuntime(fresh); ok || rt != nil { + t.Fatalf("loadContractRuntime(fresh) = (%v, %v), want (nil, false)", rt, ok) + } + if ContractValidate(fresh) != nil { + t.Fatal("ContractValidate on unregistered command must return nil") + } + if ContractValidate(nil) != nil { + t.Fatal("ContractValidate(nil) must return nil") + } + + // Positive path: stored runtime is returned as-is. + stored := &contractRuntime{validate: func(*cobra.Command, []string) error { return nil }} + cmd := &cobra.Command{Use: "stored"} + storeContractRuntime(cmd, stored) + rt, ok := loadContractRuntime(cmd) + if !ok || rt != stored { + t.Fatalf("loadContractRuntime(stored) = (%v, %v), want stored runtime", rt, ok) + } + if ContractValidate(cmd) == nil { + t.Fatal("ContractValidate must return the stored Validate hook") + } +} + +// TestCrossPlatformCoverageDeclareLeafMetadataPanicsOnExecutionFields covers every metadata-only +// misuse guard in DeclareLeafMetadata (nil cmd + all forbidden LeafSpec +// fields + missing Schema). +func TestCrossPlatformCoverageDeclareLeafMetadataPanicsOnExecutionFields(t *testing.T) { + cases := []struct { + name string + cmd *cobra.Command + spec LeafSpec + want string + }{ + {"nil cmd", nil, LeafSpec{}, "cmd is nil"}, + {"flags", &cobra.Command{Use: "leaf"}, LeafSpec{Flags: []LeafFlag{{Name: "x", Usage: "x"}}}, "Flags must be empty"}, + {"constraints", &cobra.Command{Use: "leaf"}, LeafSpec{Constraints: []LeafConstraint{{Kind: LeafAtLeastOne, Flags: []string{"a", "b"}}}}, "Constraints must be empty"}, + {"const params", &cobra.Command{Use: "leaf"}, LeafSpec{ConstParams: map[string]any{"k": true}}, "ConstParams must be empty"}, + {"call", &cobra.Command{Use: "leaf"}, LeafSpec{Call: func(*cobra.Command, string, map[string]any) error { return nil }}, "Call must be nil"}, + {"runE", &cobra.Command{Use: "leaf"}, LeafSpec{RunE: func(*cobra.Command, []string) error { return nil }}, "RunE must be nil"}, + {"postMount", &cobra.Command{Use: "leaf"}, LeafSpec{PostMount: func(*cobra.Command) {}}, "PostMount must be nil"}, + {"confirmFirst", &cobra.Command{Use: "leaf"}, LeafSpec{ConfirmFirst: true}, "ConfirmFirst must be false"}, + {"server", &cobra.Command{Use: "leaf"}, LeafSpec{Server: "srv"}, "Server/Tool must be empty"}, + {"tool", &cobra.Command{Use: "leaf"}, LeafSpec{Tool: "tool"}, "Server/Tool must be empty"}, + {"empty schema", &cobra.Command{Use: "leaf"}, LeafSpec{}, "Schema is required"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + r := recover() + if r == nil { + t.Fatalf("DeclareLeafMetadata did not panic, want %q", c.want) + } + if msg := fmt.Sprint(r); !strings.Contains(msg, c.want) { + t.Fatalf("panic = %q, want contain %q", msg, c.want) + } + }() + DeclareLeafMetadata(c.cmd, c.spec) + }) + } +} + +// TestCrossPlatformCoverageInstallContractRunEPipelinePanics covers the fail-closed guards of the +// pipeline installer: nil cmd/runtime and a leaf without RunE. +func TestCrossPlatformCoverageInstallContractRunEPipelinePanics(t *testing.T) { + assertPanic := func(name, want string, fn func()) { + t.Helper() + defer func() { + r := recover() + if r == nil { + t.Errorf("%s did not panic", name) + return + } + if msg := fmt.Sprint(r); !strings.Contains(msg, want) { + t.Errorf("%s panic = %q, want contain %q", name, msg, want) + } + }() + fn() + } + assertPanic("nil cmd/runtime", "nil cmd/runtime", func() { + installContractRunEPipeline(nil, nil) + }) + assertPanic("nil RunE", "RunE is nil", func() { + installContractRunEPipeline(&cobra.Command{Use: "leaf"}, &contractRuntime{}) + }) +} + +// TestCrossPlatformCoverageDeclareLeafMetadataValidateWithoutConfirmRunsInner covers the +// non-confirm pipeline: Validate runs first, then the inner RunE executes +// directly (no ConfirmSafety layer for not_required leaves). +func TestCrossPlatformCoverageDeclareLeafMetadataValidateWithoutConfirmRunsInner(t *testing.T) { + ran := false + cmd := &cobra.Command{ + Use: "lookup", + RunE: func(*cobra.Command, []string) error { + ran = true + return nil + }, + } + cmd.Flags().String("id", "", "") + DeclareLeafMetadata(cmd, LeafSpec{ + Safety: cli.SafetySpec{ + Effect: "read", Risk: "low", + Confirmation: "not_required", Idempotency: "idempotent", + }, + Validate: func(c *cobra.Command, args []string) error { + id, _ := c.Flags().GetString("id") + if strings.TrimSpace(id) == "" { + return fmt.Errorf("flag --id is required") + } + return nil + }, + Schema: contractCoverageSchema("test lookup"), + }) + if !HasContractValidate(cmd) { + t.Fatal("expected contract Validate annotation") + } + if HasContractConfirmSafety(cmd) || HasContractConfirmDeferred(cmd) { + t.Fatal("not_required leaf must not carry confirm annotations") + } + cmd.SetIn(strings.NewReader("")) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + cmd.SetArgs([]string{}) + + // Validate failure short-circuits before the inner RunE. + if err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), "flag --id is required") { + t.Fatalf("Execute() error = %v, want Validate failure", err) + } + if ran { + t.Fatal("inner RunE must not run when Validate fails") + } + + // Validate pass: inner RunE runs without any confirmation gate. + if err := cmd.Flags().Set("id", "x"); err != nil { + t.Fatal(err) + } + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v, want nil for not_required leaf", err) + } + if !ran { + t.Fatal("inner RunE must run after Validate passes") + } +} + +// TestCrossPlatformCoverageDeclareLeafMetadataConfirmFallbackWithoutCaller covers the +// no-Validate/no-Caller fallback: without deps the deferred CallTool gate has +// nothing to hook, so ConfirmSafety runs before the inner RunE (fail closed). +func TestCrossPlatformCoverageDeclareLeafMetadataConfirmFallbackWithoutCaller(t *testing.T) { + prev := deps + deps = nil + t.Cleanup(func() { deps = prev }) + + ran := false + cmd := &cobra.Command{ + Use: "purge", + RunE: func(*cobra.Command, []string) error { + ran = true + return nil + }, + } + cmd.Flags().Bool("yes", false, "") + cmd.Flags().Bool("dry-run", false, "") + DeclareLeafMetadata(cmd, LeafSpec{ + Safety: cli.SafetySpec{ + Effect: "destructive", Risk: "high", + Confirmation: "user_required", Idempotency: "unknown", + }, + Schema: contractCoverageSchema("test purge"), + }) + if !HasContractConfirmSafety(cmd) || !HasContractConfirmDeferred(cmd) { + t.Fatal("expected confirm + deferred-confirm annotations") + } + cmd.SetIn(strings.NewReader("")) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + cmd.SetArgs([]string{}) + + err := cmd.Execute() + if err == nil || (!strings.Contains(err.Error(), "confirmation_required") && !strings.Contains(err.Error(), "需要用户确认")) { + t.Fatalf("Execute() without deps/--yes error = %v, want confirmation_required", err) + } + if ran { + t.Fatal("inner RunE must not run before the fallback confirmation") + } + + if err := cmd.Flags().Set("yes", "true"); err != nil { + t.Fatal(err) + } + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() with --yes error = %v", err) + } + if !ran { + t.Fatal("inner RunE must run once --yes bypasses the fallback confirmation") + } +} + +// TestCrossPlatformCoverageDeclareLeafMetadataDeferredConfirmAfterRunEWithoutCallTool +// covers the deferred-confirm post-RunE fallback: with a deps.Caller present +// and an inner RunE that never dispatches through CallTool, the gate stays +// unconfirmed and ConfirmSafety must still run after the inner RunE. +func TestCrossPlatformCoverageDeclareLeafMetadataDeferredConfirmAfterRunEWithoutCallTool(t *testing.T) { + prev := deps + caller := &deferConfirmTestCaller{} + deps = &Deps{Caller: caller, Out: NewFormatter()} + t.Cleanup(func() { deps = prev }) + + ran := 0 + cmd := &cobra.Command{ + Use: "archive", + RunE: func(*cobra.Command, []string) error { + ran++ + return nil // never calls deps.Caller.CallTool + }, + } + cmd.Flags().Bool("yes", false, "") + cmd.Flags().Bool("dry-run", false, "") + DeclareLeafMetadata(cmd, LeafSpec{ + Safety: cli.SafetySpec{ + Effect: "destructive", Risk: "high", + Confirmation: "user_required", Idempotency: "unknown", + }, + Schema: contractCoverageSchema("test archive"), + }) + cmd.SetIn(strings.NewReader("")) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + cmd.SetArgs([]string{}) + + // Without --yes the post-RunE fallback confirmation must fail closed. + err := cmd.Execute() + if err == nil || (!strings.Contains(err.Error(), "confirmation_required") && !strings.Contains(err.Error(), "需要用户确认")) { + t.Fatalf("Execute() without --yes error = %v, want confirmation_required", err) + } + if ran != 1 { + t.Fatalf("inner RunE ran %d times, want 1 (deferred gate confirms after RunE)", ran) + } + if caller.calls != 0 { + t.Fatalf("CallTool calls = %d, want 0", caller.calls) + } + + // With --yes the post-RunE fallback confirmation passes. + if err := cmd.Flags().Set("yes", "true"); err != nil { + t.Fatal(err) + } + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() with --yes error = %v", err) + } + if ran != 2 { + t.Fatalf("inner RunE ran %d times, want 2", ran) + } +} + +// readCapableLeafTestCaller is a ToolCaller that also implements the optional +// ReadToolCaller extension (pre-confirm reads). +type readCapableLeafTestCaller struct { + deferConfirmTestCaller + readCalls int +} + +func (c *readCapableLeafTestCaller) CallReadTool(context.Context, string, string, map[string]any) (*edition.ToolResult, error) { + c.readCalls++ + return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: `{}`}}}, nil +} + +// TestCrossPlatformCoverageContractConfirmReadCallerDelegatesReads covers the ReadToolCaller +// preservation branch of wrapContractConfirmCaller and the ungated +// CallReadTool delegation of contractConfirmReadCaller. +func TestCrossPlatformCoverageContractConfirmReadCallerDelegatesReads(t *testing.T) { + rc := &readCapableLeafTestCaller{} + cmd := &cobra.Command{Use: "read"} + gate := &contractConfirmCaller{ + inner: rc, cmd: cmd, + safety: cli.SafetySpec{ + Effect: "destructive", Risk: "high", + Confirmation: "user_required", Idempotency: "unknown", + }, + } + wrapped := wrapContractConfirmCaller(gate, rc) + readWrapped, ok := wrapped.(*contractConfirmReadCaller) + if !ok { + t.Fatalf("wrapContractConfirmCaller = %T, want *contractConfirmReadCaller", wrapped) + } + res, err := readWrapped.CallReadTool(context.Background(), "product", "read_tool", nil) + if err != nil || res == nil { + t.Fatalf("CallReadTool() = (%v, %v), want ungated success", res, err) + } + if rc.readCalls != 1 { + t.Fatalf("readCalls = %d, want 1", rc.readCalls) + } + if gate.confirmed { + t.Fatal("reads must not flip the confirmation gate") + } + + // A caller without ReadToolCaller keeps the plain gate. + plain := &deferConfirmTestCaller{} + gate2 := &contractConfirmCaller{inner: plain, cmd: cmd} + if wrapped2 := wrapContractConfirmCaller(gate2, plain); wrapped2 != edition.ToolCaller(gate2) { + t.Fatalf("wrapContractConfirmCaller(plain) = %T, want the gate itself", wrapped2) + } +} + +// TestCrossPlatformCoverageContractAnnotationHelpersNilAndMarkedCommands covers the nil-safety of +// the exported contract annotation predicates (incl. HasContractConfirmDeferred). +func TestCrossPlatformCoverageContractAnnotationHelpersNilAndMarkedCommands(t *testing.T) { + if HasContractConfirmSafety(nil) || HasContractValidate(nil) || HasContractConfirmDeferred(nil) { + t.Fatal("nil command must not report contract annotations") + } + bare := &cobra.Command{Use: "bare"} + if HasContractConfirmSafety(bare) || HasContractValidate(bare) || HasContractConfirmDeferred(bare) { + t.Fatal("command without annotations must report false") + } + marked := &cobra.Command{Use: "marked", Annotations: map[string]string{ + contractConfirmSafetyAnnotation: "true", + contractValidateAnnotation: "true", + contractConfirmDeferredAnnotation: "true", + }} + if !HasContractConfirmSafety(marked) || !HasContractValidate(marked) || !HasContractConfirmDeferred(marked) { + t.Fatal("marked command must report all contract annotations") + } +} + +// TestCrossPlatformCoverageDevAppMemberMutationValidateRejectsSeparatorOnlyUserIDs covers the +// member add/remove Validate error branch: a separator-only --user-ids value +// passes the required check but parses to an empty list and must be rejected. +func TestCrossPlatformCoverageDevAppMemberMutationValidateRejectsSeparatorOnlyUserIDs(t *testing.T) { + cases := []struct { + name string + build func(*fakeDevAppRunner) *cobra.Command + }{ + {"add", func(r *fakeDevAppRunner) *cobra.Command { return newDevAppMemberAddCommand(r) }}, + {"remove", func(r *fakeDevAppRunner) *cobra.Command { return newDevAppMemberRemoveCommand(r) }}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + r := &fakeDevAppRunner{} + cmd := c.build(r) + cmd.Flags().Bool("yes", false, "") + if err := cmd.Flags().Set("yes", "true"); err != nil { + t.Fatal(err) + } + _ = cmd.Flags().Set("unified-app-id", "APP-1") + _ = cmd.Flags().Set("user-ids", " , ; ") + _ = cmd.Flags().Set("member-type", "DEVELOPER") + err := cmd.RunE(cmd, nil) + if err == nil || !strings.Contains(err.Error(), "--user-ids 至少包含一个 userId") { + t.Fatalf("err = %v, want --user-ids 至少包含一个 userId", err) + } + if r.got.Tool != "" { + t.Fatalf("dispatched tool = %q, want no dispatch on Validate failure", r.got.Tool) + } + }) + } +} + +// TestCrossPlatformCoverageDevAppListTransformsOmitEmptyLists covers the empty-result branches of +// transformDevAppListParam and transformDevAppScopeValues (nil → key omitted). +func TestCrossPlatformCoverageDevAppListTransformsOmitEmptyLists(t *testing.T) { + for _, raw := range []string{"", " ", ",", " ; , "} { + v, err := transformDevAppListParam(raw) + if err != nil || v != nil { + t.Fatalf("transformDevAppListParam(%q) = (%#v, %v), want (nil, nil)", raw, v, err) + } + v, err = transformDevAppScopeValues(raw) + if err != nil || v != nil { + t.Fatalf("transformDevAppScopeValues(%q) = (%#v, %v), want (nil, nil)", raw, v, err) + } + } + v, err := transformDevAppListParam("a, b") + if err != nil || !reflect.DeepEqual(v, []string{"a", "b"}) { + t.Fatalf("transformDevAppListParam(a, b) = (%#v, %v), want [a b]", v, err) + } + v, err = transformDevAppScopeValues("a,b; c") + if err != nil || !reflect.DeepEqual(v, []string{"a", "b", "c"}) { + t.Fatalf("transformDevAppScopeValues(a,b; c) = (%#v, %v), want [a b c]", v, err) + } +} + +// TestCrossPlatformCoverageDevDocSearchRequiresQueryFlag covers the required-query error branch of +// `dws dev doc search` (no positional keyword and no --query/--keyword). +func TestCrossPlatformCoverageDevDocSearchRequiresQueryFlag(t *testing.T) { + cmd := newDevDocSearchCommand() + err := cmd.RunE(cmd, nil) + if err == nil || !strings.Contains(err.Error(), "query") { + t.Fatalf("RunE() without query error = %v, want required --query error", err) + } +} + +// TestCrossPlatformCoverageSheetMutationGuardRunsContractValidateBeforeTargets covers the outer +// Sheet guard's ContractValidate branch: a DeclareLeafMetadata Validate hook +// must run (and fail) before the --node target check and the --yes-only gate. +func TestCrossPlatformCoverageSheetMutationGuardRunsContractValidateBeforeTargets(t *testing.T) { + ran := false + cmd := &cobra.Command{ + Use: "clear-range", + RunE: func(*cobra.Command, []string) error { + ran = true + return nil + }, + } + cmd.Flags().Bool("yes", false, "") + cmd.Flags().Bool("dry-run", false, "") + cmd.Flags().String("node", "", "") + cmd.Flags().String("range", "", "") + DeclareLeafMetadata(cmd, LeafSpec{ + Safety: cli.SafetySpec{ + Effect: "destructive", Risk: "high", + Confirmation: "user_required", Idempotency: "unknown", + }, + Validate: func(c *cobra.Command, args []string) error { + rng, _ := c.Flags().GetString("range") + if strings.TrimSpace(rng) == "" { + return fmt.Errorf("flag --range is required") + } + return nil + }, + Schema: contractCoverageSchema("test clear range"), + }) + protectSheetMutationCommand(cmd, "清除范围", "文档和目标范围") + if !HasContractValidate(cmd) || !HasSheetMutationConfirmationGuard(cmd) { + t.Fatal("expected contract Validate + sheet mutation guard markers") + } + cmd.SetIn(strings.NewReader("")) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + cmd.SetArgs([]string{}) + + // Validate failure must win over missing --node and the --yes-only prompt. + err := cmd.Execute() + if err == nil || !strings.Contains(err.Error(), "flag --range is required") { + t.Fatalf("Execute() error = %v, want ContractValidate failure first", err) + } + if ran { + t.Fatal("inner RunE must not run when ContractValidate fails") + } + + // Full pass: Validate + --node + --yes reach the inner RunE. + if err := cmd.Flags().Set("range", "A1:B2"); err != nil { + t.Fatal(err) + } + if err := cmd.Flags().Set("node", "node-1"); err != nil { + t.Fatal(err) + } + if err := cmd.Flags().Set("yes", "true"); err != nil { + t.Fatal(err) + } + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() with valid flags error = %v", err) + } + if !ran { + t.Fatal("inner RunE must run after Validate/--node/--yes all pass") + } +} + +// TestCrossPlatformCoverageRequireSheetMutationTargetsSkipsCommandsWithoutNodeFlag covers the skip +// path for nil commands and leaves that do not declare a --node flag. +func TestCrossPlatformCoverageRequireSheetMutationTargetsSkipsCommandsWithoutNodeFlag(t *testing.T) { + if err := requireSheetMutationTargets(nil); err != nil { + t.Fatalf("requireSheetMutationTargets(nil) = %v, want nil", err) + } + cmd := &cobra.Command{Use: "no-node"} + if err := requireSheetMutationTargets(cmd); err != nil { + t.Fatalf("requireSheetMutationTargets(no --node) = %v, want nil", err) + } +} diff --git a/internal/helpers/contact.go b/internal/helpers/contact.go index d0759e89..6e30042b 100644 --- a/internal/helpers/contact.go +++ b/internal/helpers/contact.go @@ -9,6 +9,8 @@ import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" "github.com/spf13/cobra" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" ) // ────────────────────────────────────────────────────────── @@ -954,6 +956,10 @@ contact user profile fields 获取可用字段列表。 }, Examples: []string{"dws contact user profile get --staff-id STAFF_ID --fields fieldCode1,fieldCode2"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "fields", Required: boolPtr(false)}, + {Name: "staff-id", Required: boolPtr(false)}, + }, }, }) contactUserProfileGetCmd.Flags().String("staff-id", "", "查询员工 ID(可选)") @@ -1128,6 +1134,11 @@ contact user profile fields 获取可用字段列表。 AvoidWhen: []string{"需要创建企业专属登录账号时使用 contact account create;需要创建企业组织本身时使用 contact org create"}, Examples: []string{"dws contact user invite --org-user-name \"张三\" --org-user-mobile \"13800138000\" --depts '[{\"deptId\":1}]'"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "depts", Property: "depts", Required: boolPtr(false), InterfaceType: "array"}, + {Name: "org-user-mobile", Property: "orgUserMobile", Required: boolPtr(true)}, + {Name: "org-user-name", Property: "orgUserName", Required: boolPtr(true)}, + }, }, }) contactUserInviteCmd.Flags().String("org-user-name", "", "员工在企业内的名称 (必填)") @@ -1327,6 +1338,10 @@ contact user profile fields 获取可用字段列表。 AvoidWhen: []string{"请求中包含企业账号、专属账号或登录账号时改用 contact account create;邀请员工时改用 contact user invite"}, Examples: []string{"dws contact org create --org-name \"我的企业\" --creator-username \"张三\""}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "creator-username", Property: "creatorUsername", Required: boolPtr(true)}, + {Name: "org-name", Property: "orgName", Required: boolPtr(true)}, + }, }, }) contactOrgCreateCmd.Flags().String("org-name", "", "企业名称 (必填)") @@ -1406,6 +1421,14 @@ contact user profile fields 获取可用字段列表。 AvoidWhen: []string{"创建企业组织本身应使用 contact org create;仅邀请已有手机号员工入企应使用 contact user invite"}, Examples: []string{"dws contact account create --org-user-name \"张三\" --login-id \"zhangsan001\" --org-user-mobile \"13800138000\""}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "dept-ids", Property: "deptIds", Required: boolPtr(false), InterfaceType: "array"}, + {Name: "email", Property: "email", Required: boolPtr(false)}, + {Name: "login-id", Property: "loginId", Required: boolPtr(true)}, + {Name: "org-user-mobile", Property: "orgUserMobile", Required: boolPtr(false)}, + {Name: "org-user-name", Property: "orgUserName", Required: boolPtr(true)}, + {Name: "send-pwd-via-sms", Property: "sendPwdViaSms", Required: boolPtr(false), InterfaceType: "boolean"}, + }, }, }) contactAccountCreateCmd.Flags().String("org-user-name", "", "员工在企业内的名称 (必填)") diff --git a/internal/helpers/doc.go b/internal/helpers/doc.go index a086bb38..687bf1b9 100644 --- a/internal/helpers/doc.go +++ b/internal/helpers/doc.go @@ -16,6 +16,8 @@ import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" "github.com/spf13/cobra" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" ) // ────────────────────────────────────────────────────────── @@ -1094,6 +1096,14 @@ func newDocCommand() *cobra.Command { "dws doc read --node --content-format jsonml --scope outline --max-depth 3", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "content-format", Property: "format", Required: boolPtr(false)}, + {Name: "end-block-id", Required: boolPtr(false)}, + {Name: "max-depth", Required: boolPtr(false), InterfaceType: "integer"}, + {Name: "scope", Required: boolPtr(false)}, + {Name: "start-block-id", Required: boolPtr(false), RequiredWhen: "--scope=range or --scope=section"}, + {Name: "tags", Required: boolPtr(false), RequiredWhen: "--scope=tags"}, + }, }, }) @@ -1968,6 +1978,9 @@ WARNING: --mode overwrite 为破坏性写入,会清空原文档全部内容。 }, Examples: []string{"dws doc rename --node --name \"新名称\" --format json"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "name", Description: "新显示名称;实际执行前读取节点类型与当前扩展名,仅对非文件夹且末尾后缀与当前扩展名一致的名称去掉一层,避免双扩展名"}, + }, }, }) @@ -2476,6 +2489,9 @@ resourceId 需通过 dws doc block list 获取:查询目标文档的块列表 "dws doc comment create --node --content \"请review\" --mention uid1,uid2 --mentioned-open-conversation-id --format json", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "mentioned-open-conversation-id", Required: boolPtr(false), InterfaceType: "array"}, + }, }, }) @@ -2555,6 +2571,9 @@ commentKey可从 dws doc comment create 或 dws doc comment list 返回结果中 "dws doc comment reply --node --comment-key --content \"比心\" --emoji --format json", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "mentioned-open-conversation-id", Required: boolPtr(false), InterfaceType: "array"}, + }, }, }) diff --git a/internal/helpers/hrbrain.go b/internal/helpers/hrbrain.go index 817c4699..af5b06a2 100644 --- a/internal/helpers/hrbrain.go +++ b/internal/helpers/hrbrain.go @@ -6,6 +6,8 @@ import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" "github.com/spf13/cobra" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" ) // ────────────────────────────────────────────────────────── @@ -88,6 +90,14 @@ func newHrbrainCommand() *cobra.Command { "dws hrbrain talent-pool list --keyword \"储备干部\"", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "creator", Property: "creator", Required: boolPtr(false)}, + {Name: "keyword", Property: "keyword", Required: boolPtr(false)}, + {Name: "labels", Property: "labels", Required: boolPtr(false)}, + {Name: "page", Property: "currentPage", Required: boolPtr(false)}, + {Name: "page-size", Property: "pageSize", Required: boolPtr(false)}, + {Name: "pool-type", Property: "poolType", Required: boolPtr(false)}, + }, }, }) talentPoolListCmd.Flags().String("keyword", "", "人才池名称关键词 (可选)") @@ -128,6 +138,9 @@ func newHrbrainCommand() *cobra.Command { AvoidWhen: []string{"尚未取得 poolCode 时先用 dws hrbrain talent-pool list 查找"}, Examples: []string{"dws hrbrain talent-pool detail --pool-code POOL_CODE"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "pool-code", Property: "poolCode", Required: boolPtr(true)}, + }, }, }) talentPoolDetailCmd.Flags().String("pool-code", "", "人才池编码 (必填)") @@ -170,6 +183,11 @@ func newHrbrainCommand() *cobra.Command { }, Examples: []string{"dws hrbrain talent-pool employees --pool-code POOL_CODE --page 1 --page-size 20"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "page", Property: "currentPage", Required: boolPtr(false)}, + {Name: "page-size", Property: "pageSize", Required: boolPtr(false)}, + {Name: "pool-code", Property: "poolCode", Required: boolPtr(true)}, + }, }, }) talentPoolEmployeesCmd.Flags().String("pool-code", "", "人才池编码 (必填)") @@ -213,6 +231,9 @@ func newHrbrainCommand() *cobra.Command { AvoidWhen: []string{"已知模块与字段编码,直接查询档案数据时改用 dws hrbrain profile query"}, Examples: []string{"dws hrbrain profile metadata --work-no WORK_NO"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "work-no", Property: "workNo", Required: boolPtr(true)}, + }, }, }) profileMetadataCmd.Flags().String("work-no", "", "员工工号 (必填)") @@ -262,6 +283,10 @@ func newHrbrainCommand() *cobra.Command { }, Examples: []string{"dws hrbrain profile query --work-no WORK_NO --data-queries '[{\"modelCode\":\"basic\",\"fields\":[\"name\",\"dept\"]}]'"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "data-queries", Property: "dataQueries", Required: boolPtr(true)}, + {Name: "work-no", Property: "workNo", Required: boolPtr(true)}, + }, }, }) profileQueryCmd.Flags().String("work-no", "", "目标员工工号 (必填)") @@ -303,6 +328,10 @@ func newHrbrainCommand() *cobra.Command { AvoidWhen: []string{"要查看职业历程或绩效记录时改用 dws hrbrain profile career / profile performance"}, Examples: []string{"dws hrbrain profile labels --staff-ids WORK_NO1,WORK_NO2"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "all-label", Property: "allLabel", Required: boolPtr(false)}, + {Name: "staff-ids", Property: "staffIds", Required: boolPtr(true)}, + }, }, }) profileLabelsCmd.Flags().String("staff-ids", "", "员工工号列表,逗号分隔 (必填)") @@ -338,6 +367,9 @@ func newHrbrainCommand() *cobra.Command { AvoidWhen: []string{"要查看绩效记录时改用 dws hrbrain profile performance"}, Examples: []string{"dws hrbrain profile career --work-no WORK_NO"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "work-no", Property: "workNo", Required: boolPtr(true)}, + }, }, }) profileCareerCmd.Flags().String("work-no", "", "员工工号 (必填)") @@ -372,6 +404,9 @@ func newHrbrainCommand() *cobra.Command { AvoidWhen: []string{"要查看职业历程时改用 dws hrbrain profile career"}, Examples: []string{"dws hrbrain profile performance --work-no WORK_NO"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "work-no", Property: "workNo", Required: boolPtr(true)}, + }, }, }) profilePerformanceCmd.Flags().String("work-no", "", "员工工号 (必填)") @@ -436,6 +471,15 @@ func newHrbrainCommand() *cobra.Command { "dws hrbrain search employees --dept-name \"技术部\" --job-level P7", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "dept-name", Property: "deptName", Required: boolPtr(false)}, + {Name: "job-level", Property: "jobLevel", Required: boolPtr(false)}, + {Name: "keyword", Property: "keyword", Required: boolPtr(false)}, + {Name: "page", Property: "currentPage", Required: boolPtr(false)}, + {Name: "page-size", Property: "pageSize", Required: boolPtr(false)}, + {Name: "pool-code", Property: "poolCode", Required: boolPtr(false)}, + {Name: "position-name", Property: "positionName", Required: boolPtr(false)}, + }, }, }) employeeSearchCmd.Flags().String("keyword", "", "全文搜索关键词(姓名/工号等)(可选)") @@ -507,6 +551,13 @@ func newHrbrainCommand() *cobra.Command { }, Examples: []string{"dws hrbrain search employees-structured --origin-json '{\"rules\":[{\"field\":\"name\",\"operator\":\"contains\",\"value\":\"张\"}],\"combinator\":\"and\"}' --fields '[{\"label\":\"姓名\",\"value\":\"name\"}]'"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "fields", Property: "fields", Required: boolPtr(true)}, + {Name: "order-by", Property: "orderByClauses", Required: boolPtr(false)}, + {Name: "origin-json", Property: "originJson", Required: boolPtr(true)}, + {Name: "page", Property: "currentPage", Required: boolPtr(false)}, + {Name: "page-size", Property: "pageSize", Required: boolPtr(false)}, + }, }, }) employeeSearchStructuredCmd.Flags().String("origin-json", "", "搜索条件 JSON 表达式 (必填)") diff --git a/internal/helpers/leaf.go b/internal/helpers/leaf.go index 59339fea..d382a00a 100644 --- a/internal/helpers/leaf.go +++ b/internal/helpers/leaf.go @@ -250,9 +250,8 @@ func DeclareLeafMetadata(cmd *cobra.Command, spec LeafSpec) *cobra.Command { if spec.Validate == nil && !confirm { return cmd } - if cmd.Annotations == nil { - cmd.Annotations = map[string]string{} - } + // cmd.Annotations is always non-nil here: AttachSchema above registers the + // runtime-contract annotation on every declared leaf. rt := &contractRuntime{validate: spec.Validate, confirm: confirm} if confirm { rt.safety = spec.Safety diff --git a/internal/helpers/mail.go b/internal/helpers/mail.go index d36a7287..483fcfe7 100644 --- a/internal/helpers/mail.go +++ b/internal/helpers/mail.go @@ -13,6 +13,8 @@ import ( "time" "github.com/spf13/cobra" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" ) // ────────────────────────────────────────────────────────── @@ -2126,6 +2128,10 @@ user 对象字段: AvoidWhen: []string{"列出当前用户自己的邮箱使用 mail mailbox list,搜索邮件内容使用 message search"}, Examples: []string{"dws mail user search --email user@company.com --keyword \"张三\" --limit 20"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "employee-no", Required: boolPtr(false)}, + {Name: "keyword", Required: boolPtr(false)}, + }, }, }) @@ -2376,6 +2382,11 @@ user 对象字段: AvoidWhen: []string{"创建新模板时使用 mail template create"}, Examples: []string{"dws mail template update --email user@company.com --id --subject \"新标题\" --content \"新正文\""}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "content", Required: boolPtr(false)}, + {Name: "name", Required: boolPtr(false)}, + {Name: "subject", Required: boolPtr(false)}, + }, }, }) diff --git a/internal/helpers/report.go b/internal/helpers/report.go index 75db732f..ada6f6f7 100644 --- a/internal/helpers/report.go +++ b/internal/helpers/report.go @@ -14,6 +14,8 @@ import ( "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" "github.com/spf13/cobra" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" ) const ( @@ -253,6 +255,9 @@ func newReportCommand() *cobra.Command { "dws report entry submit --template-id --contents '[{\"key\":\"今日完成\",\"sort\":\"0\",\"content\":\"完成了需求评审\",\"contentType\":\"markdown\",\"type\":\"1\"}]' --format json", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "to-chat", Required: boolPtr(false)}, + }, }, }) addReportCreateFlags(entrySubmitCmd) diff --git a/internal/helpers/todo.go b/internal/helpers/todo.go index 737fb47f..07dee66f 100644 --- a/internal/helpers/todo.go +++ b/internal/helpers/todo.go @@ -15,6 +15,8 @@ import ( apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" "github.com/fatih/color" "github.com/spf13/cobra" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" ) // ────────────────────────────────────────────────────────── @@ -263,6 +265,10 @@ func newTodoCommand() *cobra.Command { "dws todo task list --page 1 --size 20 --status false --format json", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "query-all", Required: boolPtr(false), InterfaceType: "boolean", Description: "为 true 时跨组织查询全部待办;默认仅查询当前组织待办"}, + {Name: "role-types", Property: "roleTypes", Required: boolPtr(false), Description: "角色类型列表;省略时运行时默认使用 executor"}, + }, }, }) @@ -831,6 +837,9 @@ reset-reminder 写入的提醒规则。提醒写命令的成功响应只能作 "dws todo task reset-reminder --task-id --reminder-rules '[{\"dueDateOffset\":-30,\"baseTime\":\"dueTime\"},{\"reminderTimeStamp\":\"2026-03-10T18:00:00+08:00\",\"baseTime\":\"customTime\"}]'", }, }, + Parameters: []corecmd.ParamDecl{ + {Name: "reminder-rules", Description: "提醒规则 JSON 数组;不传表示清除,显式传值必须为对象数组,且每条按 baseTime 提供整数 dueDateOffset 或 ISO8601 reminderTimeStamp"}, + }, }, }) @@ -1263,6 +1272,10 @@ reset-reminder 写入的提醒规则。提醒写命令的成功响应只能作 AvoidWhen: []string{"尚无标签 code 时先用 todo tag list 或 todo tag create;修改标签定义应使用 todo tag update"}, Examples: []string{"dws todo tag add --task-id --tag-codes code1,code2"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "tag-codes", Property: "tagCodes", Required: boolPtr(true), InterfaceType: "array"}, + {Name: "task-id", Property: "taskId", Required: boolPtr(true)}, + }, }, }) @@ -1304,6 +1317,9 @@ reset-reminder 写入的提醒规则。提醒写命令的成功响应只能作 AvoidWhen: []string{"只需从某个待办移除标签或重命名标签时不要删除标签定义;未确认 code 时先用 todo tag list"}, Examples: []string{"dws todo tag delete --tag-codes code1,code2"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "tag-codes", Property: "tagCodes", Required: boolPtr(true), InterfaceType: "array"}, + }, }, }) @@ -1352,6 +1368,9 @@ reset-reminder 写入的提醒规则。提醒写命令的成功响应只能作 AvoidWhen: []string{"给待办打标签应使用 todo tag add;创建没有 code 的新标签应使用 todo tag create"}, Examples: []string{"dws todo tag update --user-tags '[{\"code\":\"code1\",\"name\":\"新名称\"}]'"}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "user-tags", Property: "userTags", Required: boolPtr(true), InterfaceType: "array"}, + }, }, }) @@ -1419,6 +1438,9 @@ reset-reminder 写入的提醒规则。提醒写命令的成功响应只能作 AvoidWhen: []string{"给已有待办打上现有标签应使用 todo tag add;重命名已有标签应使用 todo tag update"}, Examples: []string{"dws todo tag create --name \"项目标签\""}, }, + Parameters: []corecmd.ParamDecl{ + {Name: "name", Property: "name", Required: boolPtr(true)}, + }, }, }) diff --git a/internal/shortcut/adapter.go b/internal/shortcut/adapter.go index 49f7f09a..21fc4d46 100644 --- a/internal/shortcut/adapter.go +++ b/internal/shortcut/adapter.go @@ -138,6 +138,7 @@ func fromShortcutFlags(flags []Flag) []corecmd.FlagSpec { Default: f.Default, Hidden: f.Hidden, Required: f.Required, + RequiredWhen: f.RequiredWhen, ValidationMode: corecmd.ValidationShortcut, RequiredError: fmt.Sprintf("缺少必填参数 --%s:%s", f.Name, f.Desc), Enum: append([]string(nil), f.Enum...), diff --git a/internal/shortcut/builtin/loadcost_bench_test.go b/internal/shortcut/builtin/loadcost_bench_test.go new file mode 100644 index 00000000..70f9ff7d --- /dev/null +++ b/internal/shortcut/builtin/loadcost_bench_test.go @@ -0,0 +1,24 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package builtin_test + +import ( + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/builtin" +) + +// BenchmarkShortcutCommands measures the Go-literal storage model: the 376 +// shortcut definitions are compiled-in struct literals registered from init(), +// so there is no parse step at all — this times only turning them into the Cobra +// tree. It is the comparison point for the embedded JSON catalog, whose first +// ResolveMeta hit spends ~1.4s and ~740MB decoding. +func BenchmarkShortcutCommands(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if len(builtin.Commands()) == 0 { + b.Fatal("no shortcut commands") + } + } +} diff --git a/internal/shortcut/chat/unified_send.go b/internal/shortcut/chat/unified_send.go index ab266d26..32883a9c 100644 --- a/internal/shortcut/chat/unified_send.go +++ b/internal/shortcut/chat/unified_send.go @@ -61,8 +61,8 @@ var MessagesSend = shortcut.Shortcut{ {Name: "open-dingtalk-id", Type: shortcut.FlagString, Desc: "单聊接收者 openDingTalkId(user);受发送身份能力矩阵约束"}, {Name: "users", Type: shortcut.FlagStringSlice, Desc: "批量单聊接收者 userId(bot);受发送身份能力矩阵约束"}, {Name: "open-dingtalk-ids", Type: shortcut.FlagStringSlice, Desc: "批量单聊接收者 openDingTalkId(bot);受发送身份能力矩阵约束"}, - {Name: "robot-code", Type: shortcut.FlagString, Desc: "机器人 Code(identity=bot 时使用);受发送身份能力矩阵约束"}, - {Name: "webhook-token", Type: shortcut.FlagString, Desc: "自定义机器人 Webhook token(identity=webhook 时使用);受发送身份能力矩阵约束"}, + {Name: "robot-code", Type: shortcut.FlagString, RequiredWhen: "identity=bot", Desc: "机器人 Code(identity=bot 时使用);受发送身份能力矩阵约束"}, + {Name: "webhook-token", Type: shortcut.FlagString, RequiredWhen: "identity=webhook", Desc: "自定义机器人 Webhook token(identity=webhook 时使用);受发送身份能力矩阵约束"}, {Name: "title", Type: shortcut.FlagString, Desc: "消息标题(不传则从正文生成)"}, {Name: "text", Type: shortcut.FlagString, Desc: "纯文本正文"}, {Name: "markdown", Type: shortcut.FlagString, Desc: "Markdown 正文"}, diff --git a/internal/shortcut/types.go b/internal/shortcut/types.go index 090ac369..20906fdb 100644 --- a/internal/shortcut/types.go +++ b/internal/shortcut/types.go @@ -89,6 +89,11 @@ type Flag struct { Desc string `json:"description"` // Required, when true, makes the framework error if the flag is not set. Required bool `json:"required"` + // RequiredWhen publishes a conditional-required rule that Required cannot + // express, e.g. "identity=bot" for a credential only mandatory under one + // identity. It is published Schema metadata so an Agent can predict the + // failure; enforcement stays in Validate, which remains authoritative. + RequiredWhen string `json:"required_when,omitempty"` // Enum, when non-empty, restricts the accepted values (string flags only). Enum []string `json:"enum"` // Hidden hides the flag from --help while keeping it usable. diff --git a/scripts/dev/catalog_codegen_probe.py b/scripts/dev/catalog_codegen_probe.py new file mode 100644 index 00000000..a649252f --- /dev/null +++ b/scripts/dev/catalog_codegen_probe.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""One-shot feasibility probe: render one product's catalog shard as Go literals. + +Answers whether the shortcut storage model (compiled-in struct literals, zero +runtime parse) is viable for the 845-tool Schema catalog, by measuring compile +time, binary size and access cost on a single product first. + +Deliberately drops field_provenance: it is 50% of the bytes and no runtime +consumer reads it, so a codegen design would keep it in JSON for audit only. +Keeping it here would make the size numbers pessimistic and the design wrong. +""" +import json +import sys + + +def q(s: str) -> str: + return json.dumps(s if s is not None else "", ensure_ascii=False) + + +def strs(v) -> str: + if not v: + return "nil" + return "[]string{" + ", ".join(q(x) for x in v) + "}" + + +def render_param(name, p) -> str: + fields = [ + f"Name: {q(p.get('name') or name)}", + f"Type: {q(p.get('type'))}", + f"Description: {q(p.get('description'))}", + f"Property: {q(p.get('property'))}", + f"Required: {str(bool(p.get('required'))).lower()}", + f"CLIRequired: {str(bool(p.get('cli_required'))).lower()}", + f"RequiredWhen: {q(p.get('required_when'))}", + f"Format: {q(p.get('format'))}", + f"Enum: {strs(p.get('enum'))}", + ] + return "{" + ", ".join(fields) + "}" + + +def render_tool(canonical, t) -> str: + params = t.get("parameters") or {} + lines = [ + f"\t{q(canonical)}: {{", + f"\t\tName: {q(t.get('name'))},", + f"\t\tCanonicalPath: {q(t.get('canonical_path'))},", + f"\t\tCLIPath: {q(t.get('cli_path'))},", + f"\t\tPrimaryCLIPath: {q(t.get('primary_cli_path'))},", + f"\t\tProductID: {q(t.get('product_id'))},", + f"\t\tGroup: {q(t.get('group'))},", + f"\t\tTitle: {q(t.get('title'))},", + f"\t\tDescription: {q(t.get('description'))},", + f"\t\tAliases: {strs(t.get('aliases'))},", + f"\t\tIsAlias: {str(bool(t.get('is_alias'))).lower()},", + f"\t\tEffect: {q(t.get('effect'))},", + f"\t\tRisk: {q(t.get('risk'))},", + f"\t\tConfirmation: {q(t.get('confirmation'))},", + f"\t\tIdempotency: {q(t.get('idempotency'))},", + f"\t\tInterfaceMode: {q(t.get('interface_mode'))},", + f"\t\tAvailability: {q(t.get('availability'))},", + f"\t\tAgentSummary: {q(t.get('agent_summary'))},", + f"\t\tUseWhen: {strs(t.get('use_when'))},", + f"\t\tAvoidWhen: {strs(t.get('avoid_when'))},", + f"\t\tExamples: {strs(t.get('examples'))},", + f"\t\tHasParameters: {str(bool(t.get('has_parameters'))).lower()},", + f"\t\tParameterCount: {int(t.get('parameter_count') or 0)},", + ] + if params: + lines.append("\t\tParameters: map[string]Param{") + for pname in sorted(params): + lines.append(f"\t\t\t{q(pname)}: {render_param(pname, params[pname])},") + lines.append("\t\t},") + lines.append("\t},") + return "\n".join(lines) + + +def main() -> int: + src, product, out = sys.argv[1], sys.argv[2], sys.argv[3] + tools = json.load(open(src, encoding="utf-8"))["tools"] + body = "\n".join(render_tool(c, tools[c]) for c in sorted(tools)) + with open(out, "w", encoding="utf-8") as f: + f.write(f"""// Copyright 2026 Alibaba Group +// Code generated by scripts/dev/catalog_codegen_probe.py; DO NOT EDIT. +// +// Feasibility probe for storing the Schema catalog as compiled-in Go literals +// instead of embedded JSON, mirroring how internal/shortcut stores its 376 +// definitions. field_provenance is omitted: no runtime consumer reads it. + +package catalogcodegen + +// Tools holds the {product} product's leaf ToolSpecs as static data, so there is +// no decode step on the first lookup. +var Tools = map[string]Tool{{ +{body} +}} +""") + print(f"generated {out}: {len(tools)} tools") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/dev/gen_param_decls.py b/scripts/dev/gen_param_decls.py new file mode 100644 index 00000000..0d42bcff --- /dev/null +++ b/scripts/dev/gen_param_decls.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""Generate ParamDecl Go snippets from schema_hints/metadata overlays. + +For each product with parameter overlays, emits the Go code that should be +inserted into the corresponding DeclareLeafMetadata Schema block. Also reports +whether the overlaid flags are registered before or after the DeclareLeafMetadata +call, so the operator knows which need reordering. + +Usage: python3 scripts/dev/gen_param_decls.py +""" +import json +import glob +import os +import re +import sys + +HELPER_DIR = "internal/helpers" + +# Map product hint filename to helper Go file(s) +PRODUCT_FILES = { + "aisearch": ["aisearch.go"], + "aitable": ["aitable.go"], + "attendance": ["attendance.go"], + "chat": ["chat.go"], + "contact": ["contact.go"], + "devdoc": ["devdoc.go"], + "doc": ["doc.go"], + "drive": ["drive.go"], + "hrbrain": ["hrbrain.go"], + "mail": ["mail.go"], + "markdown": ["markdown.go"], + "minutes": ["minutes.go"], + "report": ["report.go"], + "sheet": ["sheet.go"], + "todo": ["todo.go"], +} + + +def bool_ptr(v: bool) -> str: + return "boolPtr(true)" if v else "boolPtr(false)" + + +def render_param_decl(name: str, overlay: dict) -> str: + parts = [f'Name: "{name}"'] + if "property" in overlay: + parts.append(f'Property: "{overlay["property"]}"') + if "required" in overlay: + parts.append(f"Required: {bool_ptr(overlay['required'])}") + if "interface_type" in overlay: + parts.append(f'InterfaceType: "{overlay["interface_type"]}"') + if "description" in overlay: + desc = overlay["description"].replace('"', '\\"') + parts.append(f'Description: "{desc}"') + if "required_when" in overlay: + parts.append(f'RequiredWhen: "{overlay["required_when"]}"') + if "enum" in overlay and overlay["enum"]: + vals = ", ".join(f'"{v}"' for v in overlay["enum"]) + parts.append(f"Enum: []string{{{vals}}}") + return "{" + ", ".join(parts) + "}" + + +def find_declare_line(src: str, tool_name: str) -> int: + """Find the line number of DeclareLeafMetadata for a tool whose RPCName matches.""" + # Try to find by RPCName in the Schema block + pattern = re.compile( + r'DeclareLeafMetadata\(\w+,\s*LeafSpec\{.*?RPCName:\s*"' + re.escape(tool_name) + '"', + re.S, + ) + m = pattern.search(src) + if m: + return src[: m.start()].count("\n") + 1 + return -1 + + +def find_flag_reg_line(src: str, flag_name: str) -> int: + """Find the first line where a flag is registered.""" + pattern = re.compile(r'\.Flags\(\)\.\w+\("' + re.escape(flag_name) + '"') + m = pattern.search(src) + if m: + return src[: m.start()].count("\n") + 1 + return -1 + + +def main(): + for hint_file in sorted(glob.glob("internal/cli/schema_hints/metadata/*.json")): + product = os.path.basename(hint_file).replace(".json", "") + d = json.load(open(hint_file, encoding="utf-8")) + tools = d.get("tools") or {} + overlays = {t: (v or {}).get("parameters") or {} for t, v in tools.items()} + overlays = {t: p for t, p in overlays.items() if p} + if not overlays: + continue + + helper_files = PRODUCT_FILES.get(product, []) + src = "" + for hf in helper_files: + path = os.path.join(HELPER_DIR, hf) + if os.path.exists(path): + src += open(path, encoding="utf-8").read() + "\n" + + print(f"\n// === {product} ({len(overlays)} tools) ===") + for tool_name, params in sorted(overlays.items()): + decl_line = find_declare_line(src, tool_name) + print(f"\n// tool: {tool_name} (DeclareLeafMetadata at line {decl_line})") + print(f"Parameters: []corecmd.ParamDecl{{") + for pname in sorted(params): + overlay = params[pname] + reg_line = find_flag_reg_line(src, pname) + order = "" + if decl_line > 0 and reg_line > 0: + if reg_line > decl_line: + order = f" // ⚠ flag registered AFTER DeclareLeafMetadata (line {reg_line} > {decl_line}) — MOVE BEFORE" + else: + order = f" // ✓ flag registered before (line {reg_line})" + print(f"\t{render_param_decl(pname, overlay)},{order}") + print("},") + + +if __name__ == "__main__": + main() diff --git a/scripts/dev/migrate_product_params.py b/scripts/dev/migrate_product_params.py new file mode 100644 index 00000000..d5087f47 --- /dev/null +++ b/scripts/dev/migrate_product_params.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +"""Migrate one product's parameter overlays from hints into ParamDecl declarations. + +Usage: python3 scripts/dev/migrate_product_params.py + +For each tool with parameter overlays in schema_hints/metadata/.json: +1. Finds the DeclareLeafMetadata block containing the tool's RPCName. +2. Inserts Parameters: []corecmd.ParamDecl{...} into the Schema block. +3. Moves any overlaid flag registrations before the DeclareLeafMetadata call. +4. Clears the tool's parameters from the hints file. + +Run for one product at a time; verify compilation and schema output between runs. +""" +import json +import os +import re +import sys + +HELPER_DIR = "internal/helpers" +HINTS_DIR = "internal/cli/schema_hints/metadata" + +# Primary file per product (used for hints clearing only; RPCName search covers all files) +PRODUCT_FILES = { + "aisearch": "aisearch.go", "aitable": "aitable.go", "attendance": "attendance.go", + "chat": "chat.go", "contact": "contact.go", "devdoc": "devdoc.go", + "doc": "doc.go", "drive": "drive.go", "hrbrain": "hrbrain.go", + "mail": "mail.go", "markdown": "markdown.go", "minutes": "minutes.go", + "report": "report.go", "sheet": "sheet.go", "todo": "todo.go", +} + + +def all_helper_files(): + """Return all non-test .go files in internal/helpers/.""" + files = [] + for f in sorted(os.listdir(HELPER_DIR)): + if f.endswith(".go") and not f.endswith("_test.go"): + files.append(os.path.join(HELPER_DIR, f)) + return files + + +def bool_ptr(v): + return "boolPtr(true)" if v else "boolPtr(false)" + + +def render_param(name, ov): + parts = [f'Name: "{name}"'] + if "property" in ov: + parts.append(f'Property: "{ov["property"]}"') + if "required" in ov: + parts.append(f"Required: {bool_ptr(ov['required'])}") + if "interface_type" in ov: + parts.append(f'InterfaceType: "{ov["interface_type"]}"') + if "description" in ov: + parts.append(f'Description: "{ov["description"]}"') + if "required_when" in ov: + parts.append(f'RequiredWhen: "{ov["required_when"]}"') + if "enum" in ov and ov["enum"]: + vals = ", ".join(f'"{v}"' for v in ov["enum"]) + parts.append(f"Enum: []string{{{vals}}}") + return "\t\t\t\t{" + ", ".join(parts) + "}," + + +def find_block_end(lines, start_idx): + """Find the line index of the closing '})' or '},' for a block starting at start_idx.""" + depth = 0 + for i in range(start_idx, len(lines)): + depth += lines[i].count("{") - lines[i].count("}") + if depth <= 0 and i > start_idx: + return i + return -1 + + +def migrate_product(product): + hint_path = os.path.join(HINTS_DIR, f"{product}.json") + hints = json.load(open(hint_path, encoding="utf-8")) + tools = hints.get("tools") or {} + overlays = {t: (v or {}).get("parameters") or {} for t, v in tools.items()} + overlays = {t: p for t, p in overlays.items() if p} + if not overlays: + print(f"{product}: no overlays, skipping") + return True + + # Load all helper files into memory + file_contents = {} + for fp in all_helper_files(): + file_contents[fp] = open(fp, encoding="utf-8").read() + + modified_files = set() + flags_to_move = [] + migrated_tools = set() # only clear hints for tools we actually migrated + + for tool_name, params in sorted(overlays.items()): + rpc_name = tool_name.split(".")[-1] + + # Strategy 1: RPCName field in Schema.Interface + rpc_pattern = re.compile(r'RPCName:\s*"' + re.escape(rpc_name) + '"') + # Strategy 2: callMCPTool / CallMCP call sites + call_patterns = [ + re.compile(r'callMCPTool\("' + re.escape(rpc_name) + '"'), + re.compile(r'callMCPToolOnServer\([^,]+,\s*"' + re.escape(rpc_name) + '"'), + re.compile(r'rt\.CallMCP\w*\("' + re.escape(rpc_name) + '"'), + ] + + found_file = None + found_line = -1 + for fp, src in file_contents.items(): + lines_src = src.split("\n") + # Try strategy 1 first + for i, line in enumerate(lines_src): + if rpc_pattern.search(line): + found_file = fp + found_line = i + break + if found_file: + break + # Try strategy 2 + for i, line in enumerate(lines_src): + for cp in call_patterns: + if cp.search(line): + found_file = fp + found_line = i + break + if found_file: + break + if found_file: + break + + if not found_file: + print(f" WARNING: {tool_name} — RPCName '{rpc_name}' not found in any helper file") + continue + + lines = file_contents[found_file].split("\n") + + # Find DeclareLeafMetadata: search backwards first, then forwards + decl_start = -1 + search_range = list(range(found_line, max(found_line - 60, -1), -1)) + \ + list(range(found_line + 1, min(found_line + 60, len(lines)))) + for i in search_range: + if "DeclareLeafMetadata(" in lines[i]: + # Verify this DeclareLeafMetadata's Schema contains the target + # RPCName (or the tool name) to avoid matching the wrong command. + block_end = find_block_end(lines, i) + block_text = "\n".join(lines[i:block_end + 1]) + if rpc_name in block_text or tool_name in block_text: + decl_start = i + break + # Also check for callMCPTool("rpc_name") inside the block + if f'callMCPTool("{rpc_name}"' in block_text or \ + f'callMCPToolOnServer(' in block_text and rpc_name in block_text: + decl_start = i + break + # Skip this DeclareLeafMetadata — it doesn't belong to our tool + if decl_start < 0: + print(f" WARNING: {tool_name} — no matching DeclareLeafMetadata near line {found_line + 1} in {os.path.basename(found_file)}") + continue + + # Find Schema block + schema_start = -1 + for i in range(decl_start, min(decl_start + 80, len(lines))): + if "Schema: LeafSchema{" in lines[i] or "Schema: corecmd.SchemaDecl{" in lines[i]: + schema_start = i + break + if schema_start < 0: + print(f" WARNING: {tool_name} — no Schema block in {os.path.basename(found_file)}") + continue + + schema_end = find_block_end(lines, schema_start) + schema_text = "\n".join(lines[schema_start:schema_end + 1]) + if "Parameters:" in schema_text: + print(f" SKIP: {tool_name} — Parameters already declared") + migrated_tools.add(tool_name) + continue + + # Build and insert Parameters + param_lines = ["\t\t\tParameters: []corecmd.ParamDecl{"] + for pname in sorted(params): + param_lines.append(render_param(pname, params[pname])) + param_lines.append("\t\t\t},") + + insert_at = schema_end + for i in range(schema_end - 1, schema_start, -1): + stripped = lines[i].strip() + if stripped and not stripped.startswith("//"): + insert_at = i + 1 + break + + for j, pl in enumerate(param_lines): + lines.insert(insert_at + j, pl) + modified_files.add(found_file) + file_contents[found_file] = "\n".join(lines) + migrated_tools.add(tool_name) + print(f" OK: {tool_name} — inserted {len(params)} param decls in {os.path.basename(found_file)} at line {insert_at + 1}") + + # Check flag registration order + for pname in params: + flag_pattern = re.compile(r'\.Flags\(\)\.\w+\("' + re.escape(pname) + '"') + for i, line in enumerate(lines): + if flag_pattern.search(line): + if i > decl_start: + flags_to_move.append((pname, i, decl_start, os.path.basename(found_file))) + break + + # Clear hints for migrated/skipped tools (even if no source files changed) + if migrated_tools: + for tool_name in migrated_tools: + if tool_name in hints.get("tools", {}): + hints["tools"][tool_name].pop("parameters", None) + if not hints["tools"][tool_name]: + del hints["tools"][tool_name] + if not hints.get("tools"): + hints["tools"] = {} + json.dump(hints, open(hint_path, "w", encoding="utf-8"), ensure_ascii=False, indent=2) + print(f"{product}: cleared hints for {len(migrated_tools)} tools ({len(overlays) - len(migrated_tools)} remaining)") + + if not modified_files: + if not migrated_tools: + print(f"{product}: no changes needed") + return True + + # Write modified files and ensure corecmd import + for fp in modified_files: + src = file_contents[fp] + if "internal/corecmd" not in src: + m = re.search(r'(import \(\n(?:.*\n)*?\))', src) + if m: + block = m.group(1) + new_block = block.rstrip(")\n") + '\n\n\t"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd"\n)' + src = src[:m.start()] + new_block + src[m.end():] + print(f" added corecmd import to {os.path.basename(fp)}") + open(fp, "w", encoding="utf-8").write(src) + + if flags_to_move: + print(f" ⚠ {len(flags_to_move)} flags registered AFTER DeclareLeafMetadata — need manual reorder:") + for fname, reg_line, decl_line, fname_file in flags_to_move: + print(f" --{fname} in {fname_file}: reg line {reg_line + 1} > decl line {decl_line + 1}") + + return True + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: migrate_product_params.py ") + sys.exit(1) + ok = migrate_product(sys.argv[1]) + sys.exit(0 if ok else 1)