Files
dingtalk-workspace-cli/internal/cli/command_meta.go
T
玉澜andCursor 75bd1f1447 refactor(cmdcore): rename SchemaDecl to ContractDecl and unify register seam
SchemaDecl confused authoring with Catalog/ToolSpec delivery. Authors now
declare ContractDecl (nested contract.* types) on Spec/LeafSpec/Shortcut;
AttachContract registers only through cli.RegisterRuntimeContractFinal, and
description provenance stamps cobra_help when Long wins.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 20:33:45 +08:00

182 lines
6.2 KiB
Go

// 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.
// command_meta.go provides the unified metadata consumption API. All runtime
// consumers (help, schema, agent selection, skill generation) call ResolveMeta
// to get a CommandMeta struct — one function, one struct, no need to know which
// of the 6 generation layers a field comes from.
//
// This is the "simple consumption" half of the generation/consumption split:
// - Generation (gen.go + internal/generator/): 6 inputs → catalog snapshot.
// - Consumption (this file): catalog snapshot → ResolveMeta → CommandMeta.
package cli
import (
"sort"
"strings"
"sync"
)
// CommandMeta is the complete runtime metadata view for a single command.
// Consumers read this struct; they never touch the raw catalog maps.
type CommandMeta struct {
Identity CommandIdentity
Safety CommandSafety
Selection CommandSelection
}
// CommandIdentity is the stable identity of a command.
type CommandIdentity struct {
CLIPath string // "dev app delete"
Canonical string // "dev.delete_dev_app"
Aliases []string // ["search", ...]
ProductID string // "devapp"
Title string // one-line description
}
// CommandSelection is the agent-facing selection metadata.
type CommandSelection struct {
AgentSummary string
UseWhen []string
AvoidWhen []string
Examples []string
}
var (
metaByCLIPathOnce sync.Once
metaByCLIPath map[string]CommandMeta
)
// initMetaByCLIPath builds the cli_path → CommandMeta lookup from the embedded
// catalog. Runs once (sync.Once); the catalog is already decoded at package init.
func initMetaByCLIPath() {
metaByCLIPath = buildMetaByCLIPath(embeddedSchemaCatalog())
}
// buildMetaByCLIPath constructs the lookup from a loaded catalog.
// Prefer the typed Registry (production cold-start path). Fall back to
// Snapshot.Tools maps for unit tests that synthesize untyped fixtures.
func buildMetaByCLIPath(loaded loadedSchemaCatalog) map[string]CommandMeta {
if len(loaded.Registry.Products) > 0 {
return buildMetaByCLIPathFromRegistry(loaded.Registry)
}
return buildMetaByCLIPathFromSnapshotTools(loaded.Snapshot.Tools)
}
func buildMetaByCLIPathFromRegistry(registry SchemaRegistry) map[string]CommandMeta {
lookup := make(map[string]CommandMeta)
metas := make([]CommandMeta, 0, 64)
for _, product := range registry.Products {
for _, tool := range product.Tools {
cliPath := strings.TrimSpace(tool.Identity.CLIPath)
if cliPath == "" {
continue
}
meta := CommandMeta{
Identity: CommandIdentity{
CLIPath: cliPath,
Canonical: tool.Identity.CanonicalPath,
Aliases: append([]string(nil), tool.Identity.Aliases...),
ProductID: tool.Identity.ProductID,
Title: tool.Title,
},
Safety: CommandSafety{
Effect: tool.Safety.Effect,
Risk: tool.Safety.Risk,
Confirmation: tool.Safety.Confirmation,
Idempotency: tool.Safety.Idempotency,
},
Selection: CommandSelection{
AgentSummary: tool.Selection.AgentSummary,
UseWhen: append([]string(nil), tool.Selection.UseWhen...),
AvoidWhen: append([]string(nil), tool.Selection.AvoidWhen...),
Examples: append([]string(nil), tool.Selection.Examples...),
},
}
lookup[cliPath] = meta
metas = append(metas, meta)
}
}
return registerCommandMetaAliases(lookup, metas)
}
func buildMetaByCLIPathFromSnapshotTools(tools map[string]map[string]any) map[string]CommandMeta {
lookup := make(map[string]CommandMeta)
if tools == nil {
return lookup
}
metas := make([]CommandMeta, 0, len(tools))
for _, tool := range tools {
cliPath := schemaString(tool["cli_path"])
if cliPath == "" {
continue
}
meta := CommandMeta{
Identity: CommandIdentity{
CLIPath: cliPath,
Canonical: schemaString(tool["canonical_path"]),
Aliases: schemaStringSlice(tool["aliases"]),
ProductID: schemaString(tool["product_id"]),
Title: schemaString(tool["title"]),
},
Safety: CommandSafety{
Effect: schemaString(tool["effect"]),
Risk: schemaString(tool["risk"]),
Confirmation: schemaString(tool["confirmation"]),
Idempotency: schemaString(tool["idempotency"]),
},
Selection: CommandSelection{
AgentSummary: schemaString(tool["agent_summary"]),
UseWhen: schemaStringSlice(tool["use_when"]),
AvoidWhen: schemaStringSlice(tool["avoid_when"]),
Examples: schemaStringSlice(tool["examples"]),
},
}
lookup[cliPath] = meta
metas = append(metas, meta)
}
return registerCommandMetaAliases(lookup, metas)
}
// registerCommandMetaAliases fills compat alias paths. Primary paths always
// win; alias-vs-alias collisions resolve to the owner with the
// lexicographically smallest primary cli_path (map iteration is unstable).
func registerCommandMetaAliases(lookup map[string]CommandMeta, metas []CommandMeta) map[string]CommandMeta {
sort.Slice(metas, func(i, j int) bool {
return metas[i].Identity.CLIPath < metas[j].Identity.CLIPath
})
for _, meta := range metas {
for _, alias := range meta.Identity.Aliases {
alias = strings.TrimSpace(alias)
if alias == "" || alias == meta.Identity.CLIPath {
continue
}
if _, exists := lookup[alias]; !exists {
lookup[alias] = meta
}
}
}
return lookup
}
// ResolveMeta returns the complete metadata for a command identified by its CLI
// path (e.g. "dev app delete") or one of its compat aliases (e.g. "report list"
// for "report inbox list"). Returns ok=false for commands not in the embedded
// catalog (utility commands, hidden commands, shortcuts).
func ResolveMeta(cliPath string) (CommandMeta, bool) {
metaByCLIPathOnce.Do(initMetaByCLIPath)
m, ok := metaByCLIPath[strings.TrimSpace(cliPath)]
return m, ok
}