diff --git a/AGENTS.md b/AGENTS.md index 9133c525..cf4e16ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,13 +60,13 @@ The Schema data flow is one way: └─ produces ParameterSpec and constraints 4. Agent and interface semantics - schema_hints/selection/.json (selection prose) - + ContractFinal Safety / Interface on each leaf + ProductDecl + leaf ContractFinal Selection / Safety / Interface + pinned MCP metadata └─ resolves Agent metadata by source precedence Markdown is evidence only; it is not concatenated into final prose - └─ schema_hints/metadata/ is retired (directory may be absent; - index.metadata may be {}/omitted); not a parallel leaf-fact source + └─ schema_hints/selection/ and schema_hints/metadata/ are retired + (directories may be absent; index.selection / index.metadata may be + {}/omitted); not parallel leaf-fact sources 5. One typed hub BoundCommandRegistry @@ -119,9 +119,10 @@ The Schema system has two physically separated processes: (`agent_metadata_inject.go` / `InstallBuildTimeAgentMetadataJSON`); it does not write `schema_agent_metadata/`. `cmd_schema_agent_metadata` may remain as a non-delivery helper/test binary and is not a `go:generate` entry. -- Inputs: authored source groups (registry + required hints selection + MCP +- Inputs: authored source groups (registry + ProductDecl/ContractFinal + MCP metadata + parameter bindings + reviewed parameter concepts + cobra tree). - `schema_hints/metadata/` is retired and may be absent. + `schema_hints/selection/` and `schema_hints/metadata/` are retired and may + be absent. - Output (delivery): `schema_catalog/` (per-product shards, `go:embed`) + `param_aliases_generated.go`. `schema_agent_metadata/` must not reappear. - Refresh MCP metadata: `make fetch-mcp-metadata` (iterates 26 MCP server @@ -167,15 +168,14 @@ When adding or changing an Agent-visible command, review all relevant inputs: unknown fields, invalid visibility values, stale paths, and collisions fail Go validation and policy. - Leaf `Safety` / `Schema` / `ParamDecl` (helpers `LeafSpec` or shortcut - `Schema`) for parameter facts, interface disposition, and safety. Delivered - provenance is `contract_final` from `corecmd.contract`. -- `internal/cli/schema_hints/selection/.json` for reviewed Agent - selection prose (`agent_summary`, `use_when`, `avoid_when`, `examples`). -- `internal/cli/schema_hints/metadata/` is retired (directory may be absent). - Do not reintroduce reviewed tool rows or parameter overlays; declare on the - leaf instead. -- `internal/cli/schema_hints/index.json` maps product IDs to selection files; - `metadata` may be omitted or `{}`. + `Schema`) for parameter facts, interface disposition, safety, and Agent + selection prose. Delivered provenance is `contract_final` from + `corecmd.contract`. Product routing uses `ProductDecl` (`cli.product_decl`). +- `internal/cli/schema_hints/selection/` and `schema_hints/metadata/` are + retired (directories may be absent). Do not reintroduce selection HintFiles + or metadata tool rows; declare on ProductDecl / the owning leaf instead. +- `internal/cli/schema_hints/index.json` may keep `selection` / `metadata` as + `{}` or omit them; they are not leaf-fact sources. - Native Runtime Schema identity annotations, when present, as consistency assertions against `EffectiveCommandRegistry`. They must agree exactly and must never materialize, infer, or override registry identity. @@ -196,12 +196,11 @@ that exercise precedence edges. For Agent-authored selection edits: 1. Confirm the exact command and flag names in the current Cobra tree. -2. Edit `selection/.json` for selection prose; declare safety / - parameters / interface on the owning leaf (`LeafSpec` / `Shortcut.Schema`). -3. Add the smallest possible entry; do not copy generated Catalog fields into - the input. -4. Describe user-visible semantics in `review_reason`. -5. Run generation, drift, Schema policy, and the focused CLI tests before +2. Declare selection prose on the owning leaf (`Schema.Selection` / + `DeclareLeafMetadata`) and product routing via `ProductDecl`; declare + safety / parameters / interface on the same leaf. +3. Do not copy generated Catalog fields into source inputs. +4. Run generation, drift, Schema policy, and the focused CLI tests before proposing the change. ## Agent curation workflow (Schema hints) @@ -214,19 +213,19 @@ Human-authored inputs are split as follows: | Block | Path | Owns | |---|---|---| -| **declaration** | helpers / shortcut `Safety` + `Schema` / ParamDecl | `effect` / `risk` / `confirmation` / `idempotency` / `interface_*` / parameter facts (`contract_final`) | -| **selection** | `internal/cli/schema_hints/selection/.json` | `agent_summary` / `use_when` / `avoid_when` / `examples` (+ product routing) | +| **declaration** | helpers / shortcut `Safety` + `Schema` / ParamDecl + `ProductDecl` | `effect` / `risk` / `confirmation` / `idempotency` / `interface_*` / parameter facts / selection prose (`contract_final`) | +| **selection** (retired) | `internal/cli/schema_hints/selection/` may be absent | not a leaf-fact source; do not reintroduce HintFiles | | **metadata** (retired) | `internal/cli/schema_hints/metadata/` may be absent | not a leaf-fact source; do not reintroduce tool rows | -`index.json` maps product IDs to selection files; `metadata` is optional -(`{}` / omitted). Do not reintroduce metadata tool rows. +`index.json` may omit `selection` / `metadata` or set them to `{}`. Do not +reintroduce selection or metadata tool rows. ### Goals 1. **Selection prose** is decision-oriented (Feishu/Lark style): trigger intent, sibling-command routing, and outcome shape — not a restatement of the - summary. Delivered Catalog provenance is `reviewed_explicit` from - `selection/`. + summary. Delivered Catalog provenance is `contract_final` from leaf + `Schema.Selection` / `ProductDecl`. 2. **Safety** follows Runtime: `confirmation=user_required` when the leaf Contract/Safety (or remaining `runtime_gate` annotate) requires a user gate (for example `confirm_delete`, `typed_yes`, `confirm_dangerous`). @@ -238,11 +237,10 @@ Human-authored inputs are split as follows: For every curated tool: -1. Declare safety/interface/parameters on the owning leaf - (`DeclareLeafMetadata` / `Shortcut.Schema` / ParamDecl). -2. Edit `selection/.json` for selection prose (`reviewed: true`, - `review_reason`, `source_refs`). -3. Run `make generate-schema`. Do not hand-edit generated `schema_catalog/`. +1. Declare safety/interface/parameters/selection on the owning leaf + (`DeclareLeafMetadata` / `Shortcut.Schema` / ParamDecl) and product routing + via `ProductDecl` when needed. +2. Run `make generate-schema`. Do not hand-edit generated `schema_catalog/`. ### Pull live MCP descriptions (personal token) @@ -270,9 +268,9 @@ Split work by product groups. Each agent must: - Read Skill, Cobra/`--help`, Runtime confirmation sites, and live `dws schema` for its tools. -- Hand-write selection prose and leaf Contract declarations; forbid wholesale - JSON merges from review dumps. -- Edit only its product’s leaf declarations and `selection/.json`. +- Hand-write selection prose and leaf Contract / ProductDecl declarations; + forbid wholesale JSON merges from review dumps. +- Edit only its product’s leaf declarations (and `ProductDecl` when needed). - **Never** `git checkout` unrelated product files to “clean scope”. ### Regenerate and gates @@ -295,16 +293,15 @@ Example rules (fail generation otherwise): - Examples must match live Cobra argv (path, flags, required groups). - No shell comments in examples. -After generation, spot-check Catalog: selection provenance is -`reviewed_explicit` from `selection/`, and safety/interface provenance is -`contract_final` from leaf declarations (`user_required` must match Runtime -confirmation gates). +After generation, spot-check Catalog: selection and safety/interface +provenance are `contract_final` from ProductDecl / leaf declarations +(`user_required` must match Runtime confirmation gates). `make generate-schema` is a full deterministic snapshot rebuild, not an incremental patch over the previous Catalog. It rereads every reviewed input, removes stale generated product metadata, and rewrites the exact metadata and Catalog projections. Incremental work happens only when an Agent or human -edits selected leaf declarations or `selection/` entries; the next publication +edits selected leaf / ProductDecl declarations; the next publication still recomputes all outputs. Generated files must never be read back as merge input, and byte guards fail generation if it changes the hint inputs or CommandRegistry. diff --git a/Makefile b/Makefile index 2560942a..aec251d7 100644 --- a/Makefile +++ b/Makefile @@ -142,18 +142,22 @@ generate-schema: registry_guard=$$(mktemp -d); \ concepts_guard=$$(mktemp); \ concepts_schema_guard=$$(mktemp); \ - selection_guard=$$(mktemp -d); \ metadata_dir=internal/cli/schema_hints/metadata; \ metadata_guard=""; \ if [ -d "$$metadata_dir" ]; then \ metadata_guard=$$(mktemp -d); \ cp -R "$$metadata_dir"/. "$$metadata_guard"/; \ fi; \ + selection_dir=internal/cli/schema_hints/selection; \ + selection_guard=""; \ + if [ -d "$$selection_dir" ]; then \ + selection_guard=$$(mktemp -d); \ + cp -R "$$selection_dir"/. "$$selection_guard"/; \ + fi; \ trap 'rm -rf "$$registry_guard" "$$concepts_guard" "$$concepts_schema_guard" "$$metadata_guard" "$$selection_guard"' EXIT HUP INT TERM; \ cp -R internal/cli/schema_command_registry/ "$$registry_guard/"; \ cp internal/cli/param_concepts.json "$$concepts_guard"; \ cp internal/cli/param_concepts.schema.json "$$concepts_schema_guard"; \ - cp -R internal/cli/schema_hints/selection/. "$$selection_guard/"; \ $(GO) generate ./internal/cli; \ rm -rf internal/cli/schema_agent_metadata internal/cli/schema_agent_metadata_audit.json; \ diff -qr internal/cli/schema_command_registry "$$registry_guard" >/dev/null || { \ @@ -181,10 +185,19 @@ generate-schema: printf '%s\n' 'generation created reviewed input internal/cli/schema_hints/metadata' >&2; \ exit 1; \ fi; \ - diff -qr internal/cli/schema_hints/selection "$$selection_guard" >/dev/null || { \ - printf '%s\n' 'generation modified reviewed input internal/cli/schema_hints/selection' >&2; \ + if [ -n "$$selection_guard" ]; then \ + if [ ! -d "$$selection_dir" ]; then \ + printf '%s\n' 'generation removed reviewed input internal/cli/schema_hints/selection' >&2; \ + exit 1; \ + fi; \ + diff -qr "$$selection_dir" "$$selection_guard" >/dev/null || { \ + printf '%s\n' 'generation modified reviewed input internal/cli/schema_hints/selection' >&2; \ + exit 1; \ + }; \ + elif [ -d "$$selection_dir" ]; then \ + printf '%s\n' 'generation created reviewed input internal/cli/schema_hints/selection' >&2; \ exit 1; \ - } + fi # Catalog-only publication path (same as go:generate catalog step). Does not # write schema_agent_metadata/. diff --git a/README.md b/README.md index 32fb6b8c..b5245c03 100644 --- a/README.md +++ b/README.md @@ -396,7 +396,7 @@ The repo ships a complete Agent Skill system under `skills/`, organized into two - `skills/mono/` — single-skill layout (one `SKILL.md` + `references/products/`), recommended default. - `skills/multi/` — per-product skills (`dingtalk-aitable/`, `dingtalk-calendar/`, `dingtalk-chat/`, ...), each with its own `SKILL.md`. 🧪 **EXPERIMENTAL / preview — see banner in each multi `SKILL.md` for caveats.** -Shared Schema generation inputs live under `internal/cli/schema_hints/` (required reviewed `selection/` prose; `metadata/` is retired and may be absent; leaf safety/parameters come from Contract). They are not Agent Skills and are excluded from release skill bundles. +Shared Schema generation inputs live under `internal/cli/schema_hints/` (`selection/` and `metadata/` are retired and may be absent; leaf safety/parameters/selection prose come from ProductDecl / Contract). They are not Agent Skills and are excluded from release skill bundles. After installing, AI tools like Claude Code / Cursor can operate DingTalk directly through natural language: diff --git a/README_zh.md b/README_zh.md index 23f38619..6982ce6e 100644 --- a/README_zh.md +++ b/README_zh.md @@ -390,7 +390,7 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10 - `skills/mono/` — 单 skill 布局(一个 `SKILL.md` + `references/products/`),默认推荐。 - `skills/multi/` — 每个产品一个独立 skill(`dingtalk-aitable/` / `dingtalk-calendar/` / `dingtalk-chat/` ...),每个 skill 自带 `SKILL.md`。🧪 **试验版 / Preview — 各 multi `SKILL.md` 头部有详细注意事项。** -Schema 生成共享输入单独位于 `internal/cli/schema_hints/`(必需的 reviewed `selection/`;`metadata/` 已退役且可缺席;叶子 safety/参数由 Contract 声明)。它们不是 Agent Skill,也不会进入发布 skill 包。 +Schema 生成共享输入单独位于 `internal/cli/schema_hints/`(`selection/` 与 `metadata/` 已退役且可缺席;叶子 safety/参数/选型文案由 ProductDecl / Contract 声明)。它们不是 Agent Skill,也不会进入发布 skill 包。 安装之后,Claude Code / Cursor 等 AI 工具就能通过自然语言直接操作钉钉: diff --git a/internal/app/audit_command.go b/internal/app/audit_command.go index 0d527dec..249e8ccd 100644 --- a/internal/app/audit_command.go +++ b/internal/app/audit_command.go @@ -28,6 +28,20 @@ var ( ) func newAuditCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/audit.json + // products.audit). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "audit", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "查看、导出和校验本地操作审计日志", + UseWhen: []string{ + "需要排查本机 CLI 操作审计记录,或验证审计文件完整性", + }, + AvoidWhen: []string{ + "查钉钉业务数据或发消息请用对应产品命令,不要用 audit", + }, + }, + }) cmd := &cobra.Command{ Use: "audit", Short: "操作审计日志管理", diff --git a/internal/app/event_command.go b/internal/app/event_command.go index 6302fedb..e29ab356 100644 --- a/internal/app/event_command.go +++ b/internal/app/event_command.go @@ -73,6 +73,20 @@ var ( // newEventCommand returns the `event` parent command and all its subcommands. // Wired into root.go's utilityCommands list. func newEventCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/event.json + // products.event). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "event", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "订阅/消费个人消息、动作与群生命周期事件,并管理订阅生命周期", + UseWhen: []string{ + "需要实时监听个人消息接收、全量消息、已读、撤回、表情回应或群生命周期事件,或管理个人事件订阅生命周期", + }, + AvoidWhen: []string{ + "查历史聊天或主动发消息分别用 chat 查询/发送命令", + }, + }, + }) cmd := &cobra.Command{ Use: "event", Short: "事件订阅 (DingTalk Stream 长连接)", diff --git a/internal/app/schema_agent_examples_dry_run_test.go b/internal/app/schema_agent_examples_dry_run_test.go index 19736c1e..b59e93dc 100644 --- a/internal/app/schema_agent_examples_dry_run_test.go +++ b/internal/app/schema_agent_examples_dry_run_test.go @@ -152,7 +152,7 @@ func manualAgentExampleShouldExerciseDryRun(execution cli.ManualAgentExampleExec func manualAgentExampleExecutionPlan(t testing.TB) cli.ManualAgentExampleExecutionPlan { t.Helper() - hints, err := cli.LoadAgentHintsFromSelectionForValidation(os.DirFS("../cli/schema_hints/selection")) + hints, err := cli.LoadAgentHintsFromSelectionForValidation(nil) if err != nil { t.Fatalf("LoadAgentHintsFromSelectionForValidation() error = %v", err) } diff --git a/internal/app/schema_agent_selection_contract_test.go b/internal/app/schema_agent_selection_contract_test.go index 47c73da5..268f70e8 100644 --- a/internal/app/schema_agent_selection_contract_test.go +++ b/internal/app/schema_agent_selection_contract_test.go @@ -14,7 +14,6 @@ package app import ( - "os" "testing" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" @@ -30,7 +29,7 @@ func TestManualAgentSelectionScenariosCoverEveryExecutableSchemaTool(t *testing. if err != nil { t.Fatalf("BindEffectiveCommandRegistry() error = %v", err) } - hints, err := cli.LoadAgentHintsFromSelectionForValidation(os.DirFS("../cli/schema_hints/selection")) + hints, err := cli.LoadAgentHintsFromSelectionForValidation(nil) if err != nil { t.Fatalf("LoadAgentHintsFromSelectionForValidation() error = %v", err) } diff --git a/internal/app/schema_agent_selection_live_test.go b/internal/app/schema_agent_selection_live_test.go index 2705a9cc..ef4bc9c8 100644 --- a/internal/app/schema_agent_selection_live_test.go +++ b/internal/app/schema_agent_selection_live_test.go @@ -133,7 +133,7 @@ func manualAgentSelectionLiveFixture(t testing.TB) (cli.ManualAgentSelectionFixt if err != nil { t.Fatalf("BindEffectiveCommandRegistry() error = %v", err) } - hints, err := cli.LoadAgentHintsFromSelectionForValidation(os.DirFS("../cli/schema_hints/selection")) + hints, err := cli.LoadAgentHintsFromSelectionForValidation(nil) if err != nil { t.Fatalf("LoadAgentHintsFromSelectionForValidation() error = %v", err) } diff --git a/internal/cli/canonical.go b/internal/cli/canonical.go index 69cd71dd..2c60c3b1 100644 --- a/internal/cli/canonical.go +++ b/internal/cli/canonical.go @@ -55,6 +55,20 @@ type FlagSpec struct { // surface has been removed. The command tree is now built from plugins // and static endpoint registration only. func NewMCPCommand(_ context.Context, _ CatalogLoader, _ executor.Runner, _ *pipeline.Engine) *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/mcp.json + // products.mcp). Catalog assembly stamps provenance contract_final. + RegisterProductDecl(ProductDecl{ + ID: "mcp", + Selection: ProductSelectionDecl{ + AgentSummary: "解析和管理当前身份可用的 MCP 服务连接信息", + UseWhen: []string{ + "需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端", + }, + AvoidWhen: []string{ + "查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp", + }, + }, + }) cmd := &cobra.Command{ Use: "mcp", Short: "Canonical MCP-derived CLI surface (static mode)", diff --git a/internal/cli/gen.go b/internal/cli/gen.go index c2d5d370..aaace983 100644 --- a/internal/cli/gen.go +++ b/internal/cli/gen.go @@ -19,14 +19,14 @@ // // Generation inputs (authored, reviewed): // 1. schema_command_registry/ identity (canonical/aliases/navigation) -// 2. schema_hints/selection/*.json selection (use_when/avoid_when) — required +// 2. ProductDecl + leaf ContractFinal Agent routing / selection prose // 3. schema_mcp_metadata.json MCP server tool definitions // 4. schema_parameter_bindings.json parameter type/property mappings // 5. param_concepts.json + schema reviewed parameter synonym policy // 6. cobra command tree (Go runtime) flags/usage/required (reflected) // -// schema_hints/metadata/ is retired (directory may be absent). -// index.metadata may be omitted or {}; leaf safety/params come from Contract. +// schema_hints/selection/ and schema_hints/metadata/ are retired (directories +// may be absent). index.selection / index.metadata may be omitted or {}. // schema_agent_metadata/ is retired: Catalog generation injects Agent metadata // in-memory and does not write or embed that intermediate JSON directory. // diff --git a/internal/cli/product_decl.go b/internal/cli/product_decl.go new file mode 100644 index 00000000..614d689b --- /dev/null +++ b/internal/cli/product_decl.go @@ -0,0 +1,163 @@ +// 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 ( + "fmt" + "sort" + "strings" + "sync" +) + +// ProductDecl provenance labels. Assembly and Agent-metadata generation stamp +// ProductSpec.Selection winners with these sources (symmetric to leaf +// corecmd.contract / corecmd.SchemaDecl). +const ( + ProductDeclProvenanceSource = "cli.product_decl" + ProductDeclSourceRef = "cli.ProductDecl" +) + +// ProductSelectionDecl is the product-level Agent routing prose declared in +// code. Fields align with the former schema_hints/selection products{} shape. +type ProductSelectionDecl struct { + AgentSummary string + UseWhen []string + AvoidWhen []string +} + +// ProductDecl is the product-level Schema routing declaration. Assembly writes +// ProductSpec.Selection with provenance contract_final. Selection JSON rows +// are optional when a product is registered here. +type ProductDecl struct { + ID string + Selection ProductSelectionDecl +} + +var productDecls sync.Map // productID → ProductDecl + +// RegisterProductDecl stores a product-level routing declaration. +// Light runtime write: one map store; no JSON bridge. A non-empty ID with +// incomplete selection panics: declared products are the final source and +// have no selection/ fallback for missing prose. +func RegisterProductDecl(decl ProductDecl) { + productID := strings.TrimSpace(decl.ID) + if productID == "" { + return + } + missing := make([]string, 0, 3) + if strings.TrimSpace(decl.Selection.AgentSummary) == "" { + missing = append(missing, "Selection.AgentSummary") + } + if len(decl.Selection.UseWhen) == 0 { + missing = append(missing, "Selection.UseWhen") + } + if len(decl.Selection.AvoidWhen) == 0 { + missing = append(missing, "Selection.AvoidWhen") + } + if len(missing) > 0 { + panic(fmt.Sprintf( + "product %q ProductDecl is missing %s: a declared product is the final routing source and must carry full selection prose", + productID, strings.Join(missing, ", "))) + } + decl.ID = productID + productDecls.Store(productID, decl) +} + +// LookupProductDecl returns the registered product declaration, if any. +func LookupProductDecl(productID string) (ProductDecl, bool) { + productID = strings.TrimSpace(productID) + if productID == "" { + return ProductDecl{}, false + } + raw, ok := productDecls.Load(productID) + if !ok { + return ProductDecl{}, false + } + decl, ok := raw.(ProductDecl) + if !ok { + return ProductDecl{}, false + } + return decl, true +} + +// HasProductDecl reports whether product-level routing is declared in code. +// Selection-hint coverage gates use this to exempt Decl products from a +// required selection/ products{} row. +func HasProductDecl(productID string) bool { + _, ok := LookupProductDecl(productID) + return ok +} + +// RegisteredProductDeclIDs returns sorted product IDs with an in-code Decl. +func RegisteredProductDeclIDs() []string { + ids := make([]string, 0) + productDecls.Range(func(key, _ any) bool { + if id, ok := key.(string); ok { + if id = strings.TrimSpace(id); id != "" { + ids = append(ids, id) + } + } + return true + }) + sort.Strings(ids) + return ids +} + +// ClearProductDeclForTest removes a registration (tests only). +func ClearProductDeclForTest(productID string) { + productID = strings.TrimSpace(productID) + if productID != "" { + productDecls.Delete(productID) + } +} + +// ProductSelectionFromDecl projects a ProductDecl into SelectionSpec plus +// contract_final FieldProvenance for ProductSpec assembly. +func ProductSelectionFromDecl(decl ProductDecl) (SelectionSpec, map[string]FieldProvenance) { + selection := SelectionSpec{ + AgentSummary: strings.TrimSpace(decl.Selection.AgentSummary), + AgentSummarySource: ProductDeclSourceRef, + UseWhen: append([]string(nil), decl.Selection.UseWhen...), + AvoidWhen: append([]string(nil), decl.Selection.AvoidWhen...), + SourceRefs: []string{ProductDeclSourceRef}, + MetadataSource: ProductDeclProvenanceSource, + }.normalized() + provenance := map[string]FieldProvenance{ + "agent_summary": resolvedFieldProvenance( + selection.AgentSummary, + ProductDeclProvenanceSource, + ProductDeclSourceRef, + "contract_final", + "contract_pass_through", + "ProductDecl final Schema pass-through", + ), + "use_when": resolvedFieldProvenance( + selection.UseWhen, + ProductDeclProvenanceSource, + ProductDeclSourceRef, + "contract_final", + "contract_pass_through", + "ProductDecl final Schema pass-through", + ), + "avoid_when": resolvedFieldProvenance( + selection.AvoidWhen, + ProductDeclProvenanceSource, + ProductDeclSourceRef, + "contract_final", + "contract_pass_through", + "ProductDecl final Schema pass-through", + ), + } + return selection, provenance +} diff --git a/internal/cli/product_decl_test.go b/internal/cli/product_decl_test.go new file mode 100644 index 00000000..9c16b59c --- /dev/null +++ b/internal/cli/product_decl_test.go @@ -0,0 +1,71 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package cli + +import "testing" + +func TestProductDeclRegistryRoundTrip(t *testing.T) { + t.Cleanup(func() { ClearProductDeclForTest("sample") }) + ClearProductDeclForTest("sample") + + if HasProductDecl("sample") { + t.Fatal("HasProductDecl before register must be false") + } + RegisterProductDecl(ProductDecl{}) + if HasProductDecl("") { + t.Fatal("empty ID must not register") + } + + RegisterProductDecl(ProductDecl{ + ID: " sample ", + Selection: ProductSelectionDecl{ + AgentSummary: "Manage samples", + UseWhen: []string{"target is a sample"}, + AvoidWhen: []string{"target is another product"}, + }, + }) + if !HasProductDecl("sample") { + t.Fatal("HasProductDecl after register must be true") + } + got, ok := LookupProductDecl("sample") + if !ok || got.ID != "sample" || got.Selection.AgentSummary != "Manage samples" { + t.Fatalf("LookupProductDecl = %#v, ok=%v", got, ok) + } + ids := RegisteredProductDeclIDs() + found := false + for _, id := range ids { + if id == "sample" { + found = true + break + } + } + if !found { + t.Fatalf("RegisteredProductDeclIDs missing sample: %#v", ids) + } + + selection, provenance := ProductSelectionFromDecl(got) + if selection.AgentSummary != "Manage samples" || selection.AgentSummarySource != ProductDeclSourceRef { + t.Fatalf("ProductSelectionFromDecl selection = %#v", selection) + } + for _, field := range []string{"agent_summary", "use_when", "avoid_when"} { + prov, ok := provenance[field] + if !ok || prov.Precedence != "contract_final" || prov.Source != ProductDeclProvenanceSource { + t.Fatalf("field %s provenance = %#v", field, prov) + } + } + + ClearProductDeclForTest("sample") + if HasProductDecl("sample") { + t.Fatal("ClearProductDeclForTest must remove registration") + } +} + +func TestProductDeclRegisterPanicsOnIncompleteSelection(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("expected panic for incomplete ProductDecl") + } + }() + RegisterProductDecl(ProductDecl{ID: "broken"}) +} diff --git a/internal/cli/schema_agent_selection.go b/internal/cli/schema_agent_selection.go index 07f2ab4a..f4bd6688 100644 --- a/internal/cli/schema_agent_selection.go +++ b/internal/cli/schema_agent_selection.go @@ -43,8 +43,9 @@ type ManualAgentSelectionCase struct { } // ManualAgentSelectionFixture is the stable input to an optional live Agent -// evaluation. It is built from schema_hints/selection and the real bound -// command tree; it is not a second authored hint source. +// evaluation. It is built from ProductDecl/ContractFinal selection prose +// (plus any residual selection HintFiles) and the real bound command tree; +// it is not a second authored hint source. type ManualAgentSelectionFixture struct { Version int `json:"version"` Cases []ManualAgentSelectionCase `json:"cases"` diff --git a/internal/cli/schema_catalog/catalog.json b/internal/cli/schema_catalog/catalog.json index 5149ca32..62a36cb4 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:391e859404a4ff80a0d05bc35f4aa0ba8d90ffcf82b0ec5648356e7c33be0ef0", + "source_hash": "sha256:1a4edf80e10758e36d3efa16f470b596b7b396c35429e61e00eb16d80d885918", "catalog": { "agent_metadata": { "products_with_metadata": 26, "source": "embedded-skill-metadata", - "source_hash": "sha256:5397ea46c3681d39d4532dc18145e6b73d6900472cf6a88faa703d477b078ecb", + "source_hash": "sha256:12f65e8496a3ae727e0371204457848a4a686d4913c72a6661bc95536f25cba5", "surface_hash": "sha256:60eee8e2f37d6d9d60689efce85082798eb9ad38b7ba7c0b471c3de676a85a16", "surface_products": 26, "surface_tools": 845, @@ -40,18 +40,12 @@ "level": "catalog", "products": [ { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=aisearch", - "Skill:skills/mono/references/products/aisearch.md", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/selection/aisearch.json", - "live-dws-schema:product-batch-2026-07-14", - "skills/mono/SKILL.md", - "skills/mono/references/products/aisearch.md" + "cli.ProductDecl" ], "agent_summary": "企业内智能搜人、搜知识内容与搜行为记录", - "agent_summary_source": "dws-agent-selection/aisearch", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "已有明确资源 ID 要读写时改用对应产品;普通 OAuth 登录不用 aisearch" ], @@ -60,31 +54,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/aisearch.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "企业内智能搜人、搜知识内容与搜行为记录" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/aisearch.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "企业内智能搜人、搜知识内容与搜行为记录" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/aisearch.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "已有明确资源 ID 要读写时改用对应产品;普通 OAuth 登录不用 aisearch" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/aisearch.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "已有明确资源 ID 要读写时改用对应产品;普通 OAuth 登录不用 aisearch" ] @@ -92,17 +94,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/aisearch.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "语义找人、按主题搜企业知识,或追溯发送/创建/分享等行为" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/aisearch.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "语义找人、按主题搜企业知识,或追溯发送/创建/分享等行为" ] @@ -216,19 +222,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "dws-schema-live:product-index", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.aitable.prod.json", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/aitable.json", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md", - "skills/mono/references/products/aitable.md" + "cli.ProductDecl" ], "agent_summary": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。", - "agent_summary_source": "dws-agent-selection/aitable", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "目标是在线电子表格单元格读写时用 sheet;普通文档用 doc" ], @@ -237,31 +236,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/aitable.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/aitable.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "目标是在线电子表格单元格读写时用 sheet;普通文档用 doc" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "目标是在线电子表格单元格读写时用 sheet;普通文档用 doc" ] @@ -269,17 +276,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/aitable.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时" ] @@ -4620,18 +4631,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "cobra-help:dws attendance --help", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.attendance.prod.json", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/attendance.json", - "skills/mono/SKILL.md", - "skills/mono/references/products/attendance.md" + "cli.ProductDecl" ], "agent_summary": "查询考勤记录、排班、班次、考勤组、审批、报表、个人规则和假期,并执行经确认的考勤配置变更。", - "agent_summary_source": "dws-agent-selection/attendance", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "用户只需处理普通日历日程、非考勤类活动签到或通用审批流状态,而不是钉钉考勤业务。" ], @@ -4640,31 +4645,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/attendance.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询考勤记录、排班、班次、考勤组、审批、报表、个人规则和假期,并执行经确认的考勤配置变更。" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/attendance.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询考勤记录、排班、班次、考勤组、审批、报表、个人规则和假期,并执行经确认的考勤配置变更。" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/attendance.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户只需处理普通日历日程、非考勤类活动签到或通用审批流状态,而不是钉钉考勤业务。" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/attendance.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户只需处理普通日历日程、非考勤类活动签到或通用审批流状态,而不是钉钉考勤业务。" ] @@ -4672,17 +4685,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/attendance.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要查询或管理钉钉考勤数据、规则、排班、考勤组、审批表单或假期余额。" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/attendance.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要查询或管理钉钉考勤数据、规则、排班、考勤组、审批表单或假期余额。" ] @@ -6355,15 +6372,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=audit", - "cobra-help:dws audit --help", - "internal/cli/schema_command_registry.json#audit", - "internal/cli/schema_hints/selection/audit.json" + "cli.ProductDecl" ], "agent_summary": "查看、导出和校验本地操作审计日志", - "agent_summary_source": "dws-agent-selection/audit", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "查钉钉业务数据或发消息请用对应产品命令,不要用 audit" ], @@ -6372,31 +6386,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/audit.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查看、导出和校验本地操作审计日志" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/audit.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查看、导出和校验本地操作审计日志" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/audit.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查钉钉业务数据或发消息请用对应产品命令,不要用 audit" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/audit.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查钉钉业务数据或发消息请用对应产品命令,不要用 audit" ] @@ -6404,17 +6426,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/audit.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要排查本机 CLI 操作审计记录,或验证审计文件完整性" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/audit.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要排查本机 CLI 操作审计记录,或验证审计文件完整性" ] @@ -6514,18 +6540,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.calendar.prod.json", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/calendar.json", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md", - "skills/mono/references/products/calendar.md" + "cli.ProductDecl" ], "agent_summary": "管理日历本、日程、参与人、附件、会议室,并查询人员或会议室闲忙状态。", - "agent_summary_source": "dws-agent-selection/calendar", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "目标是聊天消息、待办或审批,而不是日历与日程资源时。" ], @@ -6534,31 +6554,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/calendar.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理日历本、日程、参与人、附件、会议室,并查询人员或会议室闲忙状态。" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/calendar.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理日历本、日程、参与人、附件、会议室,并查询人员或会议室闲忙状态。" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/calendar.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "目标是聊天消息、待办或审批,而不是日历与日程资源时。" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/calendar.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "目标是聊天消息、待办或审批,而不是日历与日程资源时。" ] @@ -6566,17 +6594,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/calendar.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要查询或管理钉钉日历、日程、参与人、会议室和闲忙信息时。" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/calendar.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要查询或管理钉钉日历、日程、参与人、会议室和闲忙信息时。" ] @@ -7769,21 +7801,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=chat", - "Skill:skills/mono/references/products/chat.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.chat.prod.json", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/chat.json", - "live-dws-schema:product-batch-2026-07-14", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md", - "skills/mono/references/products/chat.md" + "cli.ProductDecl" ], "agent_summary": "管理钉钉会话、群聊、群成员、机器人、消息检索与发送", - "agent_summary_source": "dws-agent-selection/chat", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "邮件用 mail;开放平台应用/机器人建号发布用 dev;企业语义找人优先 aisearch person" ], @@ -7792,31 +7815,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/chat.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉钉会话、群聊、群成员、机器人、消息检索与发送" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉钉会话、群聊、群成员、机器人、消息检索与发送" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/chat.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "邮件用 mail;开放平台应用/机器人建号发布用 dev;企业语义找人优先 aisearch person" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "邮件用 mail;开放平台应用/机器人建号发布用 dev;企业语义找人优先 aisearch person" ] @@ -7824,17 +7855,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/chat.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "请求涉及群聊管理、聊天记录、消息发送、会话设置或群机器人" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/chat.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "请求涉及群聊管理、聊天记录、消息发送、会话设置或群机器人" ] @@ -11622,19 +11657,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=contact", - "Skill:skills/mono/references/products/contact.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.contact.prod.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/contact.json", - "live-dws-schema:product-batch-2026-07-14", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md" + "cli.ProductDecl" ], "agent_summary": "查询通讯录与花名册,并管理企业、部门、员工及企业账号", - "agent_summary_source": "dws-agent-selection/contact", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "职责/上级等语义找人优先 aisearch person;不要用 contact 发消息;写操作前确认当前企业和目标信息" ], @@ -11643,31 +11671,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/contact.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询通讯录与花名册,并管理企业、部门、员工及企业账号" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/contact.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询通讯录与花名册,并管理企业、部门、员工及企业账号" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/contact.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "职责/上级等语义找人优先 aisearch person;不要用 contact 发消息;写操作前确认当前企业和目标信息" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/contact.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "职责/上级等语义找人优先 aisearch person;不要用 contact 发消息;写操作前确认当前企业和目标信息" ] @@ -11675,17 +11711,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/contact.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "按姓名/手机号/userId/部门条件做通讯录精确查询,或明确执行企业与员工入企管理" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/contact.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "按姓名/手机号/userId/部门条件做通讯录精确查询,或明确执行企业与员工入企管理" ] @@ -12680,17 +12720,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=dev", - "Skill:skills/mono/references/products/dev.md", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/selection/dev.json", - "live-dws-schema:product-batch-2026-07-14", - "skills/mono/references/products/dev.md" + "cli.ProductDecl" ], "agent_summary": "管理开放平台应用、权限、机器人、版本发布与本地连接器", - "agent_summary_source": "dws-agent-selection/dev", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "只查开放平台文档用 devdoc;业务聊天/邮信用 chat/mail" ], @@ -12699,31 +12734,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/dev.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理开放平台应用、权限、机器人、版本发布与本地连接器" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/dev.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理开放平台应用、权限、机器人、版本发布与本地连接器" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/dev.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "只查开放平台文档用 devdoc;业务聊天/邮信用 chat/mail" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/dev.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "只查开放平台文档用 devdoc;业务聊天/邮信用 chat/mail" ] @@ -12731,17 +12774,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/dev.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "创建/配置开放平台应用、机器人、权限、事件订阅或发布版本" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/dev.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "创建/配置开放平台应用、机器人、权限、事件订阅或发布版本" ] @@ -13809,15 +13856,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=devapp", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/selection/devapp.json", - "internal/shortcut/devapp" + "cli.ProductDecl" ], "agent_summary": "管理钉钉开放平台企业内部应用、成员、权限、机器人、事件与版本", - "agent_summary_source": "dws-agent-selection/devapp", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "开放平台接口文档搜索使用 devdoc;普通钉钉业务数据使用对应产品命令" ], @@ -13826,31 +13870,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/devapp.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉钉开放平台企业内部应用、成员、权限、机器人、事件与版本" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/devapp.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉钉开放平台企业内部应用、成员、权限、机器人、事件与版本" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/devapp.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "开放平台接口文档搜索使用 devdoc;普通钉钉业务数据使用对应产品命令" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/devapp.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "开放平台接口文档搜索使用 devdoc;普通钉钉业务数据使用对应产品命令" ] @@ -13858,17 +13910,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/devapp.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "请求涉及企业内部应用的查询、创建、配置、成员权限、机器人、事件订阅或版本管理" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/devapp.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "请求涉及企业内部应用的查询、创建、配置、成员权限、机器人、事件订阅或版本管理" ] @@ -14398,19 +14454,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=devdoc", - "Skill:skills/mono/references/products/devdoc.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.devdoc.prod.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/devdoc.json", - "live-dws-schema:product-batch-2026-07-14", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md" + "cli.ProductDecl" ], "agent_summary": "搜索钉钉开放平台开发文档与错误排查资料", - "agent_summary_source": "dws-agent-selection/devdoc", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "搜索用户业务文档或读写业务数据时不要用 devdoc" ], @@ -14419,31 +14468,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/devdoc.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "搜索钉钉开放平台开发文档与错误排查资料" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/devdoc.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "搜索钉钉开放平台开发文档与错误排查资料" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/devdoc.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "搜索用户业务文档或读写业务数据时不要用 devdoc" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/devdoc.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "搜索用户业务文档或读写业务数据时不要用 devdoc" ] @@ -14451,17 +14508,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/devdoc.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查询 OpenAPI、字段、错误码、SDK、鉴权或接入指南" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/devdoc.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查询 OpenAPI、字段、错误码、SDK、鉴权或接入指南" ] @@ -14510,18 +14571,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=ding", - "Skill:skills/mono/references/products/ding.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.ding.prod.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/ding.json", - "live-dws-schema:product-batch-2026-07-14", - "skills/mono/SKILL.md" + "cli.ProductDecl" ], "agent_summary": "以企业机器人发送或撤回应用内/短信/电话 DING", - "agent_summary_source": "dws-agent-selection/ding", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "普通聊天消息用 chat;用户身份 DING 不要走机器人命令" ], @@ -14530,31 +14585,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/ding.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "以企业机器人发送或撤回应用内/短信/电话 DING" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/ding.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "以企业机器人发送或撤回应用内/短信/电话 DING" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/ding.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "普通聊天消息用 chat;用户身份 DING 不要走机器人命令" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/ding.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "普通聊天消息用 chat;用户身份 DING 不要走机器人命令" ] @@ -14562,17 +14625,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/ding.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要机器人身份发送或撤回 DING" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/ding.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要机器人身份发送或撤回 DING" ] @@ -14761,18 +14828,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=doc", - "Skill:skills/mono/references/products/doc.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.doc.prod.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/doc.json", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md" + "cli.ProductDecl" ], "agent_summary": "管理钉钉在线文档的正文、块、评论、导入导出、模板与版本", - "agent_summary_source": "dws-agent-selection/doc", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "文件、目录、上传下载及节点权限已迁移到 drive;知识库空间和成员使用 wiki;不要用于搜索开放平台开发文档" ], @@ -14781,31 +14842,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/doc.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉钉在线文档的正文、块、评论、导入导出、模板与版本" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉钉在线文档的正文、块、评论、导入导出、模板与版本" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/doc.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "文件、目录、上传下载及节点权限已迁移到 drive;知识库空间和成员使用 wiki;不要用于搜索开放平台开发文档" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "文件、目录、上传下载及节点权限已迁移到 drive;知识库空间和成员使用 wiki;不要用于搜索开放平台开发文档" ] @@ -14813,17 +14882,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/doc.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "创建、读取或编辑在线文档内容,或处理文档块、评论、导入导出、模板和版本时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/doc.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "创建、读取或编辑在线文档内容,或处理文档块、评论、导入导出、模板和版本时" ] @@ -16621,17 +16694,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=drive", - "Skill:skills/mono/references/products/drive.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.drive.prod.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/drive.json", - "skills/mono/SKILL.md" + "cli.ProductDecl" ], "agent_summary": "管理钉盘及文档空间中的文件、目录、上传下载、回收站与公开发布", - "agent_summary_source": "dws-agent-selection/drive", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "需要读取或编辑在线文档正文时使用 doc;需要管理知识库空间或成员时使用 wiki" ], @@ -16640,31 +16708,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/drive.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉盘及文档空间中的文件、目录、上传下载、回收站与公开发布" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉盘及文档空间中的文件、目录、上传下载、回收站与公开发布" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/drive.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要读取或编辑在线文档正文时使用 doc;需要管理知识库空间或成员时使用 wiki" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要读取或编辑在线文档正文时使用 doc;需要管理知识库空间或成员时使用 wiki" ] @@ -16672,17 +16748,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/drive.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "浏览、搜索、上传、下载或整理钉盘和文档空间文件时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/drive.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "浏览、搜索、上传、下载或整理钉盘和文档空间文件时" ] @@ -17930,19 +18010,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=event", - "Skill:skills/mono/references/products/event.md", - "cobra-help:dws event --help", - "internal/cli/schema_command_registry.json#event", - "internal/cli/schema_hints/selection/event.json", - "live-dws-schema:product-batch-2026-07-14", - "skills/mono/SKILL.md", - "skills/mono/references/products/event.md" + "cli.ProductDecl" ], "agent_summary": "订阅/消费个人消息、动作与群生命周期事件,并管理订阅生命周期", - "agent_summary_source": "dws-agent-selection/event", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "查历史聊天或主动发消息分别用 chat 查询/发送命令" ], @@ -17951,31 +18024,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/event.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "订阅/消费个人消息、动作与群生命周期事件,并管理订阅生命周期" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/event.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "订阅/消费个人消息、动作与群生命周期事件,并管理订阅生命周期" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/event.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查历史聊天或主动发消息分别用 chat 查询/发送命令" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/event.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查历史聊天或主动发消息分别用 chat 查询/发送命令" ] @@ -17983,17 +18064,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/event.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要实时监听个人消息接收、全量消息、已读、撤回、表情回应或群生命周期事件,或管理个人事件订阅生命周期" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/event.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要实时监听个人消息接收、全量消息、已读、撤回、表情回应或群生命周期事件,或管理个人事件订阅生命周期" ] @@ -18157,16 +18242,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=hrbrain", - "Skill:skills/mono/references/products/hrbrain.md", - "Skill:skills/multi/dingtalk-hrbrain/SKILL.md", - "internal/cli/schema_hints/selection/hrbrain.json", - "skills/mono/SKILL.md" + "cli.ProductDecl" ], "agent_summary": "钉钉组织大脑:人才池管理、员工档案查询与人才搜索", - "agent_summary_source": "dws-agent-selection/hrbrain", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "要操作通讯录/组织架构基础信息时改用 contact 产品", "要提交/查询战略解码、经营合约、目标等 OKR 能力时改用 agoal(CLI-only,未接入 Agent Schema)" @@ -18176,32 +18257,40 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/hrbrain.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "钉钉组织大脑:人才池管理、员工档案查询与人才搜索" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/hrbrain.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "钉钉组织大脑:人才池管理、员工档案查询与人才搜索" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/hrbrain.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "要操作通讯录/组织架构基础信息时改用 contact 产品", "要提交/查询战略解码、经营合约、目标等 OKR 能力时改用 agoal(CLI-only,未接入 Agent Schema)" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/hrbrain.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "要操作通讯录/组织架构基础信息时改用 contact 产品", "要提交/查询战略解码、经营合约、目标等 OKR 能力时改用 agoal(CLI-only,未接入 Agent Schema)" @@ -18210,17 +18299,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/hrbrain.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要查询人才池、员工档案(元数据/标签/职业历程/绩效)或搜索员工时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/hrbrain.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要查询人才池、员工档案(元数据/标签/职业历程/绩效)或搜索员工时" ] @@ -18550,18 +18643,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=live", - "Skill:skills/mono/references/products/live.md", - "Skill:skills/multi/dingtalk-live/SKILL.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.live.prod.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/live.json", - "live-dws-schema:product-batch-2026-07-14" + "cli.ProductDecl" ], "agent_summary": "查询当前用户发起的直播列表", - "agent_summary_source": "dws-agent-selection/live", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "当前公开面不支持创建/控制直播" ], @@ -18570,31 +18657,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/live.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询当前用户发起的直播列表" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/live.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询当前用户发起的直播列表" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/live.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "当前公开面不支持创建/控制直播" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/live.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "当前公开面不支持创建/控制直播" ] @@ -18602,17 +18697,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/live.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要查看自己的直播列表或基础统计" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/live.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要查看自己的直播列表或基础统计" ] @@ -18660,21 +18759,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=mail", - "Skill:skills/mono/references/products/mail.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.mail.prod.json", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/mail.json", - "live-dws-schema:product-batch-2026-07-14", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md", - "skills/mono/references/products/mail.md" + "cli.ProductDecl" ], "agent_summary": "管理邮箱、邮件、草稿、附件、文件夹、联系人与邮件模板", - "agent_summary_source": "dws-agent-selection/mail", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "即时消息用 chat;开放平台应用配置用 dev", "公开 catalog 不含 thread trash / sent-message recall 等硬 --yes 命令时不要臆造路径" @@ -18684,32 +18774,40 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/mail.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理邮箱、邮件、草稿、附件、文件夹、联系人与邮件模板" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/mail.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理邮箱、邮件、草稿、附件、文件夹、联系人与邮件模板" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/mail.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "即时消息用 chat;开放平台应用配置用 dev", "公开 catalog 不含 thread trash / sent-message recall 等硬 --yes 命令时不要臆造路径" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/mail.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "即时消息用 chat;开放平台应用配置用 dev", "公开 catalog 不含 thread trash / sent-message recall 等硬 --yes 命令时不要臆造路径" @@ -18718,17 +18816,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/mail.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查收、搜索、阅读、回复、发送或整理邮件" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/mail.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查收、搜索、阅读、回复、发送或整理邮件" ] @@ -19977,15 +20079,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=markdown", - "Wukong-parity:3306c3307", - "internal/cli/schema_hints/selection/markdown.json", - "skills/mono/SKILL.md" + "cli.ProductDecl" ], "agent_summary": "跨钉盘与文档空间创建、获取、覆盖和局部修补原生 Markdown 文件", - "agent_summary_source": "dws-agent-selection/markdown", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "在线文档正文操作使用 doc;普通二进制文件上传下载使用 drive" ], @@ -19994,31 +20093,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/markdown.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "跨钉盘与文档空间创建、获取、覆盖和局部修补原生 Markdown 文件" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/markdown.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "跨钉盘与文档空间创建、获取、覆盖和局部修补原生 Markdown 文件" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/markdown.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "在线文档正文操作使用 doc;普通二进制文件上传下载使用 drive" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/markdown.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "在线文档正文操作使用 doc;普通二进制文件上传下载使用 drive" ] @@ -20026,17 +20133,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/markdown.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "目标是原生 .md 文件,并需要在 Drive/Doc 路由间安全处理内容时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/markdown.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "目标是原生 .md 文件,并需要在 Drive/Doc 路由间安全处理内容时" ] @@ -20173,15 +20284,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=mcp", - "cobra-help:dws mcp --help", - "internal/cli/schema_command_registry.json#mcp", - "internal/cli/schema_hints/selection/mcp.json" + "cli.ProductDecl" ], "agent_summary": "解析和管理当前身份可用的 MCP 服务连接信息", - "agent_summary_source": "dws-agent-selection/mcp", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp" ], @@ -20190,31 +20298,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/mcp.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "解析和管理当前身份可用的 MCP 服务连接信息" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/mcp.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "解析和管理当前身份可用的 MCP 服务连接信息" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/mcp.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/mcp.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp" ] @@ -20222,17 +20338,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/mcp.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/mcp.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端" ] @@ -20278,18 +20398,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "cobra-help:dws minutes --help", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.minutes.prod.json", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/minutes.json", - "skills/mono/SKILL.md", - "skills/mono/references/products/minutes.md" + "cli.ProductDecl" ], "agent_summary": "查询和维护钉钉听记的转写、摘要、待办、权限、录音、标签、说话人总结及文件上传会话。", - "agent_summary_source": "dws-agent-selection/minutes", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "用户要处理普通文档正文、群聊消息或日历会议安排,而不是钉钉听记。" ], @@ -20298,31 +20412,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/minutes.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询和维护钉钉听记的转写、摘要、待办、权限、录音、标签、说话人总结及文件上传会话。" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/minutes.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询和维护钉钉听记的转写、摘要、待办、权限、录音、标签、说话人总结及文件上传会话。" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/minutes.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要处理普通文档正文、群聊消息或日历会议安排,而不是钉钉听记。" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/minutes.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要处理普通文档正文、群聊消息或日历会议安排,而不是钉钉听记。" ] @@ -20330,17 +20452,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/minutes.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要查找、读取、编辑或管理钉钉听记及其录音、转写、摘要和衍生内容。" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/minutes.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要查找、读取、编辑或管理钉钉听记及其录音、转写、摘要和衍生内容。" ] @@ -21475,17 +21601,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=oa", - "Skill:skills/mono/references/products/oa.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.oa.prod.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/oa.json", - "skills/mono/SKILL.md" + "cli.ProductDecl" ], "agent_summary": "查询和处理 OA 审批实例、任务、记录、抄送与评论", - "agent_summary_source": "dws-agent-selection/oa", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "不要用于普通待办任务或工作日志;需要创建审批实例时先确认当前命令面是否支持" ], @@ -21494,31 +21615,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/oa.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询和处理 OA 审批实例、任务、记录、抄送与评论" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/oa.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询和处理 OA 审批实例、任务、记录、抄送与评论" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/oa.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "不要用于普通待办任务或工作日志;需要创建审批实例时先确认当前命令面是否支持" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/oa.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "不要用于普通待办任务或工作日志;需要创建审批实例时先确认当前命令面是否支持" ] @@ -21526,17 +21655,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/oa.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查看待审、已办、已发起或抄送审批,并执行同意、拒绝、撤销、转交等审批动作时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/oa.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查看待审、已办、已发起或抄送审批,并执行同意、拒绝、撤销、转交等审批动作时" ] @@ -22221,16 +22354,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=pat", - "Skill:skills/mono/references/products/pat.md", - "internal/cli/schema_hints/selection/pat.json", - "live-dws-schema:product-batch-2026-07-14", - "skills/mono/SKILL.md" + "cli.ProductDecl" ], "agent_summary": "管理 Agent 的 PAT 行为授权与本地浏览器策略", - "agent_summary_source": "dws-agent-selection/pat", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "普通登录用 auth;不要与开放平台应用权限或具体业务写操作混用" ], @@ -22239,31 +22368,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/pat.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理 Agent 的 PAT 行为授权与本地浏览器策略" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/pat.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理 Agent 的 PAT 行为授权与本地浏览器策略" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/pat.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "普通登录用 auth;不要与开放平台应用权限或具体业务写操作混用" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/pat.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "普通登录用 auth;不要与开放平台应用权限或具体业务写操作混用" ] @@ -22271,17 +22408,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/pat.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "缺少行为授权需要预览/授予 scope,或配置授权是否打开浏览器" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/pat.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "缺少行为授权需要预览/授予 scope,或配置授权是否打开浏览器" ] @@ -22358,18 +22499,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=report", - "Skill:skills/mono/references/products/report.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.report.prod.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/report.json", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md" + "cli.ProductDecl" ], "agent_summary": "查询日志模板、收发日志、日志正文与统计,并按模板提交日志", - "agent_summary_source": "dws-agent-selection/report", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "不要用于待办任务、OA 审批或在线文档正文编辑" ], @@ -22378,31 +22513,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/report.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询日志模板、收发日志、日志正文与统计,并按模板提交日志" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/report.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "查询日志模板、收发日志、日志正文与统计,并按模板提交日志" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/report.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "不要用于待办任务、OA 审批或在线文档正文编辑" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/report.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "不要用于待办任务、OA 审批或在线文档正文编辑" ] @@ -22410,17 +22553,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/report.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查看或提交日报、周报等钉钉日志时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/report.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查看或提交日报、周报等钉钉日志时" ] @@ -22732,20 +22879,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "cobra-help:dws sheet --help", - "dws-schema-live:product-index", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.sheet.prod.json", - "internal/cli/schema_command_registry.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/sheet.json", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md", - "skills/mono/references/products/sheet.md" + "cli.ProductDecl" ], "agent_summary": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", - "agent_summary_source": "dws-agent-selection/sheet", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" @@ -22755,32 +22894,40 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/sheet.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/sheet.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" @@ -22789,18 +22936,22 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/sheet.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" @@ -25487,18 +25638,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=todo", - "Skill:skills/mono/references/products/todo.md", - "dws-wukong-envelope@4574f7022c32:dws-wukong-discovery.todo.prod.json", - "internal/cli/schema_hints/imported/wukong.json", - "internal/cli/schema_hints/selection/todo.json", - "skills/mono/SKILL.md", - "skills/mono/references/intent-guide.md" + "cli.ProductDecl" ], "agent_summary": "管理待办任务、标签、子任务、执行人、参与人、评论、附件与提醒", - "agent_summary_source": "dws-agent-selection/todo", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "不要用于 OA 审批流转、工作日志提交或日历日程管理" ], @@ -25507,31 +25652,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/todo.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理待办任务、标签、子任务、执行人、参与人、评论、附件与提醒" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理待办任务、标签、子任务、执行人、参与人、评论、附件与提醒" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/todo.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "不要用于 OA 审批流转、工作日志提交或日历日程管理" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "不要用于 OA 审批流转、工作日志提交或日历日程管理" ] @@ -25539,17 +25692,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/todo.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查询、创建或更新个人待办及其协作信息时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/todo.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查询、创建或更新个人待办及其协作信息时" ] @@ -26584,15 +26741,12 @@ ] }, { - "agent_metadata_source": "embedded-skill-metadata", + "agent_metadata_source": "cli.product_decl", "agent_source_refs": [ - "CommandRegistry:product=wiki", - "Skill:skills/mono/references/products/wiki.md", - "internal/cli/schema_hints/selection/wiki.json", - "skills/mono/SKILL.md" + "cli.ProductDecl" ], "agent_summary": "管理钉钉知识库空间、节点与成员权限", - "agent_summary_source": "dws-agent-selection/wiki", + "agent_summary_source": "cli.ProductDecl", "avoid_when": [ "需要编辑在线文档正文时使用 doc;只管理钉盘普通文件时使用 drive" ], @@ -26601,31 +26755,39 @@ "agent_summary": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/wiki.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉钉知识库空间、节点与成员权限" } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/wiki.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": "管理钉钉知识库空间、节点与成员权限" }, "avoid_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/wiki.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要编辑在线文档正文时使用 doc;只管理钉盘普通文件时使用 drive" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/wiki.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "需要编辑在线文档正文时使用 doc;只管理钉盘普通文件时使用 drive" ] @@ -26633,17 +26795,21 @@ "use_when": { "candidates": [ { - "precedence": "reviewed_explicit", + "precedence": "contract_final", + "review_reason": "ProductDecl final Schema pass-through", "selected": true, - "source": "internal/cli/schema_hints/selection/wiki.json", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查找或管理知识库、知识库内节点及知识库成员时" ] } ], - "precedence": "reviewed_explicit", - "resolution": "highest_precedence", - "source": "internal/cli/schema_hints/selection/wiki.json", + "precedence": "contract_final", + "resolution": "contract_pass_through", + "review_reason": "ProductDecl final Schema pass-through", + "source": "cli.product_decl", + "source_ref": "cli.ProductDecl", "value": [ "查找或管理知识库、知识库内节点及知识库成员时" ] diff --git a/internal/cli/schema_catalog/tools/aitable.json b/internal/cli/schema_catalog/tools/aitable.json index fadd9f29..fb24086c 100644 --- a/internal/cli/schema_catalog/tools/aitable.json +++ b/internal/cli/schema_catalog/tools/aitable.json @@ -32359,26 +32359,6 @@ "source": "cobra_usage", "value": "待新增字段列表 JSON 数组,至少包含 1 个字段,单次最多 15 个。系统会按数组顺序依次创建,返回结果顺序与入参保持一致,并逐项标明成功/失败状态。若是单个字段可直接使用 --name/--type/--config" }, - "interface_type": { - "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "array" - }, - { - "precedence": "fallback", - "selected": false, - "source": "cobra_flag_type", - "value": "string" - } - ], - "precedence": "native_annotation", - "resolution": "highest_precedence", - "source": "native_annotation", - "value": "array" - }, "property": { "candidates": [ { @@ -32395,23 +32375,17 @@ }, "required": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": true - }, { "precedence": "default", - "selected": false, + "selected": true, "source": "default", "value": false } ], - "precedence": "native_annotation", - "resolution": "highest_precedence", - "source": "native_annotation", - "value": true + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false }, "required_when": { "candidates": [ @@ -32442,9 +32416,8 @@ "value": "string" } }, - "interface_type": "array", "property": "fields", - "required": true, + "required": false, "type": "string" }, "name": { @@ -57901,23 +57874,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -57962,7 +57929,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": false, "type": "string" }, @@ -61859,26 +61826,6 @@ "source": "cobra_usage", "value": "结构化过滤条件 JSON(可选)" }, - "interface_type": { - "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "object" - }, - { - "precedence": "fallback", - "selected": false, - "source": "cobra_flag_type", - "value": "string" - } - ], - "precedence": "native_annotation", - "resolution": "highest_precedence", - "source": "native_annotation", - "value": "object" - }, "property": { "candidates": [ { @@ -61936,7 +61883,6 @@ "value": "string" } }, - "interface_type": "object", "property": "filters", "required": false, "type": "string" @@ -62056,23 +62002,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -62117,7 +62057,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": false, "type": "string" }, @@ -62228,26 +62168,6 @@ "source": "cobra_usage", "value": "排序条件 JSON 数组(可选)" }, - "interface_type": { - "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "array" - }, - { - "precedence": "fallback", - "selected": false, - "source": "cobra_flag_type", - "value": "string" - } - ], - "precedence": "native_annotation", - "resolution": "highest_precedence", - "source": "native_annotation", - "value": "array" - }, "property": { "candidates": [ { @@ -62305,7 +62225,6 @@ "value": "string" } }, - "interface_type": "array", "property": "sort", "required": false, "type": "string" diff --git a/internal/cli/schema_catalog/tools/chat.json b/internal/cli/schema_catalog/tools/chat.json index 90ae3974..5dc4ed8f 100644 --- a/internal/cli/schema_catalog/tools/chat.json +++ b/internal/cli/schema_catalog/tools/chat.json @@ -54492,23 +54492,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -54559,7 +54553,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": true, "type": "string" } @@ -66205,23 +66199,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -66272,7 +66260,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": true, "type": "string" } @@ -84009,23 +83997,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -84076,7 +84058,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": false, "type": "string" }, diff --git a/internal/cli/schema_catalog/tools/contact.json b/internal/cli/schema_catalog/tools/contact.json index 82c7485c..2f03a7ca 100644 --- a/internal/cli/schema_catalog/tools/contact.json +++ b/internal/cli/schema_catalog/tools/contact.json @@ -17796,23 +17796,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -17869,7 +17863,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": true, "type": "string" } diff --git a/internal/cli/schema_catalog/tools/doc.json b/internal/cli/schema_catalog/tools/doc.json index 54f1a730..2f80f93f 100644 --- a/internal/cli/schema_catalog/tools/doc.json +++ b/internal/cli/schema_catalog/tools/doc.json @@ -27243,23 +27243,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -27316,7 +27310,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": true, "type": "string" } @@ -29724,23 +29718,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -29785,7 +29773,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": false, "type": "string" }, diff --git a/internal/cli/schema_catalog/tools/drive.json b/internal/cli/schema_catalog/tools/drive.json index e4556bcc..039247d4 100644 --- a/internal/cli/schema_catalog/tools/drive.json +++ b/internal/cli/schema_catalog/tools/drive.json @@ -12459,26 +12459,6 @@ "source": "cobra_usage", "value": "每页返回数量 (默认 20,最大 50),仅 spaceType 为 orgSpace 时有效" }, - "interface_type": { - "candidates": [ - { - "precedence": "mcp_metadata", - "selected": true, - "source": "mcp_metadata", - "value": "number" - }, - { - "precedence": "fallback", - "selected": false, - "source": "cobra_flag_type", - "value": "integer" - } - ], - "precedence": "mcp_metadata", - "resolution": "highest_precedence", - "source": "mcp_metadata", - "value": "number" - }, "property": { "candidates": [ { @@ -12549,7 +12529,6 @@ } }, "interface_description": "每页返回数量,默认 20,最大 50。建议根据实际需求调整,较大的值可以减少请求次数,但会增加单次响应的数据量。仅 spaceType 为 orgSpace 时有效", - "interface_type": "number", "property": "maxResults", "required": false, "type": "integer" @@ -22992,23 +22971,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -23065,7 +23038,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": true, "type": "string" } @@ -26106,23 +26079,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -26179,7 +26146,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": true, "type": "string" }, @@ -26769,23 +26736,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -26842,7 +26803,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": true, "type": "string" } diff --git a/internal/cli/schema_catalog/tools/mail.json b/internal/cli/schema_catalog/tools/mail.json index 9e00f8eb..cb192955 100644 --- a/internal/cli/schema_catalog/tools/mail.json +++ b/internal/cli/schema_catalog/tools/mail.json @@ -20959,23 +20959,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -21032,7 +21026,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": true, "type": "string" } diff --git a/internal/cli/schema_catalog/tools/minutes.json b/internal/cli/schema_catalog/tools/minutes.json index 1f325967..8a309043 100644 --- a/internal/cli/schema_catalog/tools/minutes.json +++ b/internal/cli/schema_catalog/tools/minutes.json @@ -18343,23 +18343,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -18404,7 +18398,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": false, "type": "string" } @@ -18971,23 +18965,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -19032,7 +19020,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": false, "type": "string" } @@ -19599,23 +19587,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -19660,7 +19642,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": false, "type": "string" } diff --git a/internal/cli/schema_catalog/tools/wiki.json b/internal/cli/schema_catalog/tools/wiki.json index e26e534a..a9f27714 100644 --- a/internal/cli/schema_catalog/tools/wiki.json +++ b/internal/cli/schema_catalog/tools/wiki.json @@ -9799,23 +9799,17 @@ }, "property": { "candidates": [ - { - "precedence": "native_annotation", - "selected": true, - "source": "native_annotation", - "value": "keyword" - }, { "precedence": "inference", - "selected": false, + "selected": true, "source": "flag_name_inference", "value": "query" } ], - "precedence": "native_annotation", + "precedence": "inference", "resolution": "highest_precedence", - "source": "native_annotation", - "value": "keyword" + "source": "flag_name_inference", + "value": "query" }, "required": { "candidates": [ @@ -9872,7 +9866,7 @@ "value": "string" } }, - "property": "keyword", + "property": "query", "required": true, "type": "string" } diff --git a/internal/cli/schema_catalog_test.go b/internal/cli/schema_catalog_test.go index 100a8942..983a0dbe 100644 --- a/internal/cli/schema_catalog_test.go +++ b/internal/cli/schema_catalog_test.go @@ -1102,19 +1102,25 @@ func TestEmbeddedCatalogAitableQueryKeywordAndHintParamDecls(t *testing.T) { } } - // shortcut +record-query Execute maps --query → keyword; without ParamDecl - // composite leaves published property=query via flag_name_inference. + // shortcut +record-query Execute maps --query → MCP keyword at runtime, but + // merge-base Schema published property=query (flag_name_inference). Keep that + // delivery for schema-compatibility; do not reintroduce ParamDecl keyword. shortcutLeaf, err := embeddedSchemaPayload([]string{"aitable +record-query"}) if err != nil { t.Fatal(err) } shortcutQuery := schemaMap(shortcutLeaf["parameters"])["query"] - if shortcutQuery["property"] != "keyword" { - t.Fatalf("aitable +record-query --query property = %#v, want keyword", shortcutQuery["property"]) + if shortcutQuery["property"] != "query" { + t.Fatalf("aitable +record-query --query property = %#v, want query (merge-base)", shortcutQuery["property"]) } - shortcutPropProv := schemaMap(shortcutQuery["field_provenance"])["property"] - if got, _ := shortcutPropProv["source"].(string); got != "native_annotation" { - t.Fatalf("aitable +record-query --query property source = %#v, want native_annotation", shortcutPropProv) + if _, has := shortcutQuery["interface_type"]; has { + t.Fatalf("aitable +record-query --query unexpectedly publishes interface_type: %#v", shortcutQuery["interface_type"]) + } + for _, flagName := range []string{"filters", "sort"} { + param := schemaMap(shortcutLeaf["parameters"])[flagName] + if _, has := param["interface_type"]; has { + t.Fatalf("aitable +record-query --%s unexpectedly publishes interface_type: %#v", flagName, param["interface_type"]) + } } // Former RegisterSchemaHints required pins that match runtime validation. @@ -1145,38 +1151,16 @@ func TestEmbeddedCatalogAitableQueryKeywordAndHintParamDecls(t *testing.T) { } } -func TestEmbeddedCatalogSmartQueryMapsToKeyword(t *testing.T) { - // smart shortcuts whose Execute maps --query → MCP keyword need an explicit - // ParamDecl; without it composite leaves publish property=query via - // flag_name_inference. +minutes-search is not Schema-published yet, so it - // is covered by the declaration check in package smart. +func TestEmbeddedCatalogShortcutQueryKeepsMergeBaseProperty(t *testing.T) { + // Composite shortcuts whose Execute maps --query → MCP keyword still publish + // property=query at merge-base (flag_name_inference). Publishing keyword via + // ParamDecl breaks schema-compatibility; keep Schema delivery aligned. for _, path := range []string{ "aitable +find-record", "doc +find-doc", "drive +find-file", "mail +find-mail-user", "chat +search-msg", - } { - leaf, err := embeddedSchemaPayload([]string{path}) - if err != nil { - t.Fatalf("%s: %v", path, err) - } - query := schemaMap(leaf["parameters"])["query"] - if query["property"] != "keyword" { - t.Fatalf("%s --query property = %#v, want keyword", path, query["property"]) - } - propProv := schemaMap(query["field_provenance"])["property"] - if got, _ := propProv["source"].(string); got != "native_annotation" { - t.Fatalf("%s --query property source = %#v, want native_annotation", path, propProv) - } - } -} - -func TestEmbeddedCatalogShortcutQueryMapsToKeyword(t *testing.T) { - // Non-smart shortcuts whose Execute maps --query → MCP keyword must publish - // property=keyword via ParamDecl; without it, composite leaves fall back to - // flag_name_inference (property=query). - for _, path := range []string{ "doc +search", "drive +search", "drive +search-docs", @@ -1193,12 +1177,8 @@ func TestEmbeddedCatalogShortcutQueryMapsToKeyword(t *testing.T) { t.Fatalf("%s: %v", path, err) } query := schemaMap(leaf["parameters"])["query"] - if query["property"] != "keyword" { - t.Fatalf("%s --query property = %#v, want keyword", path, query["property"]) - } - propProv := schemaMap(query["field_provenance"])["property"] - if got, _ := propProv["source"].(string); got != "native_annotation" { - t.Fatalf("%s --query property source = %#v, want native_annotation", path, propProv) + if query["property"] != "query" { + t.Fatalf("%s --query property = %#v, want query (merge-base)", path, query["property"]) } } } diff --git a/internal/cli/schema_full_pipeline_external_test.go b/internal/cli/schema_full_pipeline_external_test.go index 3694fdb2..31b46cf7 100644 --- a/internal/cli/schema_full_pipeline_external_test.go +++ b/internal/cli/schema_full_pipeline_external_test.go @@ -4,7 +4,6 @@ package cli_test import ( - "os" "testing" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app" @@ -55,7 +54,7 @@ func TestCrossPlatformCoverageProductionSchemaSourcePipeline(t *testing.T) { if err != nil { t.Fatal(err) } - hints, err := cli.LoadAgentHintsFromSelectionForValidation(os.DirFS("schema_hints/selection")) + hints, err := cli.LoadAgentHintsFromSelectionForValidation(nil) if err != nil { t.Fatal(err) } diff --git a/internal/cli/schema_hint_dirs.go b/internal/cli/schema_hint_dirs.go index 7419fab2..09e69b15 100644 --- a/internal/cli/schema_hint_dirs.go +++ b/internal/cli/schema_hint_dirs.go @@ -59,8 +59,10 @@ type hintDirTool struct { } func loadManualSchemaHintsFromHintDirs(metadataFS fs.FS, metadataGlob string, selectionFS fs.FS, selectionGlob string) (ManualSchemaHintSnapshot, error) { - // Metadata is optional and normally nil/empty after phase 5 deleted - // schema_hints/metadata/. Nil FS or empty glob = no parameter overlays. + // Metadata and selection HintFile dirs are both optional/retired. + // Production passes nil/"" for both; ProductDecl / ContractFinal own + // Agent routing and leaf facts. Nil FS or empty glob = no overlays and + // an empty Agent hint set (synthetic revision only). var commands []ManualSchemaCommandHint if metadataFS != nil && strings.TrimSpace(metadataGlob) != "" { var err error @@ -137,27 +139,38 @@ func loadParameterCommandsFromMetadata(metadataFS fs.FS, globPattern string) ([] } // LoadAgentHintsFromSelectionForValidation loads selection HintFiles from an -// FS rooted at schema_hints/selection (so glob *.json). +// FS rooted at schema_hints/selection (so glob *.json). A nil FS is treated +// as an absent optional/retired directory and returns an empty hint set; +// callers that rely on ProductDecl/ContractFinal should pass nil. func LoadAgentHintsFromSelectionForValidation(selectionFS fs.FS) (ManualAgentHintSet, error) { return loadAgentHintsFromSelection(selectionFS, "*.json") } -func loadAgentHintsFromSelection(selectionFS fs.FS, globPattern string) (ManualAgentHintSet, error) { - files, err := fs.Glob(selectionFS, globPattern) - if err != nil { - return ManualAgentHintSet{}, fmt.Errorf("list selection hints: %w", err) - } - sort.Strings(files) - hints := ManualAgentHintSet{ +func emptySelectionAgentHints() ManualAgentHintSet { + return ManualAgentHintSet{ Revisions: map[string]ManualAgentHintRevision{ selectionRevisionID: { GeneratedBy: "human", - Reason: "reviewed Agent selection prose under internal/cli/schema_hints/selection", + Reason: "schema_hints/selection retired; Agent routing comes from ProductDecl/ContractFinal", }, }, Products: map[string]ManualAgentProductHint{}, Tools: map[string]ManualAgentToolHint{}, } +} + +func loadAgentHintsFromSelection(selectionFS fs.FS, globPattern string) (ManualAgentHintSet, error) { + // Nil FS or empty glob = optional/absent selection directory. Zero matches + // (empty shells already deleted, or empty dir) also yield an empty set. + if selectionFS == nil || strings.TrimSpace(globPattern) == "" { + return emptySelectionAgentHints(), nil + } + files, err := fs.Glob(selectionFS, globPattern) + if err != nil { + return ManualAgentHintSet{}, fmt.Errorf("list selection hints: %w", err) + } + sort.Strings(files) + hints := emptySelectionAgentHints() for _, name := range files { data, err := fs.ReadFile(selectionFS, name) if err != nil { diff --git a/internal/cli/schema_hint_emptiness_contract_test.go b/internal/cli/schema_hint_emptiness_contract_test.go index 36bb17d6..69cc41a5 100644 --- a/internal/cli/schema_hint_emptiness_contract_test.go +++ b/internal/cli/schema_hint_emptiness_contract_test.go @@ -110,6 +110,68 @@ func TestEmbeddedMetadataHintShellsHaveEmptyTools(t *testing.T) { } } +// TestEmbeddedSelectionHintShellsHaveEmptyProductsAndTools locks the +// selection retired end-state: directory may be absent. Production embed +// loading always passes nil/empty. When residual shells are present, +// products{} and tools{} must stay empty under ProductDecl/ContractFinal. +func TestEmbeddedSelectionHintShellsHaveEmptyProductsAndTools(t *testing.T) { + hints, err := loadAgentHintsFromSelection(nil, "") + if err != nil { + t.Fatalf("loadAgentHintsFromSelection(nil, \"\"): %v", err) + } + if len(hints.Products) != 0 || len(hints.Tools) != 0 { + t.Fatalf("nil selection hints products=%d tools=%d, want 0/0", len(hints.Products), len(hints.Tools)) + } + if len(hints.Revisions) == 0 { + t.Fatal("nil selection hints must keep a synthetic revision") + } + + const selectionDir = "schema_hints/selection" + entries, err := os.ReadDir(selectionDir) + if err != nil { + if os.IsNotExist(err) { + return + } + t.Fatalf("read %s: %v", selectionDir, err) + } + if len(entries) == 0 { + return + } + + selectionFS := os.DirFS(selectionDir) + files, err := fs.Glob(selectionFS, "*.json") + if err != nil { + t.Fatalf("list selection shells: %v", err) + } + sort.Strings(files) + + nonEmpty := make([]string, 0) + for _, name := range files { + data, err := fs.ReadFile(selectionFS, name) + if err != nil { + t.Fatalf("read %s: %v", name, err) + } + var file hintDirFile + if err := json.Unmarshal(data, &file); err != nil { + t.Fatalf("decode %s: %v", name, err) + } + if len(file.Products) != 0 || len(file.Tools) != 0 { + nonEmpty = append(nonEmpty, filepath.Base(name)) + } + } + if len(nonEmpty) != 0 { + t.Fatalf("selection shells must keep products:{} tools:{} ; nonempty=%v", nonEmpty) + } + + hints, err = LoadAgentHintsFromSelectionForValidation(selectionFS) + if err != nil { + t.Fatalf("LoadAgentHintsFromSelectionForValidation: %v", err) + } + if len(hints.Products) != 0 || len(hints.Tools) != 0 { + t.Fatalf("on-disk selection hints products=%d tools=%d, want 0/0", len(hints.Products), len(hints.Tools)) + } +} + // TestEmbeddedCatalogHasNoToolSchemaHintProvenance asserts the published // Catalog no longer selects tool_schema_hint as a provenance winner or // retained candidate after RegisterSchemaHints → ParamDecl migration. diff --git a/internal/cli/schema_hints/README.md b/internal/cli/schema_hints/README.md index 1af65e1b..264dd801 100644 --- a/internal/cli/schema_hints/README.md +++ b/internal/cli/schema_hints/README.md @@ -13,18 +13,15 @@ fails if present). Do not hand-edit generated Catalog files. Human-authored inputs: -- `index.json` (`format: dws-agent-hint-index`) — maps product IDs to required - selection files and optional metadata map, plus reference review. - Strategy: `metadata` is optional (omitted or `{}`); the on-disk - `metadata/` directory is retired and may be absent. -- `selection/.json` — Agent selection prose (required) +- `index.json` (`format: dws-agent-hint-index`) — optional maps plus reference + review. Strategy: `selection` and `metadata` are optional (omitted or `{}`); + on-disk `selection/` and `metadata/` directories are retired and may be absent. - `imported/` — sanitized baseline from a fixed external revision When `index.json` is present, the generator loads `imported/` plus any -metadata files listed in `index.metadata` (optional) and the selection files -listed in `index.selection` (required). Sibling review JSON files in this -directory remain CI/audit inputs and are not applied as Agent metadata -sources. +metadata/selection files listed in the optional maps. Sibling review JSON +files in this directory remain CI/audit inputs and are not applied as Agent +metadata sources. For the end-to-end Agent curation workflow, see `AGENTS.md` § “Agent curation workflow (Schema hints)”. @@ -32,17 +29,19 @@ workflow (Schema hints)”. ## Source kinds - `contract_final`: leaf-declared Safety / Schema / ParamDecl - (`DeclareLeafMetadata`, `Shortcut.Schema`). This is the production authority - for safety, interface disposition, and parameter facts. -- `reviewed_explicit`: reviewed selection HintFiles under `selection/` - (`reviewed: true`) for Agent selection prose. Residual metadata tool rows are - not used in production shells. + (`DeclareLeafMetadata`, `Shortcut.Schema`) and product-level + `ProductDecl` routing prose. This is the production authority for safety, + interface disposition, parameter facts, and Agent selection / product + routing. +- `reviewed_explicit`: residual HintFile prose only when a fixture or + temporary selection/metadata shell still exists; production directories are + retired and must not reintroduce tool rows. - `explicit`: explicit but not per-tool-reviewed DWS hints. - `imported`: sanitized metadata from a fixed external revision. It fills missing Agent semantics but cannot redefine command paths or parameter contracts. Skill Markdown and audit JSON in this directory remain authoring evidence. Normal generation does not semantically combine Markdown into the final Agent -prose and never rewrites metadata/selection files. +prose and never rewrites retired metadata/selection directories. The Agent metadata pipeline (invoked in-memory by Catalog generation) also reads the committed `internal/cli/schema_mcp_metadata.json` after Skill and Hint parsing. A sanitized MCP description can fill an otherwise empty `agent_summary`; it is marked `reviewed: false`, retains revision provenance, and cannot infer or override risk/effect fields. diff --git a/internal/cli/schema_hints/index.json b/internal/cli/schema_hints/index.json index 0e2f842f..2ef93905 100644 --- a/internal/cli/schema_hints/index.json +++ b/internal/cli/schema_hints/index.json @@ -8,34 +8,7 @@ "channel": "open-source" }, "metadata": {}, - "selection": { - "aisearch": "selection/aisearch.json", - "aitable": "selection/aitable.json", - "attendance": "selection/attendance.json", - "audit": "selection/audit.json", - "calendar": "selection/calendar.json", - "chat": "selection/chat.json", - "contact": "selection/contact.json", - "dev": "selection/dev.json", - "devapp": "selection/devapp.json", - "devdoc": "selection/devdoc.json", - "ding": "selection/ding.json", - "doc": "selection/doc.json", - "drive": "selection/drive.json", - "event": "selection/event.json", - "hrbrain": "selection/hrbrain.json", - "live": "selection/live.json", - "mail": "selection/mail.json", - "markdown": "selection/markdown.json", - "mcp": "selection/mcp.json", - "minutes": "selection/minutes.json", - "oa": "selection/oa.json", - "pat": "selection/pat.json", - "report": "selection/report.json", - "sheet": "selection/sheet.json", - "todo": "selection/todo.json", - "wiki": "selection/wiki.json" - }, + "selection": {}, "reference_review": { "agoal contract detail": { "status": "out_of_surface", diff --git a/internal/cli/schema_hints/selection/aisearch.json b/internal/cli/schema_hints/selection/aisearch.json deleted file mode 100644 index d187ad08..00000000 --- a/internal/cli/schema_hints/selection/aisearch.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/aisearch", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "aisearch": { - "agent_summary": "企业内智能搜人、搜知识内容与搜行为记录", - "use_when": [ - "语义找人、按主题搜企业知识,或追溯发送/创建/分享等行为" - ], - "avoid_when": [ - "已有明确资源 ID 要读写时改用对应产品;普通 OAuth 登录不用 aisearch" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "skills/mono/references/products/aisearch.md", - "CommandRegistry:product=aisearch", - "Skill:skills/mono/references/products/aisearch.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/aitable.json b/internal/cli/schema_hints/selection/aitable.json deleted file mode 100644 index bf3015c6..00000000 --- a/internal/cli/schema_hints/selection/aitable.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/aitable", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "aitable": { - "agent_summary": "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。", - "use_when": [ - "需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时" - ], - "avoid_when": [ - "目标是在线电子表格单元格读写时用 sheet;普通文档用 doc" - ], - "reviewed": true, - "review_reason": "人工结合实时 MCP/Skill/Cobra 审阅产品级选择边界。", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "skills/mono/references/products/aitable.md", - "dws-schema-live:product-index" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/attendance.json b/internal/cli/schema_hints/selection/attendance.json deleted file mode 100644 index 71050edf..00000000 --- a/internal/cli/schema_hints/selection/attendance.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/attendance", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "attendance": { - "agent_summary": "查询考勤记录、排班、班次、考勤组、审批、报表、个人规则和假期,并执行经确认的考勤配置变更。", - "use_when": [ - "用户要查询或管理钉钉考勤数据、规则、排班、考勤组、审批表单或假期余额。" - ], - "avoid_when": [ - "用户只需处理普通日历日程、非考勤类活动签到或通用审批流状态,而不是钉钉考勤业务。" - ], - "reviewed": true, - "review_reason": "依据 reviewed CommandRegistry、真实 Cobra help 与 mono 产品参考,审阅并固化 attendance 产品的 Agent 选择语义。", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "cobra-help:dws attendance --help", - "skills/mono/references/products/attendance.md" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/audit.json b/internal/cli/schema_hints/selection/audit.json deleted file mode 100644 index 14220732..00000000 --- a/internal/cli/schema_hints/selection/audit.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/audit", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "audit": { - "agent_summary": "查看、导出和校验本地操作审计日志", - "use_when": [ - "需要排查本机 CLI 操作审计记录,或验证审计文件完整性" - ], - "avoid_when": [ - "查钉钉业务数据或发消息请用对应产品命令,不要用 audit" - ], - "reviewed": true, - "review_reason": "产品级路由:本地审计日志管理,不涉及 MCP。", - "source_refs": [ - "internal/cli/schema_command_registry.json#audit", - "cobra-help:dws audit --help", - "CommandRegistry:product=audit" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/calendar.json b/internal/cli/schema_hints/selection/calendar.json deleted file mode 100644 index 766ddd62..00000000 --- a/internal/cli/schema_hints/selection/calendar.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/calendar", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "calendar": { - "agent_summary": "管理日历本、日程、参与人、附件、会议室,并查询人员或会议室闲忙状态。", - "use_when": [ - "需要查询或管理钉钉日历、日程、参与人、会议室和闲忙信息时。" - ], - "avoid_when": [ - "目标是聊天消息、待办或审批,而不是日历与日程资源时。" - ], - "reviewed": true, - "review_reason": "AI 语义审阅:依据 Registry 主路径、Cobra help 与 Calendar 参考文档明确命令选择边界并校验示例。", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "skills/mono/references/products/calendar.md" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/chat.json b/internal/cli/schema_hints/selection/chat.json deleted file mode 100644 index 6ae81a27..00000000 --- a/internal/cli/schema_hints/selection/chat.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/chat", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "chat": { - "agent_summary": "管理钉钉会话、群聊、群成员、机器人、消息检索与发送", - "use_when": [ - "请求涉及群聊管理、聊天记录、消息发送、会话设置或群机器人" - ], - "avoid_when": [ - "邮件用 mail;开放平台应用/机器人建号发布用 dev;企业语义找人优先 aisearch person" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "skills/mono/references/products/chat.md", - "CommandRegistry:product=chat", - "Skill:skills/mono/references/products/chat.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/contact.json b/internal/cli/schema_hints/selection/contact.json deleted file mode 100644 index 2017460a..00000000 --- a/internal/cli/schema_hints/selection/contact.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/contact", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "contact": { - "agent_summary": "查询通讯录与花名册,并管理企业、部门、员工及企业账号", - "use_when": [ - "按姓名/手机号/userId/部门条件做通讯录精确查询,或明确执行企业与员工入企管理" - ], - "avoid_when": [ - "职责/上级等语义找人优先 aisearch person;不要用 contact 发消息;写操作前确认当前企业和目标信息" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "CommandRegistry:product=contact", - "Skill:skills/mono/references/products/contact.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/dev.json b/internal/cli/schema_hints/selection/dev.json deleted file mode 100644 index 6e920a9f..00000000 --- a/internal/cli/schema_hints/selection/dev.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/dev", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "dev": { - "agent_summary": "管理开放平台应用、权限、机器人、版本发布与本地连接器", - "use_when": [ - "创建/配置开放平台应用、机器人、权限、事件订阅或发布版本" - ], - "avoid_when": [ - "只查开放平台文档用 devdoc;业务聊天/邮信用 chat/mail" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "skills/mono/references/products/dev.md", - "CommandRegistry:product=dev", - "Skill:skills/mono/references/products/dev.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/devapp.json b/internal/cli/schema_hints/selection/devapp.json deleted file mode 100644 index 91c3cd8a..00000000 --- a/internal/cli/schema_hints/selection/devapp.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/devapp", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "devapp": { - "agent_summary": "管理钉钉开放平台企业内部应用、成员、权限、机器人、事件与版本", - "use_when": [ - "请求涉及企业内部应用的查询、创建、配置、成员权限、机器人、事件订阅或版本管理" - ], - "avoid_when": [ - "开放平台接口文档搜索使用 devdoc;普通钉钉业务数据使用对应产品命令" - ], - "reviewed": true, - "review_reason": "Agent-authored and reviewed from the public devapp Shortcut surface; product routing does not alter command identity, parameters, or runtime safety.", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "internal/shortcut/devapp", - "CommandRegistry:product=devapp" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/devdoc.json b/internal/cli/schema_hints/selection/devdoc.json deleted file mode 100644 index e42c8210..00000000 --- a/internal/cli/schema_hints/selection/devdoc.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/devdoc", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "devdoc": { - "agent_summary": "搜索钉钉开放平台开发文档与错误排查资料", - "use_when": [ - "查询 OpenAPI、字段、错误码、SDK、鉴权或接入指南" - ], - "avoid_when": [ - "搜索用户业务文档或读写业务数据时不要用 devdoc" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "CommandRegistry:product=devdoc", - "Skill:skills/mono/references/products/devdoc.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/ding.json b/internal/cli/schema_hints/selection/ding.json deleted file mode 100644 index fa5a2495..00000000 --- a/internal/cli/schema_hints/selection/ding.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/ding", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "ding": { - "agent_summary": "以企业机器人发送或撤回应用内/短信/电话 DING", - "use_when": [ - "需要机器人身份发送或撤回 DING" - ], - "avoid_when": [ - "普通聊天消息用 chat;用户身份 DING 不要走机器人命令" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "CommandRegistry:product=ding", - "Skill:skills/mono/references/products/ding.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/doc.json b/internal/cli/schema_hints/selection/doc.json deleted file mode 100644 index 0262a2be..00000000 --- a/internal/cli/schema_hints/selection/doc.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/doc", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "doc": { - "agent_summary": "管理钉钉在线文档的正文、块、评论、导入导出、模板与版本", - "use_when": [ - "创建、读取或编辑在线文档内容,或处理文档块、评论、导入导出、模板和版本时" - ], - "avoid_when": [ - "文件、目录、上传下载及节点权限已迁移到 drive;知识库空间和成员使用 wiki;不要用于搜索开放平台开发文档" - ], - "reviewed": true, - "review_reason": "依据真实 CommandRegistry 命令分区与对应产品 Skill 审阅产品级选择边界。", - "source_refs": [ - "CommandRegistry:product=doc", - "Skill:skills/mono/references/products/doc.md" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/drive.json b/internal/cli/schema_hints/selection/drive.json deleted file mode 100644 index 28b6053d..00000000 --- a/internal/cli/schema_hints/selection/drive.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/drive", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "drive": { - "agent_summary": "管理钉盘及文档空间中的文件、目录、上传下载、回收站与公开发布", - "use_when": [ - "浏览、搜索、上传、下载或整理钉盘和文档空间文件时" - ], - "avoid_when": [ - "需要读取或编辑在线文档正文时使用 doc;需要管理知识库空间或成员时使用 wiki" - ], - "reviewed": true, - "review_reason": "依据真实 CommandRegistry 命令分区与对应产品 Skill 审阅产品级选择边界。", - "source_refs": [ - "CommandRegistry:product=drive", - "Skill:skills/mono/references/products/drive.md" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/event.json b/internal/cli/schema_hints/selection/event.json deleted file mode 100644 index 2014d5c4..00000000 --- a/internal/cli/schema_hints/selection/event.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/event", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "event": { - "agent_summary": "订阅/消费个人消息、动作与群生命周期事件,并管理订阅生命周期", - "use_when": [ - "需要实时监听个人消息接收、全量消息、已读、撤回、表情回应或群生命周期事件,或管理个人事件订阅生命周期" - ], - "avoid_when": [ - "查历史聊天或主动发消息分别用 chat 查询/发送命令" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "internal/cli/schema_command_registry.json#event", - "cobra-help:dws event --help", - "skills/mono/SKILL.md", - "skills/mono/references/products/event.md", - "CommandRegistry:product=event", - "Skill:skills/mono/references/products/event.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/hrbrain.json b/internal/cli/schema_hints/selection/hrbrain.json deleted file mode 100644 index d40475cb..00000000 --- a/internal/cli/schema_hints/selection/hrbrain.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/hrbrain", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "hrbrain": { - "agent_summary": "钉钉组织大脑:人才池管理、员工档案查询与人才搜索", - "use_when": [ - "需要查询人才池、员工档案(元数据/标签/职业历程/绩效)或搜索员工时" - ], - "avoid_when": [ - "要操作通讯录/组织架构基础信息时改用 contact 产品", - "要提交/查询战略解码、经营合约、目标等 OKR 能力时改用 agoal(CLI-only,未接入 Agent Schema)" - ], - "reviewed": true, - "review_reason": "依据真实 CommandRegistry 命令分区与对应产品 Skill 审阅产品级选择边界。", - "source_refs": [ - "CommandRegistry:product=hrbrain", - "Skill:skills/mono/references/products/hrbrain.md", - "Skill:skills/multi/dingtalk-hrbrain/SKILL.md" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/live.json b/internal/cli/schema_hints/selection/live.json deleted file mode 100644 index bcc7a065..00000000 --- a/internal/cli/schema_hints/selection/live.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/live", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "live": { - "agent_summary": "查询当前用户发起的直播列表", - "use_when": [ - "用户要查看自己的直播列表或基础统计" - ], - "avoid_when": [ - "当前公开面不支持创建/控制直播" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "CommandRegistry:product=live", - "Skill:skills/multi/dingtalk-live/SKILL.md", - "Skill:skills/mono/references/products/live.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/mail.json b/internal/cli/schema_hints/selection/mail.json deleted file mode 100644 index 7a6527be..00000000 --- a/internal/cli/schema_hints/selection/mail.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/mail", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "mail": { - "agent_summary": "管理邮箱、邮件、草稿、附件、文件夹、联系人与邮件模板", - "use_when": [ - "查收、搜索、阅读、回复、发送或整理邮件" - ], - "avoid_when": [ - "即时消息用 chat;开放平台应用配置用 dev", - "公开 catalog 不含 thread trash / sent-message recall 等硬 --yes 命令时不要臆造路径" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "skills/mono/references/products/mail.md", - "CommandRegistry:product=mail", - "Skill:skills/mono/references/products/mail.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/markdown.json b/internal/cli/schema_hints/selection/markdown.json deleted file mode 100644 index 6470d298..00000000 --- a/internal/cli/schema_hints/selection/markdown.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/markdown", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "markdown": { - "agent_summary": "跨钉盘与文档空间创建、获取、覆盖和局部修补原生 Markdown 文件", - "use_when": [ - "目标是原生 .md 文件,并需要在 Drive/Doc 路由间安全处理内容时" - ], - "avoid_when": [ - "在线文档正文操作使用 doc;普通二进制文件上传下载使用 drive" - ], - "reviewed": true, - "review_reason": "依据新增公开 Markdown 命令树及其跨产品路由审阅产品级选择边界。", - "source_refs": [ - "CommandRegistry:product=markdown", - "Wukong-parity:3306c3307" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/mcp.json b/internal/cli/schema_hints/selection/mcp.json deleted file mode 100644 index f79d3e4c..00000000 --- a/internal/cli/schema_hints/selection/mcp.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/mcp", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "mcp": { - "agent_summary": "解析和管理当前身份可用的 MCP 服务连接信息", - "use_when": [ - "需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端" - ], - "avoid_when": [ - "查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp" - ], - "reviewed": true, - "review_reason": "为公开 mcp 命令提供产品级 Agent 路由,并强调连接信息属于敏感凭据。", - "source_refs": [ - "internal/cli/schema_command_registry.json#mcp", - "cobra-help:dws mcp --help", - "CommandRegistry:product=mcp" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/minutes.json b/internal/cli/schema_hints/selection/minutes.json deleted file mode 100644 index a0617568..00000000 --- a/internal/cli/schema_hints/selection/minutes.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/minutes", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "minutes": { - "agent_summary": "查询和维护钉钉听记的转写、摘要、待办、权限、录音、标签、说话人总结及文件上传会话。", - "use_when": [ - "用户要查找、读取、编辑或管理钉钉听记及其录音、转写、摘要和衍生内容。" - ], - "avoid_when": [ - "用户要处理普通文档正文、群聊消息或日历会议安排,而不是钉钉听记。" - ], - "reviewed": true, - "review_reason": "依据 reviewed CommandRegistry、真实 Cobra help 与 mono 产品参考,审阅并固化 minutes 产品的 Agent 选择语义。", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "cobra-help:dws minutes --help", - "skills/mono/references/products/minutes.md" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/oa.json b/internal/cli/schema_hints/selection/oa.json deleted file mode 100644 index 78fb08ee..00000000 --- a/internal/cli/schema_hints/selection/oa.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/oa", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "oa": { - "agent_summary": "查询和处理 OA 审批实例、任务、记录、抄送与评论", - "use_when": [ - "查看待审、已办、已发起或抄送审批,并执行同意、拒绝、撤销、转交等审批动作时" - ], - "avoid_when": [ - "不要用于普通待办任务或工作日志;需要创建审批实例时先确认当前命令面是否支持" - ], - "reviewed": true, - "review_reason": "依据真实 CommandRegistry 命令分区与对应产品 Skill 审阅产品级选择边界。", - "source_refs": [ - "CommandRegistry:product=oa", - "Skill:skills/mono/references/products/oa.md" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/pat.json b/internal/cli/schema_hints/selection/pat.json deleted file mode 100644 index 85a36ee9..00000000 --- a/internal/cli/schema_hints/selection/pat.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/pat", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "pat": { - "agent_summary": "管理 Agent 的 PAT 行为授权与本地浏览器策略", - "use_when": [ - "缺少行为授权需要预览/授予 scope,或配置授权是否打开浏览器" - ], - "avoid_when": [ - "普通登录用 auth;不要与开放平台应用权限或具体业务写操作混用" - ], - "reviewed": true, - "review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。", - "source_refs": [ - "CommandRegistry:product=pat", - "Skill:skills/mono/references/products/pat.md", - "live-dws-schema:product-batch-2026-07-14" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/report.json b/internal/cli/schema_hints/selection/report.json deleted file mode 100644 index dc95fb73..00000000 --- a/internal/cli/schema_hints/selection/report.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/report", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "report": { - "agent_summary": "查询日志模板、收发日志、日志正文与统计,并按模板提交日志", - "use_when": [ - "查看或提交日报、周报等钉钉日志时" - ], - "avoid_when": [ - "不要用于待办任务、OA 审批或在线文档正文编辑" - ], - "reviewed": true, - "review_reason": "依据真实 CommandRegistry 命令分区与对应产品 Skill 审阅产品级选择边界。", - "source_refs": [ - "CommandRegistry:product=report", - "Skill:skills/mono/references/products/report.md" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/sheet.json b/internal/cli/schema_hints/selection/sheet.json deleted file mode 100644 index c8041485..00000000 --- a/internal/cli/schema_hints/selection/sheet.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/sheet", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "sheet": { - "agent_summary": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", - "use_when": [ - "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", - "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" - ], - "avoid_when": [ - "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", - "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" - ], - "reviewed": true, - "review_reason": "人工结合实时 MCP/Skill/Cobra 审阅产品级选择边界。", - "source_refs": [ - "internal/cli/schema_command_registry.json", - "cobra-help:dws sheet --help", - "skills/mono/references/products/sheet.md", - "dws-schema-live:product-index" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/todo.json b/internal/cli/schema_hints/selection/todo.json deleted file mode 100644 index 68255c71..00000000 --- a/internal/cli/schema_hints/selection/todo.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/todo", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "todo": { - "agent_summary": "管理待办任务、标签、子任务、执行人、参与人、评论、附件与提醒", - "use_when": [ - "查询、创建或更新个人待办及其协作信息时" - ], - "avoid_when": [ - "不要用于 OA 审批流转、工作日志提交或日历日程管理" - ], - "reviewed": true, - "review_reason": "依据真实 CommandRegistry 命令分区与对应产品 Skill 审阅产品级选择边界。", - "source_refs": [ - "CommandRegistry:product=todo", - "Skill:skills/mono/references/products/todo.md" - ] - } - } -} diff --git a/internal/cli/schema_hints/selection/wiki.json b/internal/cli/schema_hints/selection/wiki.json deleted file mode 100644 index 419f36cb..00000000 --- a/internal/cli/schema_hints/selection/wiki.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "source": { - "kind": "explicit", - "name": "dws-agent-selection/wiki", - "repository": "DingTalk-Real-AI/dingtalk-workspace-cli", - "channel": "open-source", - "reviewed": true - }, - "tools": {}, - "products": { - "wiki": { - "agent_summary": "管理钉钉知识库空间、节点与成员权限", - "use_when": [ - "查找或管理知识库、知识库内节点及知识库成员时" - ], - "avoid_when": [ - "需要编辑在线文档正文时使用 doc;只管理钉盘普通文件时使用 drive" - ], - "reviewed": true, - "review_reason": "依据真实 CommandRegistry 命令分区与对应产品 Skill 审阅产品级选择边界。", - "source_refs": [ - "CommandRegistry:product=wiki", - "Skill:skills/mono/references/products/wiki.md" - ] - } - } -} diff --git a/internal/cli/schema_manual_hints.go b/internal/cli/schema_manual_hints.go index c95d44e0..9ab4bada 100644 --- a/internal/cli/schema_manual_hints.go +++ b/internal/cli/schema_manual_hints.go @@ -15,7 +15,6 @@ package cli import ( "bytes" - "embed" "encoding/json" "fmt" "io" @@ -37,11 +36,6 @@ const ( runtimeSchemaManualReasonAnnotation = "dws.schema.manual.reason" ) -//go:embed schema_hints/selection/*.json -var embeddedSelectionFS embed.FS - -const embeddedSelectionGlob = "schema_hints/selection/*.json" - // ManualSchemaHintSnapshot is the human-owned bridge from an existing // public Cobra leaf to Schema. It cannot create commands, flags, exclusions, or // interfaces: every referenced command and flag is checked against the live @@ -214,10 +208,9 @@ var ( // ApplyEmbeddedManualSchemaHints loads committed hint dirs and applies any // residual metadata parameter overlays to an already-built Cobra tree. -// Selection prose is loaded (and validated) as part of the same snapshot for -// generators/tests; this function only annotates Commands. Metadata overlays -// are optional: empty tools shells produce an empty Commands list. The -// operation is deterministic and idempotent. +// Selection HintFiles are retired in production (nil/empty load); Agent routing +// comes from ProductDecl/ContractFinal. This function only annotates Commands. +// The operation is deterministic and idempotent. func ApplyEmbeddedManualSchemaHints(root *cobra.Command) (ManualSchemaHintReport, error) { snapshot, err := loadManualSchemaHints() if err != nil { @@ -228,12 +221,12 @@ func ApplyEmbeddedManualSchemaHints(root *cobra.Command) (ManualSchemaHintReport func embeddedManualSchemaHints() (ManualSchemaHintSnapshot, error) { manualSchemaHintsOnce.Do(func() { - // Selection FS/glob are required. Metadata overlays are retired: - // pass nil/empty so loadManualSchemaHintsFromHintDirs yields no - // parameter Commands (schema_hints/metadata/ may be absent). + // Metadata and selection HintFile dirs are both retired (nil/empty). + // ProductDecl / ContractFinal own Agent routing prose; residual + // schema_hints/selection/ must not reappear as an embed source. manualSchemaHintsSnapshot, manualSchemaHintsErr = loadManualSchemaHintsFromHintDirs( nil, "", - embeddedSelectionFS, embeddedSelectionGlob, + nil, "", ) }) return manualSchemaHintsSnapshot, manualSchemaHintsErr @@ -340,7 +333,18 @@ func ValidateManualAgentHintSet(hints ManualAgentHintSet, expectedProducts, expe } } - if err := validateManualAgentHintExactSet("products", expectedProducts, mapKeysManualAgentProducts(hints.Products)); err != nil { + actualProducts := mapKeysManualAgentProducts(hints.Products) + if expectedProducts != nil { + // Declared products may keep a selection/ products{} row during + // migration; count those leftovers as covered so exact-set passes. + expectedProducts = cloneManualAgentHintBoolSet(expectedProducts) + for productID := range actualProducts { + if HasProductDecl(productID) { + expectedProducts[productID] = true + } + } + } + if err := validateManualAgentHintExactSet("products", expectedProducts, actualProducts); err != nil { return err } if err := validateManualAgentHintExactSet("tools", expectedTools, mapKeysManualAgentTools(hints.Tools)); err != nil { @@ -349,6 +353,17 @@ func ValidateManualAgentHintSet(hints ManualAgentHintSet, expectedProducts, expe return nil } +func cloneManualAgentHintBoolSet(values map[string]bool) map[string]bool { + if values == nil { + return nil + } + out := make(map[string]bool, len(values)) + for key, include := range values { + out[key] = include + } + return out +} + func validateManualAgentExampleDispositions(canonical string, examples []string, dispositions []ManualAgentExampleDisposition) error { seen := make(map[int]bool, len(dispositions)) for _, disposition := range dispositions { diff --git a/internal/cli/schema_manual_hints_test.go b/internal/cli/schema_manual_hints_test.go index 0fed5029..253d6202 100644 --- a/internal/cli/schema_manual_hints_test.go +++ b/internal/cli/schema_manual_hints_test.go @@ -262,6 +262,24 @@ func TestDecodeManualSchemaHintsRequiresAgentHints(t *testing.T) { } } +func TestValidateManualAgentHintSetAllowsLingeringProductDeclRows(t *testing.T) { + t.Cleanup(func() { ClearProductDeclForTest("sample") }) + RegisterProductDecl(ProductDecl{ + ID: "sample", + Selection: ProductSelectionDecl{ + AgentSummary: "Manage samples", + UseWhen: []string{"target is a sample"}, + AvoidWhen: []string{"target is another product"}, + }, + }) + hints := manualAgentHintSetFixture() + // Declared product may still appear in selection products{} during + // migration; with empty expected products it must not fail as unexpected. + if err := ValidateManualAgentHintSet(hints, map[string]bool{}, map[string]bool{"sample.search_items": true}); err != nil { + t.Fatalf("ValidateManualAgentHintSet() error = %v", err) + } +} + func TestValidateManualAgentHintSetRequiresReviewedExactCoverage(t *testing.T) { hints := manualAgentHintSetFixture() if err := ValidateManualAgentHintSet(hints, map[string]bool{"sample": true}, map[string]bool{"sample.search_items": true}); err != nil { diff --git a/internal/cli/schema_runtime_registry.go b/internal/cli/schema_runtime_registry.go index e657140e..a58c6300 100644 --- a/internal/cli/schema_runtime_registry.go +++ b/internal/cli/schema_runtime_registry.go @@ -144,7 +144,16 @@ func assembleSchemaRegistryFromBound(bound BoundCommandRegistry, metadata runtim } product := byProduct[entry.ProductID] if product == nil { - selection, provenance, _ := agentProductContractForIDsFromMetadata(metadata.Agent, entry.ProductID, entry.SourceProductID) + var selection SelectionSpec + var provenance map[string]FieldProvenance + // ProductDecl is the final product routing source (contract_final), + // symmetric to leaf RuntimeContractFinal. Selection JSON remains a + // fallback only when no Decl is registered. + if decl, ok := LookupProductDecl(entry.ProductID); ok { + selection, provenance = ProductSelectionFromDecl(decl) + } else { + selection, provenance, _ = agentProductContractForIDsFromMetadata(metadata.Agent, entry.ProductID, entry.SourceProductID) + } product = &ProductSpec{ ID: entry.ProductID, Name: entry.ProductName, diff --git a/internal/generator/agentmetadata/contract_final.go b/internal/generator/agentmetadata/contract_final.go index 8e9a5994..3ea12e99 100644 --- a/internal/generator/agentmetadata/contract_final.go +++ b/internal/generator/agentmetadata/contract_final.go @@ -29,9 +29,16 @@ import ( // field it carries. Non-declared tools are untouched. // // Runs after hint reconciliation so rank ordering decides the winner; the -// contract rank outranks every file/manual source. +// contract rank outranks every file/manual source. Product-level ProductDecl +// routing is applied in the same pass. func applyContractFinalDeclarations(file *File, opts Options) error { - if file == nil || len(opts.BoundCommands.ByCanonical) == 0 { + if file == nil { + return nil + } + if err := applyContractFinalProductDeclarations(file, opts); err != nil { + return err + } + if len(opts.BoundCommands.ByCanonical) == 0 { return nil } canonicals := make([]string, 0, len(opts.BoundCommands.ByCanonical)) @@ -61,6 +68,84 @@ func applyContractFinalDeclarations(file *File, opts Options) error { return nil } +// applyContractFinalProductDeclarations merges each RegisterProductDecl into +// the matching product record as the top-precedence (contract_final) +// candidate. Declared products no longer require a selection/ products{} row. +func applyContractFinalProductDeclarations(file *File, opts Options) error { + if file == nil { + return nil + } + if file.Products == nil { + file.Products = map[string]ProductMetadata{} + } + for _, productID := range cli.RegisteredProductDeclIDs() { + if len(opts.ProductIDs) > 0 && !opts.ProductIDs[productID] { + continue + } + decl, ok := cli.LookupProductDecl(productID) + if !ok { + continue + } + incoming := contractFinalProductMetadata(decl) + existing := file.Products[productID] + if err := mergeRankedStringValue( + &existing.AgentSummary, &existing.agentSummaryPresent, &existing.agentSummaryRank, &existing.agentSummaryOrigin, + incoming.AgentSummary, incoming.agentSummaryPresent, selectionRankContractFinal, productDeclOrigin, productID, "agent_summary", + ); err != nil { + return err + } + if incoming.agentSummaryPresent && existing.agentSummaryOrigin == productDeclOrigin { + existing.AgentSummarySource = incoming.AgentSummarySource + } + recordProductListCandidate(&existing, "use_when", incoming.UseWhen, incoming.useWhenPresent, selectionRankContractFinal, productDeclOrigin) + if err := mergeRankedStringList( + &existing.UseWhen, &existing.useWhenPresent, &existing.useWhenRank, &existing.useWhenOrigin, + incoming.UseWhen, incoming.useWhenPresent, selectionRankContractFinal, productDeclOrigin, productID, "use_when", + ); err != nil { + return err + } + recordProductListCandidate(&existing, "avoid_when", incoming.AvoidWhen, incoming.avoidWhenPresent, selectionRankContractFinal, productDeclOrigin) + if err := mergeRankedStringList( + &existing.AvoidWhen, &existing.avoidWhenPresent, &existing.avoidWhenRank, &existing.avoidWhenOrigin, + incoming.AvoidWhen, incoming.avoidWhenPresent, selectionRankContractFinal, productDeclOrigin, productID, "avoid_when", + ); err != nil { + return err + } + existing.SourceRefs = append(existing.SourceRefs, incoming.SourceRefs...) + file.Products[productID] = existing + } + return nil +} + +// productDeclOrigin labels ProductDecl candidates in Agent-metadata provenance. +const productDeclOrigin = cli.ProductDeclProvenanceSource + +func contractFinalProductMetadata(decl cli.ProductDecl) ProductMetadata { + metadata := ProductMetadata{ + AgentSummarySource: cli.ProductDeclSourceRef, + SourceRefs: []string{cli.ProductDeclSourceRef}, + } + if summary := strings.TrimSpace(decl.Selection.AgentSummary); summary != "" { + metadata.AgentSummary = summary + metadata.agentSummaryPresent = true + metadata.agentSummaryRank = selectionRankContractFinal + metadata.agentSummaryOrigin = productDeclOrigin + } + if decl.Selection.UseWhen != nil { + metadata.UseWhen = append([]string(nil), decl.Selection.UseWhen...) + metadata.useWhenPresent = true + metadata.useWhenRank = selectionRankContractFinal + metadata.useWhenOrigin = productDeclOrigin + } + if decl.Selection.AvoidWhen != nil { + metadata.AvoidWhen = append([]string(nil), decl.Selection.AvoidWhen...) + metadata.avoidWhenPresent = true + metadata.avoidWhenRank = selectionRankContractFinal + metadata.avoidWhenOrigin = productDeclOrigin + } + return metadata +} + // contractFinalToolMetadata maps a registered Contract final overlay to a // ToolMetadata candidate whose fields carry the contract_final rank/origin. // Absent payload sections stay absent (never authored-empty); declared diff --git a/internal/generator/agentmetadata/contract_final_product_test.go b/internal/generator/agentmetadata/contract_final_product_test.go new file mode 100644 index 00000000..7a4a6ec6 --- /dev/null +++ b/internal/generator/agentmetadata/contract_final_product_test.go @@ -0,0 +1,87 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); + +package agentmetadata + +import ( + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" +) + +func TestApplyContractFinalProductDeclarations(t *testing.T) { + t.Cleanup(func() { cli.ClearProductDeclForTest("sample") }) + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "sample", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "Manage samples", + UseWhen: []string{"target is a sample"}, + AvoidWhen: []string{"target is another product"}, + }, + }) + + file := &File{Products: map[string]ProductMetadata{}} + if err := applyContractFinalProductDeclarations(file, Options{ + ProductIDs: map[string]bool{"sample": true}, + }); err != nil { + t.Fatalf("applyContractFinalProductDeclarations: %v", err) + } + metadata, ok := file.Products["sample"] + if !ok { + t.Fatal("missing product after ProductDecl apply") + } + if metadata.AgentSummary != "Manage samples" || metadata.agentSummaryRank != selectionRankContractFinal { + t.Fatalf("agent_summary = %#v rank=%d", metadata.AgentSummary, metadata.agentSummaryRank) + } + if metadata.useWhenRank != selectionRankContractFinal || metadata.avoidWhenRank != selectionRankContractFinal { + t.Fatalf("list ranks use=%d avoid=%d", metadata.useWhenRank, metadata.avoidWhenRank) + } + if metadata.AgentSummarySource != cli.ProductDeclSourceRef { + t.Fatalf("AgentSummarySource = %q", metadata.AgentSummarySource) + } + normalizeFile(file, 0) + metadata = file.Products["sample"] + for _, field := range []string{"agent_summary", "use_when", "avoid_when"} { + prov, ok := metadata.FieldProvenance[field] + if !ok || prov.Precedence != selectionPrecedenceContractFinal || prov.Source != productDeclOrigin { + t.Fatalf("%s provenance = %#v", field, prov) + } + } + + // Surface filter: Decl outside ProductIDs must not materialize. + file = &File{Products: map[string]ProductMetadata{}} + if err := applyContractFinalProductDeclarations(file, Options{ + ProductIDs: map[string]bool{"other": true}, + }); err != nil { + t.Fatalf("filtered apply: %v", err) + } + if _, ok := file.Products["sample"]; ok { + t.Fatal("ProductDecl outside surface must not be applied") + } + + if err := applyContractFinalProductDeclarations(nil, Options{}); err != nil { + t.Fatalf("nil file: %v", err) + } +} + +func TestApplyContractFinalDeclarationsIncludesProducts(t *testing.T) { + t.Cleanup(func() { cli.ClearProductDeclForTest("wired") }) + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "wired", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "Wired product", + UseWhen: []string{"use wired"}, + AvoidWhen: []string{"avoid wired"}, + }, + }) + file := &File{Products: map[string]ProductMetadata{}, Tools: map[string]ToolMetadata{}} + if err := applyContractFinalDeclarations(file, Options{ProductIDs: map[string]bool{"wired": true}}); err != nil { + t.Fatalf("applyContractFinalDeclarations: %v", err) + } + if summary := file.Products["wired"].AgentSummary; summary != "Wired product" { + t.Fatalf("wired summary = %q", summary) + } + if err := applyContractFinalDeclarations(nil, Options{}); err != nil { + t.Fatalf("nil file: %v", err) + } +} diff --git a/internal/generator/agentmetadata/hints.go b/internal/generator/agentmetadata/hints.go index 3025aec3..5b91030b 100644 --- a/internal/generator/agentmetadata/hints.go +++ b/internal/generator/agentmetadata/hints.go @@ -38,16 +38,18 @@ type HintFile struct { // HintIndex is the human-authored entrypoint. It maps product IDs to optional // metadata HintFile paths (legacy safety/interface/parameters/runtime_gate -// overlays; may be omitted once leaf facts live on Contract) and required -// selection (Agent routing prose) HintFile paths. Index-level runtime_gates -// were removed; residual gates live on each metadata tool as runtime_gate. +// overlays; may be omitted once leaf facts live on Contract) and optional +// selection (Agent routing prose) HintFile paths. Selection may be omitted +// or {} once ProductDecl/ContractFinal cover routing. Index-level +// runtime_gates were removed; residual gates live on each metadata tool as +// runtime_gate. type HintIndex struct { Version int `json:"version"` Format string `json:"format"` Source HintSource `json:"source"` Coverage HintCoverage `json:"coverage,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` - Selection map[string]string `json:"selection"` + Selection map[string]string `json:"selection,omitempty"` ReferenceReview map[string]ReferenceReview `json:"reference_review,omitempty"` } @@ -222,21 +224,21 @@ func parseHintSources(out *File, files []sourceFile, opts Options, stats *Stats, } return nil } - // Metadata is optional: Contract/Safety owns leaf safety and params. - // An omitted or empty map is a valid migration end-state for phase 5 - // (directory deletion). Selection remains required. - if len(index.Selection) == 0 { - return false, fmt.Errorf("decode Agent hint index %s: selection map is required", indexFile.display) - } + // Metadata and selection maps are both optional end-states once + // ProductDecl / ContractFinal own routing and leaf facts. Omitted + // or empty maps pave directory deletion; mapped files still load + // when present (including empty products{}/tools{} shells). if len(index.Metadata) > 0 { if err := appendMapped("metadata", index.Metadata, hintRoleMetadata); err != nil { return false, err } } - if err := appendMapped("selection", index.Selection, hintRoleSelection); err != nil { - return false, err + if len(index.Selection) > 0 { + if err := appendMapped("selection", index.Selection, hintRoleSelection); err != nil { + return false, err + } + usedSelection = true } - usedSelection = true importedRoot := filepath.Join(hintsRoot, "imported") importedPrefix := importedRoot + string(filepath.Separator) imported := []sourceFile{} diff --git a/internal/generator/agentmetadata/hints_coverage_test.go b/internal/generator/agentmetadata/hints_coverage_test.go index 5ea055de..a7048ea0 100644 --- a/internal/generator/agentmetadata/hints_coverage_test.go +++ b/internal/generator/agentmetadata/hints_coverage_test.go @@ -51,10 +51,12 @@ func TestCrossPlatformCoverageHintUnmarshalAndIndexValidationEdges(t *testing.T) {"version", `{"version":2}`, nil, "unsupported version"}, {"format", `{"version":1,"format":"other"}`, nil, "unsupported format"}, {"source kind", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"imported"}}`, nil, "unsupported source kind"}, - {"selection required", `{"version":1,"format":"dws-agent-hint-index","metadata":{"sample":"metadata.json"}}`, nil, "selection map is required"}, {"empty metadata path", validCoverageIndex("", "selection.json"), map[string]string{"selection.json": validHint}, "missing path"}, {"escaping metadata path", validCoverageIndex("../outside.json", "selection.json"), map[string]string{"selection.json": validHint}, "escapes hints root"}, {"missing metadata file", validCoverageIndex("metadata.json", "selection.json"), map[string]string{"selection.json": validHint}, "file missing"}, + {"empty selection path", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"selection":{"sample":""}}`, nil, "missing path"}, + {"escaping selection path", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"selection":{"sample":"../outside.json"}}`, nil, "escapes hints root"}, + {"missing selection file", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"selection":{"sample":"selection.json"}}`, nil, "file missing"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { @@ -69,17 +71,27 @@ func TestCrossPlatformCoverageHintUnmarshalAndIndexValidationEdges(t *testing.T) }) } - // Metadata map may be omitted or empty; selection alone is enough. + // Metadata and selection maps may each be omitted or empty. for _, tc := range []struct { name string index string + files map[string]string + used bool }{ - {"metadata omitted", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"selection":{"sample":"selection.json"}}`}, - {"metadata empty", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"metadata":{},"selection":{"sample":"selection.json"}}`}, + {"metadata omitted", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"selection":{"sample":"selection.json"}}`, map[string]string{"selection.json": validHint}, true}, + {"metadata empty", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"metadata":{},"selection":{"sample":"selection.json"}}`, map[string]string{"selection.json": validHint}, true}, + {"selection omitted", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"metadata":{}}`, nil, false}, + {"selection empty", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"selection":{}}`, nil, false}, + {"both empty", `{"version":1,"format":"dws-agent-hint-index","source":{"kind":"explicit"},"metadata":{},"selection":{}}`, nil, false}, } { t.Run(tc.name, func(t *testing.T) { - if used, err := parseCoverageHints(root, map[string]string{"index.json": tc.index, "selection.json": validHint}); err != nil || !used { - t.Fatalf("optional metadata = %v, %v", used, err) + bodies := map[string]string{"index.json": tc.index} + for path, body := range tc.files { + bodies[path] = body + } + used, err := parseCoverageHints(root, bodies) + if err != nil || used != tc.used { + t.Fatalf("optional maps = used=%v err=%v, want used=%v", used, err, tc.used) } }) } diff --git a/internal/generator/agentmetadata/manual_hints.go b/internal/generator/agentmetadata/manual_hints.go index 299d211b..bad680d3 100644 --- a/internal/generator/agentmetadata/manual_hints.go +++ b/internal/generator/agentmetadata/manual_hints.go @@ -87,24 +87,35 @@ func validateReviewedSelectionDelivery(file File, opts Options) error { for _, productID := range productIDs { metadata, ok := file.Products[productID] if !ok { + // ProductDecl-registered products may omit selection/ products{} + // rows; their contract_final prose is applied outside hint files. + if cli.HasProductDecl(productID) { + continue + } problems = append(problems, "missing product "+productID) continue } - if strings.TrimSpace(metadata.AgentSummary) == "" || metadata.agentSummaryRank != selectionRankReviewedExplicit { - problems = append(problems, productID+" agent_summary is not reviewed_explicit") + checks := []struct { + name string + valid bool + }{ + {"agent_summary", strings.TrimSpace(metadata.AgentSummary) != "" && isReviewedSelectionRank(metadata.agentSummaryRank)}, + {"use_when", len(metadata.UseWhen) > 0 && isReviewedSelectionRank(metadata.useWhenRank)}, + {"avoid_when", len(metadata.AvoidWhen) > 0 && isReviewedSelectionRank(metadata.avoidWhenRank)}, } - if len(metadata.UseWhen) == 0 || metadata.useWhenRank != selectionRankReviewedExplicit { - problems = append(problems, productID+" use_when is not reviewed_explicit") - } - if len(metadata.AvoidWhen) == 0 || metadata.avoidWhenRank != selectionRankReviewedExplicit { - problems = append(problems, productID+" avoid_when is not reviewed_explicit") + for _, check := range checks { + if !check.valid { + problems = append(problems, productID+" "+check.name+" is not reviewed_explicit/contract_final") + } } for _, field := range []string{"agent_summary", "use_when", "avoid_when"} { provenance, ok := metadata.FieldProvenance[field] - if !ok || provenance.Precedence != selectionPrecedenceReviewedExplicit || selectedCandidateCount(provenance.Candidates) != 1 { - problems = append(problems, productID+" "+field+" provenance is not one reviewed_explicit winner") + if !ok || !isReviewedSelectionPrecedence(provenance.Precedence) || selectedCandidateCount(provenance.Candidates) != 1 { + problems = append(problems, productID+" "+field+" provenance is not one reviewed_explicit/contract_final winner") } - if ok && !strings.Contains(provenance.Source, "/selection/") { + // File-based winners must come from a selection/ source file; + // contract_final winners are declared in code (no file path). + if ok && provenance.Precedence != selectionPrecedenceContractFinal && !strings.Contains(provenance.Source, "/selection/") { problems = append(problems, productID+" "+field+" source is not selection/: "+provenance.Source) } } @@ -189,6 +200,26 @@ func selectedCandidateCount(candidates []FieldCandidateProvenance) int { return count } +// selectionHintCoverageProducts returns product IDs that still need a +// selection/ products{} row. ProductDecl-registered products are exempt. +func selectionHintCoverageProducts(productIDs map[string]bool) map[string]bool { + if productIDs == nil { + return nil + } + expected := make(map[string]bool, len(productIDs)) + for productID, include := range productIDs { + if !include { + continue + } + productID = strings.TrimSpace(productID) + if productID == "" || cli.HasProductDecl(productID) { + continue + } + expected[productID] = true + } + return expected +} + func expectedCanonicalToolSet(opts Options) map[string]bool { if len(opts.CanonicalToolPaths) > 0 { expected := make(map[string]bool, len(opts.CanonicalToolPaths)) @@ -214,10 +245,6 @@ func expectedCanonicalToolSet(opts Options) map[string]bool { func validateSelectionAuthoringContracts(opts Options) error { selectionRoot := filepath.Join(resolvePath(opts.Root, opts.HintsDir), "selection") - hints, err := cli.LoadAgentHintsFromSelectionForValidation(os.DirFS(selectionRoot)) - if err != nil { - return fmt.Errorf("load selection Agent hints: %w", err) - } expectedTools := expectedCanonicalToolSet(opts) // Declared tools carry their selection fields in the Contract final // overlay (corecmd.SchemaDecl); they are exempt from hint-file coverage. @@ -229,7 +256,20 @@ func validateSelectionAuthoringContracts(opts Options) error { delete(expectedTools, canonical) } } - if err := cli.ValidateManualAgentHintSet(hints, opts.ProductIDs, expectedTools); err != nil { + expectedProducts := selectionHintCoverageProducts(opts.ProductIDs) + info, err := os.Stat(selectionRoot) + missing := err != nil || !info.IsDir() + if missing { + if selectionHintCoverageRequired(expectedProducts, expectedTools) { + return fmt.Errorf("required Agent hint directory missing: %s", selectionRoot) + } + return nil + } + hints, err := cli.LoadAgentHintsFromSelectionForValidation(os.DirFS(selectionRoot)) + if err != nil { + return fmt.Errorf("load selection Agent hints: %w", err) + } + if err := cli.ValidateManualAgentHintSet(hints, expectedProducts, expectedTools); err != nil { return fmt.Errorf("validate selection Agent hints: %w", err) } if opts.MaxExamples > 0 { diff --git a/internal/generator/agentmetadata/manual_hints_coverage_test.go b/internal/generator/agentmetadata/manual_hints_coverage_test.go index 26406c9c..6cd63483 100644 --- a/internal/generator/agentmetadata/manual_hints_coverage_test.go +++ b/internal/generator/agentmetadata/manual_hints_coverage_test.go @@ -8,6 +8,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" ) func TestCrossPlatformCoverageReviewedSelectionRetentionAndDeliveryFailures(t *testing.T) { @@ -49,7 +51,7 @@ func TestCrossPlatformCoverageReviewedSelectionRetentionAndDeliveryFailures(t *t }}, } err := validateReviewedSelectionDelivery(file, opts) - if err == nil || !strings.Contains(err.Error(), "source is not selection") || !strings.Contains(err.Error(), "is not reviewed_explicit") { + if err == nil || !strings.Contains(err.Error(), "source is not selection") || !strings.Contains(err.Error(), "is not reviewed_explicit/contract_final") { t.Fatalf("invalid delivery error = %v", err) } @@ -74,6 +76,63 @@ func TestCrossPlatformCoverageReviewedSelectionRetentionAndDeliveryFailures(t *t } } +func TestValidateReviewedSelectionDeliveryAcceptsProductContractFinal(t *testing.T) { + t.Cleanup(func() { cli.ClearProductDeclForTest("sample") }) + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "sample", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "Manage samples", + UseWhen: []string{"target is a sample"}, + AvoidWhen: []string{"target is another product"}, + }, + }) + + selected := []FieldCandidateProvenance{{Precedence: selectionPrecedenceContractFinal, Selected: true, Source: "cli.product_decl"}} + contractFinalProvenance := FieldProvenance{ + Precedence: selectionPrecedenceContractFinal, + Source: "cli.product_decl", + Candidates: selected, + } + trueValue := true + file := File{ + Products: map[string]ProductMetadata{"sample": { + AgentSummary: "Manage samples", agentSummaryRank: selectionRankContractFinal, + UseWhen: []string{"target is a sample"}, useWhenRank: selectionRankContractFinal, + AvoidWhen: []string{"target is another product"}, avoidWhenRank: selectionRankContractFinal, + FieldProvenance: map[string]FieldProvenance{ + "agent_summary": contractFinalProvenance, + "use_when": contractFinalProvenance, + "avoid_when": contractFinalProvenance, + }, + }}, + Tools: map[string]ToolMetadata{"sample tool": { + AgentSummary: "summary", agentSummaryRank: selectionRankContractFinal, + UseWhen: []string{"use"}, useWhenRank: selectionRankContractFinal, + AvoidWhen: []string{"avoid"}, avoidWhenRank: selectionRankContractFinal, + Examples: []string{"dws sample tool"}, examplesRank: selectionRankContractFinal, + Reviewed: &trueValue, reviewedRank: selectionRankContractFinal, + FieldProvenance: map[string]FieldProvenance{ + "agent_summary": contractFinalProvenance, + "use_when": contractFinalProvenance, + "avoid_when": contractFinalProvenance, + "examples": contractFinalProvenance, + }, + }}, + } + opts := Options{ProductIDs: map[string]bool{"sample": true}, CanonicalToolPaths: map[string]string{"sample.tool": "sample tool"}} + if err := validateReviewedSelectionDelivery(file, opts); err != nil { + t.Fatalf("contract_final product delivery rejected: %v", err) + } + + // Declared products may omit selection products{} entirely. + if err := validateReviewedSelectionDelivery(File{ + Products: map[string]ProductMetadata{}, + Tools: file.Tools, + }, opts); err != nil { + t.Fatalf("ProductDecl-exempt missing product rejected: %v", err) + } +} + func TestCrossPlatformCoverageExpectedCanonicalToolHelpersRemainingEdges(t *testing.T) { paths := expectedCanonicalToolPaths(Options{CanonicalToolPaths: map[string]string{ " sample.tool ": " sample item get ", diff --git a/internal/generator/agentmetadata/manual_hints_test.go b/internal/generator/agentmetadata/manual_hints_test.go index d3dc1bd2..0e0e8792 100644 --- a/internal/generator/agentmetadata/manual_hints_test.go +++ b/internal/generator/agentmetadata/manual_hints_test.go @@ -49,11 +49,11 @@ func TestGenerateUsesReviewedSelectionAsSingleDeliverySource(t *testing.T) { product := file.Products["sample"] for _, field := range []string{"agent_summary", "use_when", "avoid_when"} { provenance := product.FieldProvenance[field] - if provenance.Precedence != selectionPrecedenceReviewedExplicit || selectedCandidateCount(provenance.Candidates) != 1 { + if !isReviewedSelectionPrecedence(provenance.Precedence) || selectedCandidateCount(provenance.Candidates) != 1 { t.Fatalf("product %s provenance = %#v", field, provenance) } - if !strings.Contains(provenance.Source, "/selection/") { - t.Fatalf("product %s source = %q, want selection/", field, provenance.Source) + if provenance.Precedence != selectionPrecedenceContractFinal && !strings.Contains(provenance.Source, "/selection/") { + t.Fatalf("product %s source = %q, want selection/ or contract_final", field, provenance.Source) } } for _, field := range []string{"agent_summary", "use_when", "avoid_when", "examples"} { @@ -85,6 +85,27 @@ func TestGenerateRejectsMaxExamplesThatWouldTruncateReviewedSelection(t *testing } } +func TestSelectionHintCoverageProductsExemptsProductDecl(t *testing.T) { + t.Cleanup(func() { cli.ClearProductDeclForTest("declared") }) + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "declared", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "Declared product", + UseWhen: []string{"use declared"}, + AvoidWhen: []string{"avoid declared"}, + }, + }) + got := SelectionHintCoverageProducts(RegistryProjection{ + ProductIDs: map[string]bool{"declared": true, "hinted": true}, + }) + if got["declared"] { + t.Fatal("ProductDecl product must be exempt from selection products coverage") + } + if !got["hinted"] { + t.Fatal("non-Decl product must remain required in selection products coverage") + } +} + func TestGenerateRejectsIncompleteSelectionCoverage(t *testing.T) { root := t.TempDir() writeSelectionFixture(t, root, false, `[]`) diff --git a/internal/generator/agentmetadata/metadata.go b/internal/generator/agentmetadata/metadata.go index b0181f60..023f4577 100644 --- a/internal/generator/agentmetadata/metadata.go +++ b/internal/generator/agentmetadata/metadata.go @@ -382,8 +382,9 @@ func Generate(opts Options) (File, Stats, error) { // generateFromSources retains the lower-precedence evidence parsers as a // package-internal seam for focused tests. Production callers must use Generate, -// which requires the reviewed selection hint sources under HintsDir -// (metadata map/files are optional; empty metadata is a valid end-state). +// which reads HintsDir when set. Both metadata and selection maps/files are +// optional once ProductDecl/ContractFinal cover routing; empty or absent +// selection is a valid end-state. func generateFromSources(opts Options) (File, Stats, error) { if opts.Root == "" { opts.Root = "." diff --git a/internal/generator/agentmetadata/pipeline.go b/internal/generator/agentmetadata/pipeline.go index 1d50389a..11d61187 100644 --- a/internal/generator/agentmetadata/pipeline.go +++ b/internal/generator/agentmetadata/pipeline.go @@ -84,20 +84,37 @@ func SelectionHintCoverageTools(projection RegistryProjection) map[string]bool { return expectedTools } +// SelectionHintCoverageProducts returns product IDs that still need a +// selection/ products{} row. ProductDecl-registered products are exempt +// because their routing prose lives on the in-code declaration. +func SelectionHintCoverageProducts(projection RegistryProjection) map[string]bool { + return selectionHintCoverageProducts(projection.ProductIDs) +} + // ValidateSelectionHints validates selection/ against the projected registry. // metadata/ under HintsDir is optional and is not consulted here. +// selection/ is optional when every projected product has ProductDecl and +// every projected tool has ContractFinal (no residual hint-file coverage). +// The directory may be absent in that end-state; when present, empty +// products{}/tools{} shells remain valid. func ValidateSelectionHints(rootPath, hintsDir string, projection RegistryProjection) error { hintsRoot := resolvePipelineRootPath(rootPath, hintsDir) selectionRoot := filepath.Join(hintsRoot, "selection") - if info, err := os.Stat(selectionRoot); err != nil || !info.IsDir() { - return fmt.Errorf("required Agent hint directory missing: %s", selectionRoot) + expectedTools := SelectionHintCoverageTools(projection) + expectedProducts := SelectionHintCoverageProducts(projection) + info, err := os.Stat(selectionRoot) + missing := err != nil || !info.IsDir() + if missing { + if selectionHintCoverageRequired(expectedProducts, expectedTools) { + return fmt.Errorf("required Agent hint directory missing: %s", selectionRoot) + } + return nil } agentHints, err := cli.LoadAgentHintsFromSelectionForValidation(os.DirFS(selectionRoot)) if err != nil { return fmt.Errorf("load selection Agent hints: %w", err) } - expectedTools := SelectionHintCoverageTools(projection) - if err := cli.ValidateManualAgentHintSet(agentHints, projection.ProductIDs, expectedTools); err != nil { + if err := cli.ValidateManualAgentHintSet(agentHints, expectedProducts, expectedTools); err != nil { return fmt.Errorf("validate selection Agent hints: %w", err) } if err := cli.ValidateManualAgentHintExamples(projection.Bound, agentHints); err != nil { @@ -109,6 +126,20 @@ func ValidateSelectionHints(rootPath, hintsDir string, projection RegistryProjec return nil } +func selectionHintCoverageRequired(expectedProducts, expectedTools map[string]bool) bool { + for _, include := range expectedProducts { + if include { + return true + } + } + for _, include := range expectedTools { + if include { + return true + } + } + return false +} + // GenerateFromCommandRoot is the Catalog in-memory Agent-metadata pipeline. // It applies reviewed manual hints, binds the EffectiveCommandRegistry, // validates selection/, and Generate()s metadata without reading or writing diff --git a/internal/generator/cmd_schema_agent_metadata/coverage_more_test.go b/internal/generator/cmd_schema_agent_metadata/coverage_more_test.go index 3b29d622..64c477ee 100644 --- a/internal/generator/cmd_schema_agent_metadata/coverage_more_test.go +++ b/internal/generator/cmd_schema_agent_metadata/coverage_more_test.go @@ -308,3 +308,21 @@ func TestCrossPlatformCoverageSelectionHintInputExemptsDeclaredTools(t *testing. t.Fatalf("declared tool must be exempt from hint coverage, expected = %#v", expected) } } + +func TestCrossPlatformCoverageSelectionHintInputExemptsDeclaredProducts(t *testing.T) { + t.Cleanup(func() { cli.ClearProductDeclForTest("declared") }) + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "declared", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "Declared product", + UseWhen: []string{"use declared"}, + AvoidWhen: []string{"avoid declared"}, + }, + }) + expected := agentmetadata.SelectionHintCoverageProducts(commandRegistryProjection{ + ProductIDs: map[string]bool{"declared": true, "hinted": true}, + }) + if expected["declared"] || !expected["hinted"] { + t.Fatalf("ProductDecl product must be exempt from hint coverage, expected = %#v", expected) + } +} diff --git a/internal/generator/cmd_schema_agent_metadata/main.go b/internal/generator/cmd_schema_agent_metadata/main.go index 3a48b659..011835a2 100644 --- a/internal/generator/cmd_schema_agent_metadata/main.go +++ b/internal/generator/cmd_schema_agent_metadata/main.go @@ -71,7 +71,7 @@ func main() { flag.StringVar(&skillPath, "skill", "skills/mono/SKILL.md", "Main DWS SKILL.md path") flag.StringVar(&productsDir, "products", "skills/mono/references/products", "Product skill reference directory") flag.StringVar(&intentGuidePath, "intent-guide", "skills/mono/references/intent-guide.md", "Cross-product intent guide path") - flag.StringVar(&hintsDir, "hints", "internal/cli/schema_hints", "Versioned Agent hint JSON directory (required selection/; optional metadata/ shells)") + flag.StringVar(&hintsDir, "hints", "internal/cli/schema_hints", "Versioned Agent hint JSON directory (selection/ and metadata/ optional/retired when Decl covers)") flag.StringVar(&interfaceMetadataPath, "interface-metadata", "internal/cli/schema_mcp_metadata.json", "Sanitized versioned MCP metadata used only for fallback Agent summaries") flag.StringVar(&outputPath, "output", "", "Optional diagnostic single-file Agent metadata JSON (not a Catalog input)") flag.StringVar(&outputDir, "output-dir", "", "Optional diagnostic split Agent metadata directory (not a Catalog input; Catalog injects in-memory)") diff --git a/internal/generator/cmd_schema_agent_metadata/main_test.go b/internal/generator/cmd_schema_agent_metadata/main_test.go index 95279e9b..ac478069 100644 --- a/internal/generator/cmd_schema_agent_metadata/main_test.go +++ b/internal/generator/cmd_schema_agent_metadata/main_test.go @@ -248,9 +248,20 @@ func TestValidateAgentMetadataOutputIsolationProtectsAllSourceKinds(t *testing.T } } -func TestValidateSelectionHintInputRequiresHintDirs(t *testing.T) { +func TestValidateSelectionHintInputAllowsMissingWhenCoverageEmpty(t *testing.T) { root := t.TempDir() - err := validateSelectionHintInput(root, "internal/cli/schema_hints", commandRegistryProjection{}) + if err := validateSelectionHintInput(root, "internal/cli/schema_hints", commandRegistryProjection{}); err != nil { + t.Fatalf("missing selection/ must be optional when coverage is empty: %v", err) + } +} + +func TestValidateSelectionHintInputRequiresHintDirsWhenCoverageRemains(t *testing.T) { + root := t.TempDir() + registry := commandRegistryProjection{ + CanonicalToolPaths: map[string]string{"sample.run": "sample run"}, + ProductIDs: map[string]bool{"sample": true}, + } + err := validateSelectionHintInput(root, "internal/cli/schema_hints", registry) if err == nil || !strings.Contains(err.Error(), "required Agent hint directory missing") { t.Fatalf("validateSelectionHintInput() error = %v", err) } diff --git a/internal/helpers/aisearch.go b/internal/helpers/aisearch.go index 1fb3ce66..01e89db2 100644 --- a/internal/helpers/aisearch.go +++ b/internal/helpers/aisearch.go @@ -165,6 +165,20 @@ func normalizeAisearchSearchTypes(values []string) []string { } func newAisearchCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/aisearch.json + // products.aisearch). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "aisearch", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "企业内智能搜人、搜知识内容与搜行为记录", + UseWhen: []string{ + "语义找人、按主题搜企业知识,或追溯发送/创建/分享等行为", + }, + AvoidWhen: []string{ + "已有明确资源 ID 要读写时改用对应产品;普通 OAuth 登录不用 aisearch", + }, + }, + }) root := &cobra.Command{ Use: "aisearch", Short: "AI 搜问", diff --git a/internal/helpers/aitable.go b/internal/helpers/aitable.go index 01aa3f44..9fef3cff 100644 --- a/internal/helpers/aitable.go +++ b/internal/helpers/aitable.go @@ -925,6 +925,20 @@ func runAitableViewUpdateArray(cmd *cobra.Command, blockKey string) error { } func newAitableCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/aitable.json + // products.aitable). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "aitable", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理 AI 表格 Base、数据表、字段、记录、视图、表单、仪表盘、权限、导入导出与自动化工作流。", + UseWhen: []string{ + "需要读取或管理 AI 表格中的结构、数据、视图、权限、导入导出或工作流时", + }, + AvoidWhen: []string{ + "目标是在线电子表格单元格读写时用 sheet;普通文档用 doc", + }, + }, + }) root := &cobra.Command{ Use: "aitable", Short: "AI 表格操作", @@ -6370,9 +6384,24 @@ parentSectionId 为空串表示该节点在 Base 根目录下。 formUpdateCmd.Flags().String("title", "", "表单标题(与 --name 等价,二选一)") formUpdateCmd.Flags().String("name", "", "表单标题(与 --title 等价)") formUpdateCmd.Flags().String("description", "", "表单描述") - // form questions create/delete 复用 field create/delete 的 flag(保持入参完全一致) - copyFlags(fieldCreateCmd, formQuestionsCreateCmd, "base-id", "table-id", "fields", "name", "type", "config", "ai-config", "field-name", "field-type") - copyFlags(fieldDeleteCmd, formQuestionsDeleteCmd, "base-id", "table-id", "field-id") + // form questions create/delete keep the same flag surface as field create/delete, + // but must not share flag pointers: field create ParamDecl annotates --fields as + // required+array, and sharing would leak that into form_questions_create and break + // merge-base schema-compatibility (fields was optional / no interface_type there). + formQuestionsCreateCmd.Flags().String("base-id", "", "Base ID(通过 base list 获取)(必填)") + formQuestionsCreateCmd.Flags().String("table-id", "", "Table ID(通过 base get 获取)(必填)") + formQuestionsCreateCmd.Flags().String("fields", "", "待新增字段列表 JSON 数组,至少包含 1 个字段,单次最多 15 个。系统会按数组顺序依次创建,返回结果顺序与入参保持一致,并逐项标明成功/失败状态。若是单个字段可直接使用 --name/--type/--config") + formQuestionsCreateCmd.Flags().String("name", "", "要创建的单字段名称(与 --type 配合使用,替代 --fields)") + formQuestionsCreateCmd.Flags().String("field-name", "", "--name 的别名(兼容 LLM 常见误用)") + _ = formQuestionsCreateCmd.Flags().MarkHidden("field-name") + formQuestionsCreateCmd.Flags().String("type", "", "要创建的单字段类型(需要配合 --name,参考 table create 的内置类型)") + formQuestionsCreateCmd.Flags().String("field-type", "", "--type 的别名(兼容 LLM 常见误用)") + _ = formQuestionsCreateCmd.Flags().MarkHidden("field-type") + formQuestionsCreateCmd.Flags().String("config", "", "单字段的额外配置 JSON(如 options,配合 --name/--type 使用)") + formQuestionsCreateCmd.Flags().String("ai-config", "", "单字段 AI 配置 JSON(如 outputType/prompt,配合 --name/--type 使用)") + formQuestionsDeleteCmd.Flags().String("base-id", "", "Base ID(通过 base list 获取)(必填)") + formQuestionsDeleteCmd.Flags().String("table-id", "", "Table ID(通过 base get 获取)(必填)") + formQuestionsDeleteCmd.Flags().String("field-id", "", "待删除字段 ID(通过 table get 获取)(必填)") formFieldListCmd.Flags().String("base-id", "", "所属 Base ID (必填)") formFieldListCmd.Flags().String("table-id", "", "所属 Table ID (必填)") formFieldListCmd.Flags().String("view-id", "", "目标表单视图 ID (必填)") diff --git a/internal/helpers/attendance.go b/internal/helpers/attendance.go index f092c8a4..7d68e92d 100644 --- a/internal/helpers/attendance.go +++ b/internal/helpers/attendance.go @@ -516,6 +516,20 @@ func readGlobalSettingSaveFlagValue(cmd *cobra.Command, spec globalSettingSaveFl } func newAttendanceCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/attendance.json + // products.attendance). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "attendance", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "查询考勤记录、排班、班次、考勤组、审批、报表、个人规则和假期,并执行经确认的考勤配置变更。", + UseWhen: []string{ + "用户要查询或管理钉钉考勤数据、规则、排班、考勤组、审批表单或假期余额。", + }, + AvoidWhen: []string{ + "用户只需处理普通日历日程、非考勤类活动签到或通用审批流状态,而不是钉钉考勤业务。", + }, + }, + }) root := &cobra.Command{ Use: "attendance", Short: "考勤打卡 / 排班 / 统计", diff --git a/internal/helpers/calendar.go b/internal/helpers/calendar.go index 6d9b0886..44a04fa5 100644 --- a/internal/helpers/calendar.go +++ b/internal/helpers/calendar.go @@ -210,6 +210,20 @@ func findUnknownFlag(cmd *cobra.Command) string { } func newCalendarCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/calendar.json + // products.calendar). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "calendar", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理日历本、日程、参与人、附件、会议室,并查询人员或会议室闲忙状态。", + UseWhen: []string{ + "需要查询或管理钉钉日历、日程、参与人、会议室和闲忙信息时。", + }, + AvoidWhen: []string{ + "目标是聊天消息、待办或审批,而不是日历与日程资源时。", + }, + }, + }) root := &cobra.Command{ Use: "calendar", Short: "日历日程 / 会议室 / 闲忙", diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index eec2e9cb..a645d5c6 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -1064,6 +1064,20 @@ func firstStringField(data map[string]any, keys ...string) string { // ────────────────────────────────────────────────────────── func newChatCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/chat.json + // products.chat). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "chat", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理钉钉会话、群聊、群成员、机器人、消息检索与发送", + UseWhen: []string{ + "请求涉及群聊管理、聊天记录、消息发送、会话设置或群机器人", + }, + AvoidWhen: []string{ + "邮件用 mail;开放平台应用/机器人建号发布用 dev;企业语义找人优先 aisearch person", + }, + }, + }) root := &cobra.Command{ Use: "chat", Aliases: []string{"im"}, diff --git a/internal/helpers/contact.go b/internal/helpers/contact.go index a37b736e..ece3a332 100644 --- a/internal/helpers/contact.go +++ b/internal/helpers/contact.go @@ -370,6 +370,20 @@ func newContactAccountUpdateCommand() *cobra.Command { } func newContactCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/contact.json + // products.contact). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "contact", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "查询通讯录与花名册,并管理企业、部门、员工及企业账号", + UseWhen: []string{ + "按姓名/手机号/userId/部门条件做通讯录精确查询,或明确执行企业与员工入企管理", + }, + AvoidWhen: []string{ + "职责/上级等语义找人优先 aisearch person;不要用 contact 发消息;写操作前确认当前企业和目标信息", + }, + }, + }) root := &cobra.Command{ Use: "contact", Short: "通讯录 / 用户 / 部门 / 角色 / 人员关系", diff --git a/internal/helpers/dev.go b/internal/helpers/dev.go index ce95e4d0..8ff5dff0 100644 --- a/internal/helpers/dev.go +++ b/internal/helpers/dev.go @@ -14,6 +14,7 @@ package helpers import ( + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor" "github.com/spf13/cobra" ) @@ -44,6 +45,20 @@ func (devHandler) Name() string { } func (devHandler) Command(runner executor.Runner) *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/dev.json + // products.dev). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "dev", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理开放平台应用、权限、机器人、版本发布与本地连接器", + UseWhen: []string{ + "创建/配置开放平台应用、机器人、权限、事件订阅或发布版本", + }, + AvoidWhen: []string{ + "只查开放平台文档用 devdoc;业务聊天/邮信用 chat/mail", + }, + }, + }) root := &cobra.Command{ Use: "dev", Short: "开放平台开发者能力", diff --git a/internal/helpers/devapp.go b/internal/helpers/devapp.go index 76e06652..6108343a 100644 --- a/internal/helpers/devapp.go +++ b/internal/helpers/devapp.go @@ -83,6 +83,20 @@ const ( // backend contract (SupplementServers/StaticServers injection key and the // pinned op-app endpoint), decoupled from the user-facing command name. func newDevAppCommand(runner executor.Runner) *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/devapp.json + // products.devapp). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "devapp", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理钉钉开放平台企业内部应用、成员、权限、机器人、事件与版本", + UseWhen: []string{ + "请求涉及企业内部应用的查询、创建、配置、成员权限、机器人、事件订阅或版本管理", + }, + AvoidWhen: []string{ + "开放平台接口文档搜索使用 devdoc;普通钉钉业务数据使用对应产品命令", + }, + }, + }) root := &cobra.Command{ Use: "app", Short: "开放平台应用", diff --git a/internal/helpers/devdoc.go b/internal/helpers/devdoc.go index a8b55383..198ee183 100644 --- a/internal/helpers/devdoc.go +++ b/internal/helpers/devdoc.go @@ -10,6 +10,20 @@ import ( ) func newDevdocCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/devdoc.json + // products.devdoc). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "devdoc", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "搜索钉钉开放平台开发文档与错误排查资料", + UseWhen: []string{ + "查询 OpenAPI、字段、错误码、SDK、鉴权或接入指南", + }, + AvoidWhen: []string{ + "搜索用户业务文档或读写业务数据时不要用 devdoc", + }, + }, + }) root := &cobra.Command{ Use: "devdoc", Short: "开放平台文档搜索", diff --git a/internal/helpers/ding.go b/internal/helpers/ding.go index 5a9e539e..6880496f 100644 --- a/internal/helpers/ding.go +++ b/internal/helpers/ding.go @@ -19,6 +19,20 @@ import ( var dingRemindTypeMap = map[string]int{"app": 1, "sms": 2, "call": 3} func newDingCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/ding.json + // products.ding). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "ding", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "以企业机器人发送或撤回应用内/短信/电话 DING", + UseWhen: []string{ + "需要机器人身份发送或撤回 DING", + }, + AvoidWhen: []string{ + "普通聊天消息用 chat;用户身份 DING 不要走机器人命令", + }, + }, + }) root := &cobra.Command{ Use: "ding", Short: "DING 消息 / 发送 / 撤回", diff --git a/internal/helpers/doc.go b/internal/helpers/doc.go index 196c5c3b..67448b95 100644 --- a/internal/helpers/doc.go +++ b/internal/helpers/doc.go @@ -789,6 +789,20 @@ func renderDocOverwriteDiff(nodeID, before, after string) string { } func newDocCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/doc.json + // products.doc). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "doc", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理钉钉在线文档的正文、块、评论、导入导出、模板与版本", + UseWhen: []string{ + "创建、读取或编辑在线文档内容,或处理文档块、评论、导入导出、模板和版本时", + }, + AvoidWhen: []string{ + "文件、目录、上传下载及节点权限已迁移到 drive;知识库空间和成员使用 wiki;不要用于搜索开放平台开发文档", + }, + }, + }) root := &cobra.Command{ Use: "doc", Short: "钉钉文档管理", diff --git a/internal/helpers/drive.go b/internal/helpers/drive.go index 92851833..0409fa39 100644 --- a/internal/helpers/drive.go +++ b/internal/helpers/drive.go @@ -284,6 +284,20 @@ func parseDriveUploadInfo(text string) (resourceURL, uploadID string, headers ma } func newDriveCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/drive.json + // products.drive). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "drive", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理钉盘及文档空间中的文件、目录、上传下载、回收站与公开发布", + UseWhen: []string{ + "浏览、搜索、上传、下载或整理钉盘和文档空间文件时", + }, + AvoidWhen: []string{ + "需要读取或编辑在线文档正文时使用 doc;需要管理知识库空间或成员时使用 wiki", + }, + }, + }) driveCmd := &cobra.Command{ Use: "drive", Short: "钉盘文件管理", @@ -1048,6 +1062,12 @@ func newDriveCommand() *cobra.Command { "dws drive list-spaces --space-type mySpace --format json", }, }, + // MCP pins maxResults as number while the Cobra flag is integer. Publishing + // interface_type=number is a merge-base contract change; declare integer so + // resolution equals cobra_flag_type and omits a separate interface_type field. + Parameters: []corecmd.ParamDecl{ + {Name: "limit", InterfaceType: "integer"}, + }, }, }) driveListSpacesCmd.Flags().Int("limit", 20, "每页返回数量 (默认 20,最大 50),仅 spaceType 为 orgSpace 时有效") diff --git a/internal/helpers/hrbrain.go b/internal/helpers/hrbrain.go index af5b06a2..5a5dbfa2 100644 --- a/internal/helpers/hrbrain.go +++ b/internal/helpers/hrbrain.go @@ -15,6 +15,21 @@ import ( // ────────────────────────────────────────────────────────── func newHrbrainCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/hrbrain.json + // products.hrbrain). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "hrbrain", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "钉钉组织大脑:人才池管理、员工档案查询与人才搜索", + UseWhen: []string{ + "需要查询人才池、员工档案(元数据/标签/职业历程/绩效)或搜索员工时", + }, + AvoidWhen: []string{ + "要操作通讯录/组织架构基础信息时改用 contact 产品", + "要提交/查询战略解码、经营合约、目标等 OKR 能力时改用 agoal(CLI-only,未接入 Agent Schema)", + }, + }, + }) root := &cobra.Command{ Use: "hrbrain", Short: "组织大脑:人才池、员工档案与人才搜索", diff --git a/internal/helpers/live.go b/internal/helpers/live.go index e102e005..68cb7360 100644 --- a/internal/helpers/live.go +++ b/internal/helpers/live.go @@ -6,6 +6,20 @@ import ( ) func newLiveCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/live.json + // products.live). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "live", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "查询当前用户发起的直播列表", + UseWhen: []string{ + "用户要查看自己的直播列表或基础统计", + }, + AvoidWhen: []string{ + "当前公开面不支持创建/控制直播", + }, + }, + }) root := &cobra.Command{ Use: "live", Short: "直播列表 / 信息", diff --git a/internal/helpers/mail.go b/internal/helpers/mail.go index 6d1f1f03..3a53b3aa 100644 --- a/internal/helpers/mail.go +++ b/internal/helpers/mail.go @@ -98,6 +98,21 @@ func validateMailRuleConditions(conditions []any) error { } func newMailCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/mail.json + // products.mail). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "mail", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理邮箱、邮件、草稿、附件、文件夹、联系人与邮件模板", + UseWhen: []string{ + "查收、搜索、阅读、回复、发送或整理邮件", + }, + AvoidWhen: []string{ + "即时消息用 chat;开放平台应用配置用 dev", + "公开 catalog 不含 thread trash / sent-message recall 等硬 --yes 命令时不要臆造路径", + }, + }, + }) root := &cobra.Command{ Use: "mail", Short: "邮箱 / 邮件收发", diff --git a/internal/helpers/markdown.go b/internal/helpers/markdown.go index 7892e1c6..8e3d3e54 100644 --- a/internal/helpers/markdown.go +++ b/internal/helpers/markdown.go @@ -32,6 +32,20 @@ import ( var markdownUploadStat = os.Stat func newMarkdownCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/markdown.json + // products.markdown). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "markdown", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "跨钉盘与文档空间创建、获取、覆盖和局部修补原生 Markdown 文件", + UseWhen: []string{ + "目标是原生 .md 文件,并需要在 Drive/Doc 路由间安全处理内容时", + }, + AvoidWhen: []string{ + "在线文档正文操作使用 doc;普通二进制文件上传下载使用 drive", + }, + }, + }) root := &cobra.Command{ Use: "markdown", Short: "Markdown 文件处理", diff --git a/internal/helpers/minutes.go b/internal/helpers/minutes.go index 91d337d5..2d37f734 100644 --- a/internal/helpers/minutes.go +++ b/internal/helpers/minutes.go @@ -14,6 +14,20 @@ import ( // ────────────────────────────────────────────────────────── func newMinutesCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/minutes.json + // products.minutes). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "minutes", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "查询和维护钉钉听记的转写、摘要、待办、权限、录音、标签、说话人总结及文件上传会话。", + UseWhen: []string{ + "用户要查找、读取、编辑或管理钉钉听记及其录音、转写、摘要和衍生内容。", + }, + AvoidWhen: []string{ + "用户要处理普通文档正文、群聊消息或日历会议安排,而不是钉钉听记。", + }, + }, + }) minutesListCmd := &cobra.Command{Use: "list", Short: "听记列表", RunE: groupRunE} minutesListMineCmd := &cobra.Command{ diff --git a/internal/helpers/oa.go b/internal/helpers/oa.go index 80a11d0e..5ed90621 100644 --- a/internal/helpers/oa.go +++ b/internal/helpers/oa.go @@ -19,6 +19,20 @@ import ( // ────────────────────────────────────────────────────────── func newOaCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/oa.json + // products.oa). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "oa", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "查询和处理 OA 审批实例、任务、记录、抄送与评论", + UseWhen: []string{ + "查看待审、已办、已发起或抄送审批,并执行同意、拒绝、撤销、转交等审批动作时", + }, + AvoidWhen: []string{ + "不要用于普通待办任务或工作日志;需要创建审批实例时先确认当前命令面是否支持", + }, + }, + }) root := &cobra.Command{ Use: "oa", Short: "OA 审批 / 同意 / 拒绝 / 撤销", diff --git a/internal/helpers/report.go b/internal/helpers/report.go index 807b3d0a..c49f9afd 100644 --- a/internal/helpers/report.go +++ b/internal/helpers/report.go @@ -50,6 +50,20 @@ var ( // ────────────────────────────────────────────────────────── func newReportCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/report.json + // products.report). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "report", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "查询日志模板、收发日志、日志正文与统计,并按模板提交日志", + UseWhen: []string{ + "查看或提交日报、周报等钉钉日志时", + }, + AvoidWhen: []string{ + "不要用于待办任务、OA 审批或在线文档正文编辑", + }, + }, + }) root := &cobra.Command{ Use: "report", Aliases: []string{"log"}, diff --git a/internal/helpers/sheet.go b/internal/helpers/sheet.go index cb7e34a9..02cdae05 100644 --- a/internal/helpers/sheet.go +++ b/internal/helpers/sheet.go @@ -21,6 +21,22 @@ func resolveSheetName(cmd *cobra.Command) string { } func newSheetCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/sheet.json + // products.sheet). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "sheet", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", + UseWhen: []string{ + "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", + "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时", + }, + AvoidWhen: []string{ + "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", + "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令", + }, + }, + }) root := &cobra.Command{ Use: "sheet", Short: "钉钉表格管理", diff --git a/internal/helpers/todo.go b/internal/helpers/todo.go index 07dee66f..2bdd1925 100644 --- a/internal/helpers/todo.go +++ b/internal/helpers/todo.go @@ -56,6 +56,20 @@ func ensureTodoTaskExists(ctx context.Context, taskID string) error { } func newTodoCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/todo.json + // products.todo). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "todo", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理待办任务、标签、子任务、执行人、参与人、评论、附件与提醒", + UseWhen: []string{ + "查询、创建或更新个人待办及其协作信息时", + }, + AvoidWhen: []string{ + "不要用于 OA 审批流转、工作日志提交或日历日程管理", + }, + }, + }) todoCmd := &cobra.Command{ Use: "todo", Short: "待办任务管理", diff --git a/internal/helpers/wiki.go b/internal/helpers/wiki.go index 15e62f28..3eb99e15 100644 --- a/internal/helpers/wiki.go +++ b/internal/helpers/wiki.go @@ -143,6 +143,20 @@ func proxySubCmd(use, targetProduct, targetPath string, flagRenames map[string]s // ────────────────────────────────────────────────────────── func newWikiCommand() *cobra.Command { + // Product-level Agent routing Decl (migrated from selection/wiki.json + // products.wiki). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "wiki", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理钉钉知识库空间、节点与成员权限", + UseWhen: []string{ + "查找或管理知识库、知识库内节点及知识库成员时", + }, + AvoidWhen: []string{ + "需要编辑在线文档正文时使用 doc;只管理钉盘普通文件时使用 drive", + }, + }, + }) root := &cobra.Command{ Use: "wiki", Short: "知识库 / 空间管理 / 节点管理 / 成员管理", diff --git a/internal/pat/pat.go b/internal/pat/pat.go index 233ca8c6..19e8cfd5 100644 --- a/internal/pat/pat.go +++ b/internal/pat/pat.go @@ -18,12 +18,27 @@ package pat import ( "github.com/spf13/cobra" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/cmdutil" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" ) // RegisterCommands adds the pat command tree to rootCmd. func RegisterCommands(root *cobra.Command, c edition.ToolCaller) { + // Product-level Agent routing Decl (migrated from selection/pat.json + // products.pat). Catalog assembly stamps provenance contract_final. + cli.RegisterProductDecl(cli.ProductDecl{ + ID: "pat", + Selection: cli.ProductSelectionDecl{ + AgentSummary: "管理 Agent 的 PAT 行为授权与本地浏览器策略", + UseWhen: []string{ + "缺少行为授权需要预览/授予 scope,或配置授权是否打开浏览器", + }, + AvoidWhen: []string{ + "普通登录用 auth;不要与开放平台应用权限或具体业务写操作混用", + }, + }, + }) patCmd := &cobra.Command{ Use: "pat", Short: "行为授权管理", diff --git a/internal/shortcut/aitable/aitable.go b/internal/shortcut/aitable/aitable.go index e188fae4..b98ba8ef 100644 --- a/internal/shortcut/aitable/aitable.go +++ b/internal/shortcut/aitable/aitable.go @@ -598,15 +598,6 @@ var RecordQuery = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws aitable +record-query --base-id B --table-id T --query \"关键词\" --limit 50"}, }, - // Execute maps --query → MCP keyword (not inferable); JSON flags keep - // wire types so Agents do not treat comma-strings as opaque text. - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - {Name: "filters", InterfaceType: "object"}, - {Name: "sort", InterfaceType: "array"}, - {Name: "record-ids", InterfaceType: "array"}, - {Name: "field-ids", InterfaceType: "array"}, - }, }, Flags: []shortcut.Flag{ {Name: "base-id", Type: shortcut.FlagString, Desc: "Base ID", Required: true}, diff --git a/internal/shortcut/aitable/query_keyword_paramdecl_test.go b/internal/shortcut/aitable/query_keyword_paramdecl_test.go deleted file mode 100644 index 6dc446ef..00000000 --- a/internal/shortcut/aitable/query_keyword_paramdecl_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// 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 aitable - -import ( - "testing" - - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" -) - -func TestCrossPlatformCoverageQueryToKeywordParamDecls(t *testing.T) { - // Force package-level Schema Parameter blocks into the coverage graph and - // pin Execute's --query → MCP keyword mapping for Schema publication. - assertQueryKeywordParamDecl(t, "+record-query", RecordQuery) - spec := shortcut.FromShortcut(RecordQuery) - if len(spec.Schema.Parameters) == 0 { - t.Fatal("+record-query Schema.Parameters empty after FromShortcut") - } -} - -func assertQueryKeywordParamDecl(t *testing.T, name string, sc shortcut.Shortcut) { - t.Helper() - for _, p := range sc.Schema.Parameters { - if p.Name == "query" && p.Property == "keyword" { - return - } - } - t.Fatalf("%s missing ParamDecl query→keyword; got %#v", name, sc.Schema.Parameters) -} diff --git a/internal/shortcut/chat/chat_bot.go b/internal/shortcut/chat/chat_bot.go index b3764bb3..581ef6dd 100644 --- a/internal/shortcut/chat/chat_bot.go +++ b/internal/shortcut/chat/chat_bot.go @@ -154,10 +154,6 @@ var BotFind = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws chat +bot-find --query \"日报\""}, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词"}, diff --git a/internal/shortcut/chat/chat_group.go b/internal/shortcut/chat/chat_group.go index 246d04da..37e8915e 100644 --- a/internal/shortcut/chat/chat_group.go +++ b/internal/shortcut/chat/chat_group.go @@ -48,10 +48,6 @@ var ChatSearch = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws chat +chat-search --query \"项目冲刺\""}, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "群名称关键词"}, diff --git a/internal/shortcut/chat/query_keyword_paramdecl_test.go b/internal/shortcut/chat/query_keyword_paramdecl_test.go deleted file mode 100644 index bb010d46..00000000 --- a/internal/shortcut/chat/query_keyword_paramdecl_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// 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 chat - -import ( - "testing" - - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" -) - -func TestCrossPlatformCoverageQueryToKeywordParamDecls(t *testing.T) { - for _, tc := range []struct { - name string - sc shortcut.Shortcut - }{ - {"+chat-search", ChatSearch}, - {"+bot-find", BotFind}, - } { - assertQueryKeywordParamDecl(t, tc.name, tc.sc) - if len(shortcut.FromShortcut(tc.sc).Schema.Parameters) == 0 { - t.Fatalf("%s Schema.Parameters empty after FromShortcut", tc.name) - } - } -} - -func assertQueryKeywordParamDecl(t *testing.T, name string, sc shortcut.Shortcut) { - t.Helper() - for _, p := range sc.Schema.Parameters { - if p.Name == "query" && p.Property == "keyword" { - return - } - } - t.Fatalf("%s missing ParamDecl query→keyword; got %#v", name, sc.Schema.Parameters) -} diff --git a/internal/shortcut/contact/contact.go b/internal/shortcut/contact/contact.go index fffd8944..82d0b48c 100644 --- a/internal/shortcut/contact/contact.go +++ b/internal/shortcut/contact/contact.go @@ -122,10 +122,6 @@ var SearchUser = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws contact +search-user --query \"张三\""}, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词", Required: true}, diff --git a/internal/shortcut/contact/query_keyword_paramdecl_test.go b/internal/shortcut/contact/query_keyword_paramdecl_test.go deleted file mode 100644 index 97a06067..00000000 --- a/internal/shortcut/contact/query_keyword_paramdecl_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// 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 contact - -import ( - "testing" - - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" -) - -func TestCrossPlatformCoverageQueryToKeywordParamDecls(t *testing.T) { - assertQueryKeywordParamDecl(t, "+search-user", SearchUser) - if len(shortcut.FromShortcut(SearchUser).Schema.Parameters) == 0 { - t.Fatal("+search-user Schema.Parameters empty after FromShortcut") - } -} - -func assertQueryKeywordParamDecl(t *testing.T, name string, sc shortcut.Shortcut) { - t.Helper() - for _, p := range sc.Schema.Parameters { - if p.Name == "query" && p.Property == "keyword" { - return - } - } - t.Fatalf("%s missing ParamDecl query→keyword; got %#v", name, sc.Schema.Parameters) -} diff --git a/internal/shortcut/doc/doc.go b/internal/shortcut/doc/doc.go index 7a933576..543896b0 100644 --- a/internal/shortcut/doc/doc.go +++ b/internal/shortcut/doc/doc.go @@ -55,10 +55,6 @@ var Search = shortcut.Shortcut{ "dws doc +search --extensions pdf,docx", }, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词,不传返回最近访问的文档"}, diff --git a/internal/shortcut/doc/query_keyword_paramdecl_test.go b/internal/shortcut/doc/query_keyword_paramdecl_test.go deleted file mode 100644 index 24e5731b..00000000 --- a/internal/shortcut/doc/query_keyword_paramdecl_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// 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 doc - -import ( - "testing" - - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" -) - -func TestCrossPlatformCoverageQueryToKeywordParamDecls(t *testing.T) { - assertQueryKeywordParamDecl(t, "+search", Search) - if len(shortcut.FromShortcut(Search).Schema.Parameters) == 0 { - t.Fatal("+search Schema.Parameters empty after FromShortcut") - } -} - -func assertQueryKeywordParamDecl(t *testing.T, name string, sc shortcut.Shortcut) { - t.Helper() - for _, p := range sc.Schema.Parameters { - if p.Name == "query" && p.Property == "keyword" { - return - } - } - t.Fatalf("%s missing ParamDecl query→keyword; got %#v", name, sc.Schema.Parameters) -} diff --git a/internal/shortcut/drive/drive.go b/internal/shortcut/drive/drive.go index 45a79496..8d5bec0a 100644 --- a/internal/shortcut/drive/drive.go +++ b/internal/shortcut/drive/drive.go @@ -226,10 +226,6 @@ var Search = shortcut.Shortcut{ "dws drive +search --query \"合同\" --target file --extensions pdf,docx", }, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词", Required: true}, @@ -378,10 +374,6 @@ var SearchDocs = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws drive +search-docs --query \"季度汇报\""}, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词", Required: true}, diff --git a/internal/shortcut/drive/query_keyword_paramdecl_test.go b/internal/shortcut/drive/query_keyword_paramdecl_test.go deleted file mode 100644 index 48c22240..00000000 --- a/internal/shortcut/drive/query_keyword_paramdecl_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// 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 drive - -import ( - "testing" - - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" -) - -func TestCrossPlatformCoverageQueryToKeywordParamDecls(t *testing.T) { - for _, tc := range []struct { - name string - sc shortcut.Shortcut - }{ - {"+search", Search}, - {"+search-docs", SearchDocs}, - } { - assertQueryKeywordParamDecl(t, tc.name, tc.sc) - if len(shortcut.FromShortcut(tc.sc).Schema.Parameters) == 0 { - t.Fatalf("%s Schema.Parameters empty after FromShortcut", tc.name) - } - } -} - -func assertQueryKeywordParamDecl(t *testing.T, name string, sc shortcut.Shortcut) { - t.Helper() - for _, p := range sc.Schema.Parameters { - if p.Name == "query" && p.Property == "keyword" { - return - } - } - t.Fatalf("%s missing ParamDecl query→keyword; got %#v", name, sc.Schema.Parameters) -} diff --git a/internal/shortcut/minutes/minutes.go b/internal/shortcut/minutes/minutes.go index 959d8a47..0b646b97 100644 --- a/internal/shortcut/minutes/minutes.go +++ b/internal/shortcut/minutes/minutes.go @@ -51,10 +51,6 @@ var ListMine = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws minutes +list-mine --query \"周会\" --limit 10"}, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "关键字筛选"}, @@ -90,10 +86,6 @@ var ListShared = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws minutes +list-shared --limit 20"}, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "关键字筛选"}, @@ -129,10 +121,6 @@ var ListAll = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws minutes +list-all --query \"周会\" --limit 20"}, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "关键字筛选"}, diff --git a/internal/shortcut/minutes/query_keyword_paramdecl_test.go b/internal/shortcut/minutes/query_keyword_paramdecl_test.go deleted file mode 100644 index c8eea49f..00000000 --- a/internal/shortcut/minutes/query_keyword_paramdecl_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// 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 minutes - -import ( - "testing" - - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" -) - -func TestCrossPlatformCoverageQueryToKeywordParamDecls(t *testing.T) { - for _, tc := range []struct { - name string - sc shortcut.Shortcut - }{ - {"+list-mine", ListMine}, - {"+list-shared", ListShared}, - {"+list-all", ListAll}, - } { - assertQueryKeywordParamDecl(t, tc.name, tc.sc) - if len(shortcut.FromShortcut(tc.sc).Schema.Parameters) == 0 { - t.Fatalf("%s Schema.Parameters empty after FromShortcut", tc.name) - } - } -} - -func assertQueryKeywordParamDecl(t *testing.T, name string, sc shortcut.Shortcut) { - t.Helper() - for _, p := range sc.Schema.Parameters { - if p.Name == "query" && p.Property == "keyword" { - return - } - } - t.Fatalf("%s missing ParamDecl query→keyword; got %#v", name, sc.Schema.Parameters) -} diff --git a/internal/shortcut/smart/find_doc.go b/internal/shortcut/smart/find_doc.go index 3f5ef986..80c8e3c3 100644 --- a/internal/shortcut/smart/find_doc.go +++ b/internal/shortcut/smart/find_doc.go @@ -67,10 +67,6 @@ var FindDoc = shortcut.Shortcut{ "dws doc +find-doc --query 合同 --limit 10", }, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "按关键词搜索云文档(必填)", Required: true}, diff --git a/internal/shortcut/smart/find_file.go b/internal/shortcut/smart/find_file.go index 8cd1b0f2..972c33b7 100644 --- a/internal/shortcut/smart/find_file.go +++ b/internal/shortcut/smart/find_file.go @@ -66,10 +66,6 @@ var FindFile = shortcut.Shortcut{ "dws drive +find-file --query 合同", }, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "文件名关键词(必填)", Required: true}, diff --git a/internal/shortcut/smart/find_mail_user.go b/internal/shortcut/smart/find_mail_user.go index 5bf7bab6..82553538 100644 --- a/internal/shortcut/smart/find_mail_user.go +++ b/internal/shortcut/smart/find_mail_user.go @@ -76,10 +76,6 @@ var FindMailUser = shortcut.Shortcut{ "dws mail +find-mail-user --query alice --limit 10", }, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词(姓名/花名/邮箱片段,必填)", Required: true}, diff --git a/internal/shortcut/smart/find_record.go b/internal/shortcut/smart/find_record.go index 89f3b520..b7851ebc 100644 --- a/internal/shortcut/smart/find_record.go +++ b/internal/shortcut/smart/find_record.go @@ -62,10 +62,6 @@ var FindRecord = shortcut.Shortcut{ "dws aitable +find-record --base B --table T --query 张三", }, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "base", Type: shortcut.FlagString, Desc: "Base ID(多维表所属 base)", Required: true}, diff --git a/internal/shortcut/smart/minutes_search.go b/internal/shortcut/smart/minutes_search.go index 6dcd7053..6c78a3de 100644 --- a/internal/shortcut/smart/minutes_search.go +++ b/internal/shortcut/smart/minutes_search.go @@ -59,10 +59,6 @@ var MinutesSearch = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws minutes +minutes-search --query 周会"}, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "按关键词搜索听记(必填)", Required: true}, diff --git a/internal/shortcut/smart/query_keyword_paramdecl_test.go b/internal/shortcut/smart/query_keyword_paramdecl_test.go deleted file mode 100644 index 52280d2d..00000000 --- a/internal/shortcut/smart/query_keyword_paramdecl_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// 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 smart - -import ( - "testing" - - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" -) - -func TestCrossPlatformCoverageQueryToKeywordParamDecls(t *testing.T) { - // Execute paths that feed --query into MCP "keyword" must declare the - // mapping; flag_name_inference alone would publish property=query. - for _, tc := range []struct { - name string - sc shortcut.Shortcut - }{ - {"+find-doc", FindDoc}, - {"+find-file", FindFile}, - {"+find-mail-user", FindMailUser}, - {"+minutes-search", MinutesSearch}, - {"+search-msg", SearchMsg}, - {"+find-record", FindRecord}, - } { - var found bool - for _, p := range tc.sc.Schema.Parameters { - if p.Name == "query" && p.Property == "keyword" { - found = true - break - } - } - if !found { - t.Fatalf("%s missing ParamDecl query→keyword; got %#v", - tc.name, tc.sc.Schema.Parameters) - } - } -} diff --git a/internal/shortcut/smart/search_msg.go b/internal/shortcut/smart/search_msg.go index 7c5e6ab4..9ffc31c3 100644 --- a/internal/shortcut/smart/search_msg.go +++ b/internal/shortcut/smart/search_msg.go @@ -60,10 +60,6 @@ var SearchMsg = shortcut.Shortcut{ "dws chat +search-msg --group --message-type file --download-resources --output-dir ./downloads", }, }, - // Execute maps --query (or hidden --keyword) → MCP keyword. - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: append([]shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词"}, diff --git a/internal/shortcut/wiki/query_keyword_paramdecl_test.go b/internal/shortcut/wiki/query_keyword_paramdecl_test.go deleted file mode 100644 index e7527e6a..00000000 --- a/internal/shortcut/wiki/query_keyword_paramdecl_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// 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 wiki - -import ( - "testing" - - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut" -) - -func TestCrossPlatformCoverageQueryToKeywordParamDecls(t *testing.T) { - assertQueryKeywordParamDecl(t, "+space-search", SpaceSearch) - if len(shortcut.FromShortcut(SpaceSearch).Schema.Parameters) == 0 { - t.Fatal("+space-search Schema.Parameters empty after FromShortcut") - } -} - -func assertQueryKeywordParamDecl(t *testing.T, name string, sc shortcut.Shortcut) { - t.Helper() - for _, p := range sc.Schema.Parameters { - if p.Name == "query" && p.Property == "keyword" { - return - } - } - t.Fatalf("%s missing ParamDecl query→keyword; got %#v", name, sc.Schema.Parameters) -} diff --git a/internal/shortcut/wiki/wiki.go b/internal/shortcut/wiki/wiki.go index ece29b8c..cb8318de 100644 --- a/internal/shortcut/wiki/wiki.go +++ b/internal/shortcut/wiki/wiki.go @@ -150,10 +150,6 @@ var SpaceSearch = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws wiki +space-search --query \"产品文档\""}, }, - // Execute maps --query → MCP keyword (not inferable from flag name). - Parameters: []corecmd.ParamDecl{ - {Name: "query", Property: "keyword"}, - }, }, Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "搜索关键词", Required: true}, diff --git a/scripts/policy/check-generated-drift.sh b/scripts/policy/check-generated-drift.sh index 10085ee7..f62f86ab 100755 --- a/scripts/policy/check-generated-drift.sh +++ b/scripts/policy/check-generated-drift.sh @@ -28,20 +28,23 @@ concepts_guard="$tmp/param_concepts.json" concepts_schema_guard="$tmp/param_concepts.schema.json" cp internal/cli/param_concepts.json "$concepts_guard" cp internal/cli/param_concepts.schema.json "$concepts_schema_guard" -# Guard human-authored selection (required) and metadata (optional). -# schema_hints/metadata may be absent, an empty directory, or hold only -# reserved product shells with tools: {} / missing tools — none of those -# alone is drift. When the directory is present, keep a byte guard so -# generation cannot rewrite those shells; never fail solely because the -# directory is missing or every product shell has empty tools. +# Guard human-authored selection and metadata (both optional). +# schema_hints/selection and schema_hints/metadata may each be absent, empty, +# or hold only empty products{}/tools{} shells — none of those alone is drift. +# When a directory is present, keep a byte guard so generation cannot rewrite +# those shells; never fail solely because the directory is missing. metadata_dir="internal/cli/schema_hints/metadata" metadata_guard="" if [ -d "$metadata_dir" ]; then metadata_guard="$tmp/metadata-hints" cp -R "$metadata_dir" "$metadata_guard" fi -selection_guard="$tmp/selection-hints" -cp -R internal/cli/schema_hints/selection "$selection_guard" +selection_dir="internal/cli/schema_hints/selection" +selection_guard="" +if [ -d "$selection_dir" ]; then + selection_guard="$tmp/selection-hints" + cp -R "$selection_dir" "$selection_guard" +fi catalog_tmp="$tmp/schema_catalog" catalog_tmp_second="$tmp/schema_catalog-second" @@ -103,8 +106,18 @@ elif [ -d "$metadata_dir" ]; then exit 1 fi -if ! diff -qr internal/cli/schema_hints/selection "$selection_guard" >/dev/null; then - printf '%s\n' 'generation modified reviewed input internal/cli/schema_hints/selection' >&2 +# Optional selection: same presence/absence contract as metadata. +if [ -n "$selection_guard" ]; then + if [ ! -d "$selection_dir" ]; then + printf '%s\n' 'generation removed reviewed input internal/cli/schema_hints/selection' >&2 + exit 1 + fi + if ! diff -qr "$selection_dir" "$selection_guard" >/dev/null; then + printf '%s\n' 'generation modified reviewed input internal/cli/schema_hints/selection' >&2 + exit 1 + fi +elif [ -d "$selection_dir" ]; then + printf '%s\n' 'generation created reviewed input internal/cli/schema_hints/selection' >&2 exit 1 fi diff --git a/scripts/policy/check-schema-catalog.sh b/scripts/policy/check-schema-catalog.sh index fdfe635e..eed524b9 100755 --- a/scripts/policy/check-schema-catalog.sh +++ b/scripts/policy/check-schema-catalog.sh @@ -119,7 +119,17 @@ fi if ! jq -e --arg registry_count "$registry_count" ' (.tools | length) == ($registry_count | tonumber) and - all(.catalog.products[]; ((.agent_summary // "") | length) > 0) and + all(.catalog.products[]; + ((.agent_summary // "") | length) > 0 and + (has("use_when") and (.use_when | type) == "array" and (.use_when | length) > 0) and + (has("avoid_when") and (.avoid_when | type) == "array" and (.avoid_when | length) > 0) and + ( + ((.field_provenance.agent_summary.source // "") | test("schema_hints/selection/")) or + (.field_provenance.agent_summary.precedence // "") == "contract_final" or + (.field_provenance.agent_summary.source // "") == "corecmd.contract" or + (.field_provenance.agent_summary.source // "") == "cli.product_decl" + ) + ) and all(.tools[]; ((.agent_summary // "") | length) > 0 and (.effect == "read" or .effect == "write" or .effect == "destructive") and @@ -146,7 +156,9 @@ if ! jq -e --arg registry_count "$registry_count" ' else false end) and ( ((.agent_source_refs // []) | map(test("schema_hints/selection/")) | any) or - .field_provenance.agent_summary.source == "corecmd.contract" + .field_provenance.agent_summary.source == "corecmd.contract" or + .field_provenance.agent_summary.source == "cli.product_decl" or + (.field_provenance.agent_summary.precedence // "") == "contract_final" ) ) ' "$catalog" >/dev/null; then