Rename misleading public Embedded* loaders to Reviewed/Load APIs, keep fail-closed HintsDir/-hints valves, and centralize wire provenance string literals behind named consts without changing Catalog values. Co-authored-by: Cursor <cursoragent@cursor.com>
2940 lines
108 KiB
Go
2940 lines
108 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.
|
||
|
||
package agentmetadata
|
||
|
||
import (
|
||
"bufio"
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io/fs"
|
||
"os"
|
||
"path/filepath"
|
||
"regexp"
|
||
"sort"
|
||
"strings"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
|
||
)
|
||
|
||
const CurrentVersion = 1
|
||
|
||
type File struct {
|
||
Version int `json:"version"`
|
||
SourceHash string `json:"source_hash"`
|
||
SurfaceHash string `json:"surface_hash,omitempty"`
|
||
Coverage Coverage `json:"coverage"`
|
||
Products map[string]ProductMetadata `json:"products"`
|
||
Tools map[string]ToolMetadata `json:"tools"`
|
||
}
|
||
|
||
type Coverage struct {
|
||
SurfaceProducts int `json:"surface_products,omitempty"`
|
||
ProductsWithMetadata int `json:"products_with_metadata"`
|
||
SurfaceTools int `json:"surface_tools,omitempty"`
|
||
ToolsWithMetadata int `json:"tools_with_metadata"`
|
||
ToolsWithSummary int `json:"tools_with_agent_summary,omitempty"`
|
||
ToolsWithUseWhen int `json:"tools_with_use_when,omitempty"`
|
||
ToolsWithAvoidWhen int `json:"tools_with_avoid_when,omitempty"`
|
||
ToolsWithExamples int `json:"tools_with_examples,omitempty"`
|
||
ToolsWithInterfaceMode int `json:"tools_with_interface_mode,omitempty"`
|
||
UnmatchedSkillTools int `json:"unmatched_skill_tools,omitempty"`
|
||
UnreviewedSkillTools int `json:"unreviewed_skill_tools,omitempty"`
|
||
}
|
||
|
||
type ProductMetadata struct {
|
||
AgentSummary string `json:"agent_summary,omitempty"`
|
||
AgentSummarySource string `json:"agent_summary_source,omitempty"`
|
||
UseWhen []string `json:"use_when,omitempty"`
|
||
AvoidWhen []string `json:"avoid_when,omitempty"`
|
||
SourceRefs []string `json:"source_refs,omitempty"`
|
||
FieldProvenance map[string]FieldProvenance `json:"field_provenance,omitempty"`
|
||
agentSummaryPresent bool
|
||
agentSummaryRank int
|
||
agentSummaryOrigin string
|
||
useWhenRank int
|
||
useWhenOrigin string
|
||
useWhenPresent bool
|
||
avoidWhenRank int
|
||
avoidWhenOrigin string
|
||
avoidWhenPresent bool
|
||
fieldCandidates map[string][]FieldCandidateProvenance
|
||
}
|
||
|
||
// MarshalJSON preserves the distinction between an omitted authored list and
|
||
// an explicitly authored empty list. The latter is a real precedence value: it
|
||
// intentionally clears a lower-ranked non-empty list and must be emitted as
|
||
// [] rather than being removed by omitempty.
|
||
func (metadata ProductMetadata) MarshalJSON() ([]byte, error) {
|
||
type wire ProductMetadata
|
||
encoded, err := json.Marshal(wire(metadata))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var object map[string]json.RawMessage
|
||
_ = json.Unmarshal(encoded, &object)
|
||
for _, list := range []struct {
|
||
name string
|
||
value []string
|
||
present bool
|
||
}{
|
||
{"use_when", metadata.UseWhen, metadata.useWhenPresent},
|
||
{"avoid_when", metadata.AvoidWhen, metadata.avoidWhenPresent},
|
||
} {
|
||
if !listIsPresent(list.value, list.present) {
|
||
continue
|
||
}
|
||
value, _ := json.Marshal(normalizeAuthoredStrings(list.value))
|
||
object[list.name] = value
|
||
}
|
||
return json.Marshal(object)
|
||
}
|
||
|
||
// InterfaceRef links a stable public command to the MCP operation that
|
||
// implements it. It is interface identity, not runtime endpoint discovery.
|
||
type InterfaceRef struct {
|
||
ProductID string `json:"product_id"`
|
||
RPCName string `json:"rpc_name"`
|
||
}
|
||
|
||
// FieldProvenance identifies the winning source and the deterministic rule
|
||
// used to resolve one final Agent-facing field.
|
||
type FieldProvenance struct {
|
||
Value any `json:"value"`
|
||
Source string `json:"source"`
|
||
Precedence string `json:"precedence"`
|
||
Resolution string `json:"resolution"`
|
||
ReviewReason string `json:"review_reason,omitempty"`
|
||
Candidates []FieldCandidateProvenance `json:"candidates"`
|
||
OverriddenCandidates []FieldCandidateProvenance `json:"overridden_candidates,omitempty"`
|
||
}
|
||
|
||
// FieldCandidateProvenance preserves a non-winning input so precedence
|
||
// decisions remain auditable in the generated Agent contract.
|
||
type FieldCandidateProvenance struct {
|
||
Value any `json:"value"`
|
||
Source string `json:"source"`
|
||
Precedence string `json:"precedence"`
|
||
Selected bool `json:"selected"`
|
||
ReviewReason string `json:"review_reason,omitempty"`
|
||
}
|
||
|
||
type ToolMetadata struct {
|
||
AgentSummary string `json:"agent_summary,omitempty"`
|
||
AgentSummarySource string `json:"agent_summary_source,omitempty"`
|
||
UseWhen []string `json:"use_when,omitempty"`
|
||
AvoidWhen []string `json:"avoid_when,omitempty"`
|
||
Prerequisites []string `json:"prerequisites,omitempty"`
|
||
Tips []string `json:"tips,omitempty"`
|
||
Effect string `json:"effect,omitempty"`
|
||
EffectSource string `json:"effect_source,omitempty"`
|
||
Risk string `json:"risk,omitempty"`
|
||
Confirmation string `json:"confirmation,omitempty"`
|
||
Idempotency string `json:"idempotency,omitempty"`
|
||
WorkflowRefs []string `json:"workflow_refs,omitempty"`
|
||
Examples []string `json:"examples,omitempty"`
|
||
Reviewed *bool `json:"reviewed,omitempty"`
|
||
SourceRefs []string `json:"source_refs,omitempty"`
|
||
InterfaceRef *InterfaceRef `json:"interface_ref,omitempty"`
|
||
InterfaceMode string `json:"interface_mode,omitempty"`
|
||
Availability string `json:"availability,omitempty"`
|
||
InterfaceReason string `json:"interface_reason,omitempty"`
|
||
FieldProvenance map[string]FieldProvenance `json:"field_provenance,omitempty"`
|
||
agentSummaryPresent bool
|
||
agentSummaryRank int
|
||
agentSummaryOrigin string
|
||
useWhenRank int
|
||
useWhenOrigin string
|
||
useWhenPresent bool
|
||
avoidWhenRank int
|
||
avoidWhenOrigin string
|
||
avoidWhenPresent bool
|
||
prerequisitesRank int
|
||
prerequisitesOrigin string
|
||
prerequisitesPresent bool
|
||
tipsRank int
|
||
tipsOrigin string
|
||
tipsPresent bool
|
||
workflowRefsRank int
|
||
workflowRefsOrigin string
|
||
workflowRefsPresent bool
|
||
examplesRank int
|
||
examplesOrigin string
|
||
examplesPresent bool
|
||
effectPresent bool
|
||
riskPresent bool
|
||
confirmationPresent bool
|
||
idempotencyPresent bool
|
||
idempotencyRank int
|
||
idempotencyOrigin string
|
||
reviewedRank int
|
||
reviewedOrigin string
|
||
interfaceRefPresent bool
|
||
interfaceRefRank int
|
||
interfaceRefOrigin string
|
||
interfaceModePresent bool
|
||
interfaceModeRank int
|
||
interfaceModeOrigin string
|
||
availabilityPresent bool
|
||
availabilityRank int
|
||
availabilityOrigin string
|
||
interfaceReasonPresent bool
|
||
interfaceReasonRank int
|
||
interfaceReasonOrigin string
|
||
effectRank int
|
||
effectOrigin string
|
||
riskRank int
|
||
riskOrigin string
|
||
confirmationRank int
|
||
confirmationOrigin string
|
||
fieldCandidates map[string][]FieldCandidateProvenance
|
||
}
|
||
|
||
// MarshalJSON preserves explicit empty authored lists for the same reason as
|
||
// ProductMetadata.MarshalJSON. Unset lists remain omitted.
|
||
func (metadata ToolMetadata) MarshalJSON() ([]byte, error) {
|
||
type wire ToolMetadata
|
||
encoded, err := json.Marshal(wire(metadata))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var object map[string]json.RawMessage
|
||
_ = json.Unmarshal(encoded, &object)
|
||
for _, list := range []struct {
|
||
name string
|
||
value []string
|
||
present bool
|
||
}{
|
||
{"use_when", metadata.UseWhen, metadata.useWhenPresent},
|
||
{"avoid_when", metadata.AvoidWhen, metadata.avoidWhenPresent},
|
||
{"prerequisites", metadata.Prerequisites, metadata.prerequisitesPresent},
|
||
{"tips", metadata.Tips, metadata.tipsPresent},
|
||
{"workflow_refs", metadata.WorkflowRefs, metadata.workflowRefsPresent},
|
||
{"examples", metadata.Examples, metadata.examplesPresent},
|
||
} {
|
||
if !listIsPresent(list.value, list.present) {
|
||
continue
|
||
}
|
||
value, _ := json.Marshal(normalizeAuthoredStrings(list.value))
|
||
object[list.name] = value
|
||
}
|
||
return json.Marshal(object)
|
||
}
|
||
|
||
const (
|
||
// Scalar fields use one source-only precedence for selection, safety, and
|
||
// interface disposition. Values never influence precedence: a reviewed
|
||
// explicit hint may intentionally upgrade or downgrade any scalar.
|
||
selectionRankDefault = 0
|
||
selectionRankMCPFallback = 10
|
||
selectionRankSkill = 100
|
||
selectionRankUnreviewedExplicit = 150
|
||
selectionRankImported = 200
|
||
selectionRankExplicit = 300
|
||
selectionRankReviewedExplicit = 400
|
||
selectionRankReviewedManual = 500
|
||
// Contract final declarations (corecmd.ContractDecl, registered in-process)
|
||
// outrank every file/manual source: the declaration in reviewed code is
|
||
// the final data source for declared tools.
|
||
selectionRankContractFinal = 600
|
||
|
||
selectionPrecedenceDefault = "inference_or_default"
|
||
selectionPrecedenceMCPFallback = "mcp_fallback"
|
||
selectionPrecedenceSkill = "skill"
|
||
selectionPrecedenceUnreviewedExplicit = "unreviewed_explicit"
|
||
selectionPrecedenceImported = "imported"
|
||
selectionPrecedenceExplicit = "explicit"
|
||
selectionPrecedenceReviewedExplicit = "reviewed_explicit"
|
||
selectionPrecedenceReviewedManual = cli.ProvenanceReviewedManual
|
||
selectionPrecedenceContractFinal = "contract_final"
|
||
)
|
||
|
||
// contractFinalOrigin labels every candidate sourced from an in-process
|
||
// Contract final declaration. It is not a file path; the declaration lives in
|
||
// reviewed command source code (corecmd.ContractDecl).
|
||
const contractFinalOrigin = "corecmd.contract"
|
||
|
||
type Options struct {
|
||
Root string
|
||
SkillPath string
|
||
ProductsDir string
|
||
IntentGuidePath string
|
||
// HintsDir is a fail-closed anti-regression valve. The field name and the
|
||
// "schema_hints/" error text are intentional: policy and callers still probe
|
||
// this retired path. Non-empty values must keep failing; do not rename away.
|
||
HintsDir string
|
||
// ManualHintsPath is a fail-closed anti-regression valve for the retired
|
||
// manual-hints file path. Keep the field name; non-empty values must fail.
|
||
ManualHintsPath string
|
||
InterfaceMetadataPath string
|
||
MaxExamples int
|
||
MaxInterfaceSummaryRunes int
|
||
ToolPaths map[string]string
|
||
CanonicalToolPaths map[string]string
|
||
BoundCommands cli.BoundCommandRegistry
|
||
ProductIDs map[string]bool
|
||
SurfaceHash string
|
||
SurfaceToolCount int
|
||
}
|
||
|
||
type Stats struct {
|
||
SourceFiles int
|
||
Products int
|
||
Tools int
|
||
ToolIntents int
|
||
Examples int
|
||
RiskRules int
|
||
InterfaceMetadata *InterfaceMetadataAudit
|
||
UnmatchedTools int
|
||
SourceProducts []string
|
||
SkillProductsOutsideSurface []string
|
||
SurfaceProductsWithoutRouting []string
|
||
UnmatchedReferences []UnmatchedReference
|
||
referenceReviews map[string]ReferenceReview
|
||
unreviewedSkillTools int
|
||
}
|
||
|
||
// UnmatchedReference identifies one Skill command reference that cannot be
|
||
// resolved against the versioned command surface. It is emitted only in the
|
||
// build-time audit and is not embedded in the runtime Agent schema.
|
||
type UnmatchedReference struct {
|
||
ToolPath string `json:"tool_path"`
|
||
Source string `json:"source,omitempty"`
|
||
Line int `json:"line,omitempty"`
|
||
Candidates []string `json:"candidates,omitempty"`
|
||
Review *ReferenceReview `json:"review,omitempty"`
|
||
}
|
||
|
||
// ReferenceReview is an optional disposition of a Skill command reference that
|
||
// is not a current public leaf. Production no longer loads reviewed HintFile
|
||
// reference_review maps; unmatched Skill paths are recorded in the build-time
|
||
// audit without requiring a reviewed disposition.
|
||
type ReferenceReview struct {
|
||
Status string `json:"status"`
|
||
Target string `json:"target,omitempty"`
|
||
Reason string `json:"reason"`
|
||
}
|
||
|
||
// Audit contains build-time diagnostics that are intentionally kept separate
|
||
// from the runtime Agent metadata contract.
|
||
type Audit struct {
|
||
Version int `json:"version"`
|
||
SourceHash string `json:"source_hash"`
|
||
SurfaceHash string `json:"surface_hash,omitempty"`
|
||
SourceFiles int `json:"source_files"`
|
||
InterfaceMetadata *InterfaceMetadataAudit `json:"interface_metadata,omitempty"`
|
||
Coverage Coverage `json:"coverage"`
|
||
SourceProducts []string `json:"source_products,omitempty"`
|
||
SkillProductsOutsideSurface []string `json:"skill_products_outside_surface,omitempty"`
|
||
SurfaceProductsWithoutRouting []string `json:"surface_products_without_routing_metadata,omitempty"`
|
||
UnmatchedReferences []UnmatchedReference `json:"unmatched_references,omitempty"`
|
||
}
|
||
|
||
type sourceFile struct {
|
||
path string
|
||
display string
|
||
data []byte
|
||
}
|
||
|
||
type commandReference struct {
|
||
text string
|
||
line int
|
||
commentIntent string
|
||
}
|
||
|
||
type sourceLocation struct {
|
||
source string
|
||
line int
|
||
}
|
||
|
||
type sourceTracker map[string]map[string]sourceLocation
|
||
|
||
var (
|
||
quotedIntentRE = regexp.MustCompile(`用户(?:提到|说)["“]([^"”]+)["”]`)
|
||
codeSpanRE = regexp.MustCompile("`([^`]+)`")
|
||
stepCommentRE = regexp.MustCompile(`(?i)^step\s+[0-9]+\s*[::]\s*`)
|
||
)
|
||
|
||
func Generate(opts Options) (File, Stats, error) {
|
||
if len(opts.CanonicalToolPaths) == 0 || len(opts.ToolPaths) == 0 || len(opts.ProductIDs) == 0 {
|
||
return File{}, Stats{}, fmt.Errorf("complete Effective CommandRegistry projection is required")
|
||
}
|
||
if len(opts.BoundCommands.ByCanonical) != len(opts.CanonicalToolPaths) {
|
||
return File{}, Stats{}, fmt.Errorf("complete Cobra-bound CommandRegistry is required")
|
||
}
|
||
return generateFromSources(opts)
|
||
}
|
||
|
||
// generateFromSources retains the lower-precedence evidence parsers as a
|
||
// package-internal seam for focused tests. Production callers must use Generate.
|
||
// HintsDir and ManualHintsPath are retired: ProductDecl / ContractFinal own
|
||
// routing and leaf facts. Non-empty values fail closed in loadSources;
|
||
// Skill Markdown remains evidence-only.
|
||
func generateFromSources(opts Options) (File, Stats, error) {
|
||
if opts.Root == "" {
|
||
opts.Root = "."
|
||
}
|
||
if opts.MaxExamples <= 0 {
|
||
opts.MaxExamples = 2
|
||
}
|
||
files, err := loadSources(opts)
|
||
if err != nil {
|
||
return File{}, Stats{}, err
|
||
}
|
||
byDisplay := make(map[string]sourceFile, len(files))
|
||
for _, file := range files {
|
||
byDisplay[file.display] = file
|
||
}
|
||
|
||
out := File{
|
||
Version: CurrentVersion,
|
||
SourceHash: hashSources(files),
|
||
SurfaceHash: strings.TrimSpace(opts.SurfaceHash),
|
||
Products: map[string]ProductMetadata{},
|
||
Tools: map[string]ToolMetadata{},
|
||
}
|
||
stats := Stats{SourceFiles: len(files), referenceReviews: map[string]ReferenceReview{}}
|
||
origins := sourceTracker{}
|
||
|
||
skillDisplay := displayPath(opts.Root, resolvePath(opts.Root, opts.SkillPath))
|
||
if skill, ok := byDisplay[skillDisplay]; ok {
|
||
parseProductRouting(&out, string(skill.data), skill.display)
|
||
if err := parseDangerRules(&out, string(skill.data), skill.display, &stats, origins); err != nil {
|
||
return File{}, Stats{}, err
|
||
}
|
||
}
|
||
|
||
intentDisplay := displayPath(opts.Root, resolvePath(opts.Root, opts.IntentGuidePath))
|
||
if guide, ok := byDisplay[intentDisplay]; ok {
|
||
parseIntentGuide(&out, string(guide.data), guide.display, origins)
|
||
}
|
||
|
||
productFiles := make([]sourceFile, 0)
|
||
productsRoot := filepath.Clean(resolvePath(opts.Root, opts.ProductsDir))
|
||
productsPrefix := productsRoot + string(filepath.Separator)
|
||
for _, file := range files {
|
||
if strings.HasPrefix(filepath.Clean(file.path), productsPrefix) {
|
||
productFiles = append(productFiles, file)
|
||
}
|
||
}
|
||
sort.Slice(productFiles, func(i, j int) bool { return productFiles[i].display < productFiles[j].display })
|
||
sourceProducts := map[string]bool{}
|
||
for _, file := range productFiles {
|
||
body := string(file.data)
|
||
references := collectCommandReferences(body)
|
||
productIDs := sourceProductIDs(file, productsRoot, references, opts.ProductIDs)
|
||
for _, productID := range productIDs {
|
||
sourceProducts[productID] = true
|
||
}
|
||
known := collectCommandPaths(productIDs, references)
|
||
parseToolIntents(&out, productIDs, known, body, file.display, origins)
|
||
if err := parseExamples(&out, known, references, file.display, opts.MaxExamples, origins); err != nil {
|
||
return File{}, Stats{}, err
|
||
}
|
||
}
|
||
if err := applyInterfaceMetadataFallback(&out, byDisplay, opts, &stats, origins); err != nil {
|
||
return File{}, Stats{}, err
|
||
}
|
||
|
||
normalizeFile(&out, opts.MaxExamples)
|
||
for productID := range out.Products {
|
||
sourceProducts[productID] = true
|
||
}
|
||
for toolPath := range out.Tools {
|
||
if productID := metadataProductID(toolPath); productID != "" {
|
||
sourceProducts[productID] = true
|
||
}
|
||
}
|
||
stats.SourceProducts = sortedBoolKeys(sourceProducts)
|
||
if len(opts.ProductIDs) > 0 {
|
||
for _, productID := range stats.SourceProducts {
|
||
if !opts.ProductIDs[productID] {
|
||
stats.SkillProductsOutsideSurface = append(stats.SkillProductsOutsideSurface, productID)
|
||
}
|
||
}
|
||
}
|
||
if err := reconcileSurface(&out, opts, &stats, origins); err != nil {
|
||
return File{}, Stats{}, err
|
||
}
|
||
if err := applyContractFinalDeclarations(&out, opts); err != nil {
|
||
return File{}, Stats{}, err
|
||
}
|
||
seedEffectiveToolProjection(&out, opts.ToolPaths)
|
||
if err := validateEffectiveToolProjection(out, opts); err != nil {
|
||
return File{}, Stats{}, err
|
||
}
|
||
normalizeFile(&out, opts.MaxExamples)
|
||
if err := validateToolFieldCandidateConflicts(out); err != nil {
|
||
return File{}, Stats{}, err
|
||
}
|
||
finalizeInterfaceDispositions(&out)
|
||
if err := validateInterfaceDispositions(out); err != nil {
|
||
return File{}, Stats{}, err
|
||
}
|
||
if len(opts.ProductIDs) > 0 {
|
||
for productID := range opts.ProductIDs {
|
||
if _, ok := out.Products[productID]; !ok {
|
||
stats.SurfaceProductsWithoutRouting = append(stats.SurfaceProductsWithoutRouting, productID)
|
||
}
|
||
}
|
||
sort.Strings(stats.SurfaceProductsWithoutRouting)
|
||
}
|
||
stats.Products = len(out.Products)
|
||
stats.Tools = len(out.Tools)
|
||
toolsWithSummary, toolsWithUseWhen, toolsWithAvoidWhen := 0, 0, 0
|
||
toolsWithExamples, toolsWithInterfaceMode := 0, 0
|
||
for _, metadata := range out.Tools {
|
||
stats.ToolIntents += len(metadata.UseWhen)
|
||
stats.Examples += len(metadata.Examples)
|
||
if strings.TrimSpace(metadata.AgentSummary) != "" {
|
||
toolsWithSummary++
|
||
}
|
||
// Coverage is about a resolved field, not truthiness. A reviewed [] is
|
||
// an authored winner that intentionally clears lower-priority guidance.
|
||
if listIsPresent(metadata.UseWhen, metadata.useWhenPresent) {
|
||
toolsWithUseWhen++
|
||
}
|
||
if listIsPresent(metadata.AvoidWhen, metadata.avoidWhenPresent) {
|
||
toolsWithAvoidWhen++
|
||
}
|
||
if listIsPresent(metadata.Examples, metadata.examplesPresent) {
|
||
toolsWithExamples++
|
||
}
|
||
if strings.TrimSpace(metadata.InterfaceMode) != "" {
|
||
toolsWithInterfaceMode++
|
||
}
|
||
}
|
||
out.Coverage = Coverage{
|
||
SurfaceProducts: len(opts.ProductIDs),
|
||
ProductsWithMetadata: len(out.Products),
|
||
SurfaceTools: opts.SurfaceToolCount,
|
||
ToolsWithMetadata: len(out.Tools),
|
||
ToolsWithSummary: toolsWithSummary,
|
||
ToolsWithUseWhen: toolsWithUseWhen,
|
||
ToolsWithAvoidWhen: toolsWithAvoidWhen,
|
||
ToolsWithExamples: toolsWithExamples,
|
||
ToolsWithInterfaceMode: toolsWithInterfaceMode,
|
||
UnmatchedSkillTools: stats.UnmatchedTools,
|
||
UnreviewedSkillTools: stats.unreviewedSkillTools,
|
||
}
|
||
return out, stats, nil
|
||
}
|
||
|
||
func BuildAudit(file File, stats Stats) Audit {
|
||
return Audit{
|
||
Version: CurrentVersion,
|
||
SourceHash: file.SourceHash,
|
||
SurfaceHash: file.SurfaceHash,
|
||
SourceFiles: stats.SourceFiles,
|
||
InterfaceMetadata: stats.InterfaceMetadata,
|
||
Coverage: file.Coverage,
|
||
SourceProducts: append([]string(nil), stats.SourceProducts...),
|
||
SkillProductsOutsideSurface: append([]string(nil), stats.SkillProductsOutsideSurface...),
|
||
SurfaceProductsWithoutRouting: append([]string(nil), stats.SurfaceProductsWithoutRouting...),
|
||
UnmatchedReferences: append([]UnmatchedReference(nil), stats.UnmatchedReferences...),
|
||
}
|
||
}
|
||
|
||
func reconcileSurface(file *File, opts Options, stats *Stats, origins sourceTracker) error {
|
||
if len(opts.ProductIDs) > 0 {
|
||
for productID := range file.Products {
|
||
if !opts.ProductIDs[productID] {
|
||
delete(file.Products, productID)
|
||
}
|
||
}
|
||
}
|
||
if len(opts.ToolPaths) == 0 {
|
||
return nil
|
||
}
|
||
reconciled := map[string]ToolMetadata{}
|
||
sourcePaths := make([]string, 0, len(file.Tools))
|
||
for skillPath := range file.Tools {
|
||
sourcePaths = append(sourcePaths, skillPath)
|
||
}
|
||
sort.Strings(sourcePaths)
|
||
for _, skillPath := range sourcePaths {
|
||
metadata := file.Tools[skillPath]
|
||
livePath, ok := opts.ToolPaths[skillPath]
|
||
if !ok {
|
||
review, reviewed := stats.referenceReviews[skillPath]
|
||
if reviewed && review.Status == "alias" {
|
||
if target, targetOK := opts.ToolPaths[normalizeCommandPath(review.Target)]; targetOK {
|
||
existing := reconciled[target]
|
||
merged, err := mergeToolMetadata(existing, metadata, target)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
reconciled[target] = merged
|
||
continue
|
||
}
|
||
}
|
||
stats.UnmatchedTools++
|
||
if !reviewed {
|
||
stats.unreviewedSkillTools++
|
||
}
|
||
locations := origins.locations(skillPath)
|
||
if len(locations) == 0 {
|
||
locations = []sourceLocation{{}}
|
||
}
|
||
candidates := candidateToolPaths(skillPath, opts.ToolPaths, 3)
|
||
for _, location := range locations {
|
||
reference := UnmatchedReference{
|
||
ToolPath: skillPath,
|
||
Source: location.source,
|
||
Line: location.line,
|
||
Candidates: append([]string(nil), candidates...),
|
||
}
|
||
if reviewed {
|
||
value := review
|
||
reference.Review = &value
|
||
}
|
||
stats.UnmatchedReferences = append(stats.UnmatchedReferences, reference)
|
||
}
|
||
continue
|
||
}
|
||
existing := reconciled[livePath]
|
||
merged, err := mergeToolMetadata(existing, metadata, livePath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
reconciled[livePath] = merged
|
||
}
|
||
file.Tools = reconciled
|
||
sort.Slice(stats.UnmatchedReferences, func(i, j int) bool {
|
||
left, right := stats.UnmatchedReferences[i], stats.UnmatchedReferences[j]
|
||
if left.Source != right.Source {
|
||
return left.Source < right.Source
|
||
}
|
||
if left.Line != right.Line {
|
||
return left.Line < right.Line
|
||
}
|
||
return left.ToolPath < right.ToolPath
|
||
})
|
||
return nil
|
||
}
|
||
|
||
// seedEffectiveToolProjection materializes one Agent metadata record for every
|
||
// effective public command, including reviewed manual-only commands. Empty
|
||
// records remain subject to the normal defaulting and final release gates; the
|
||
// important identity invariant here is that absence can no longer be hidden
|
||
// behind a surface count.
|
||
func seedEffectiveToolProjection(file *File, toolPaths map[string]string) {
|
||
if file == nil {
|
||
return
|
||
}
|
||
if file.Tools == nil {
|
||
file.Tools = map[string]ToolMetadata{}
|
||
}
|
||
for _, rawPrimary := range toolPaths {
|
||
primary := normalizeCommandPath(rawPrimary)
|
||
if primary == "" {
|
||
continue
|
||
}
|
||
if _, exists := file.Tools[primary]; !exists {
|
||
file.Tools[primary] = ToolMetadata{}
|
||
}
|
||
}
|
||
}
|
||
|
||
// validateEffectiveToolProjection makes the EffectiveCommandRegistry
|
||
// projection fail closed. ToolPaths contains canonical paths and reviewed
|
||
// aliases mapped to their one primary CLI path; therefore its unique values
|
||
// are the exact public command set that Agent metadata must deliver.
|
||
func validateEffectiveToolProjection(file File, opts Options) error {
|
||
if len(opts.ToolPaths) == 0 {
|
||
return nil
|
||
}
|
||
expected := make(map[string]bool, len(opts.ToolPaths))
|
||
for _, rawPrimary := range opts.ToolPaths {
|
||
if primary := normalizeCommandPath(rawPrimary); primary != "" {
|
||
expected[primary] = true
|
||
}
|
||
}
|
||
missing := make([]string, 0)
|
||
for primary := range expected {
|
||
if _, ok := file.Tools[primary]; !ok {
|
||
missing = append(missing, primary)
|
||
}
|
||
}
|
||
unexpected := make([]string, 0)
|
||
for primary := range file.Tools {
|
||
if !expected[normalizeCommandPath(primary)] {
|
||
unexpected = append(unexpected, primary)
|
||
}
|
||
}
|
||
sort.Strings(missing)
|
||
sort.Strings(unexpected)
|
||
if len(missing) > 0 || len(unexpected) > 0 {
|
||
return fmt.Errorf(
|
||
"agent metadata does not exactly project the effective CommandRegistry: missing=%v unexpected=%v",
|
||
missing,
|
||
unexpected,
|
||
)
|
||
}
|
||
if opts.SurfaceToolCount > 0 && opts.SurfaceToolCount != len(expected) {
|
||
return fmt.Errorf(
|
||
"agent metadata effective CommandRegistry count %d disagrees with unique projected tools %d",
|
||
opts.SurfaceToolCount,
|
||
len(expected),
|
||
)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func mergeToolMetadata(left, right ToolMetadata, path string) (ToolMetadata, error) {
|
||
seedToolFieldCandidates(&left)
|
||
seedToolFieldCandidates(&right)
|
||
mergeFieldCandidateHistory(&left, right)
|
||
previousSummaryPresent := scalarIsPresent(left.AgentSummary, left.agentSummaryPresent)
|
||
previousSummaryRank := left.agentSummaryRank
|
||
previousSummaryOrigin := left.agentSummaryOrigin
|
||
incomingSummaryPresent := scalarIsPresent(right.AgentSummary, right.agentSummaryPresent)
|
||
incomingSummaryRank := effectiveSelectionScalarRank(right.AgentSummary, incomingSummaryPresent, right.agentSummaryRank)
|
||
incomingSummaryOrigin := firstNonEmpty(right.agentSummaryOrigin, right.AgentSummarySource)
|
||
recordStringFieldCandidate(&left, "agent_summary", right.AgentSummary, incomingSummaryPresent, incomingSummaryRank, incomingSummaryOrigin,
|
||
fieldWinnerReviewReason(right, "agent_summary", right.AgentSummary, incomingSummaryOrigin, incomingSummaryRank))
|
||
if err := mergeRankedStringValue(
|
||
&left.AgentSummary, &left.agentSummaryPresent, &left.agentSummaryRank, &left.agentSummaryOrigin,
|
||
right.AgentSummary, incomingSummaryPresent, incomingSummaryRank, incomingSummaryOrigin, path, "agent_summary",
|
||
); err != nil {
|
||
return ToolMetadata{}, err
|
||
}
|
||
incomingSummaryWon := incomingSummaryPresent &&
|
||
(!previousSummaryPresent || incomingSummaryRank > previousSummaryRank ||
|
||
(incomingSummaryRank == previousSummaryRank && left.agentSummaryOrigin != previousSummaryOrigin && left.agentSummaryOrigin == strings.TrimSpace(incomingSummaryOrigin)))
|
||
if incomingSummaryWon {
|
||
left.AgentSummarySource = right.AgentSummarySource
|
||
}
|
||
for _, list := range []struct {
|
||
name string
|
||
leftValue *[]string
|
||
leftPresent *bool
|
||
leftRank *int
|
||
leftOrigin *string
|
||
rightValue []string
|
||
rightPresent bool
|
||
rightRank int
|
||
rightOrigin string
|
||
}{
|
||
{"use_when", &left.UseWhen, &left.useWhenPresent, &left.useWhenRank, &left.useWhenOrigin, right.UseWhen, listIsPresent(right.UseWhen, right.useWhenPresent), right.useWhenRank, right.useWhenOrigin},
|
||
{"avoid_when", &left.AvoidWhen, &left.avoidWhenPresent, &left.avoidWhenRank, &left.avoidWhenOrigin, right.AvoidWhen, listIsPresent(right.AvoidWhen, right.avoidWhenPresent), right.avoidWhenRank, right.avoidWhenOrigin},
|
||
{"prerequisites", &left.Prerequisites, &left.prerequisitesPresent, &left.prerequisitesRank, &left.prerequisitesOrigin, right.Prerequisites, listIsPresent(right.Prerequisites, right.prerequisitesPresent), right.prerequisitesRank, right.prerequisitesOrigin},
|
||
{"tips", &left.Tips, &left.tipsPresent, &left.tipsRank, &left.tipsOrigin, right.Tips, listIsPresent(right.Tips, right.tipsPresent), right.tipsRank, right.tipsOrigin},
|
||
{"workflow_refs", &left.WorkflowRefs, &left.workflowRefsPresent, &left.workflowRefsRank, &left.workflowRefsOrigin, right.WorkflowRefs, listIsPresent(right.WorkflowRefs, right.workflowRefsPresent), right.workflowRefsRank, right.workflowRefsOrigin},
|
||
{"examples", &left.Examples, &left.examplesPresent, &left.examplesRank, &left.examplesOrigin, right.Examples, listIsPresent(right.Examples, right.examplesPresent), right.examplesRank, right.examplesOrigin},
|
||
} {
|
||
incomingRank := effectiveSelectionListRank(list.rightValue, list.rightPresent, list.rightRank)
|
||
if err := mergeRankedStringList(list.leftValue, list.leftPresent, list.leftRank, list.leftOrigin, list.rightValue, list.rightPresent, incomingRank, list.rightOrigin, path, list.name); err != nil {
|
||
return ToolMetadata{}, err
|
||
}
|
||
}
|
||
left.SourceRefs = append(left.SourceRefs, right.SourceRefs...)
|
||
if err := mergeEffectValue(&left, right.Effect, scalarIsPresent(right.Effect, right.effectPresent), right.EffectSource, right.effectRank, right.effectOrigin,
|
||
fieldWinnerReviewReason(right, "effect", right.Effect, right.effectOrigin, right.effectRank), path); err != nil {
|
||
return ToolMetadata{}, err
|
||
}
|
||
if err := mergeRiskValue(&left, right.Risk, scalarIsPresent(right.Risk, right.riskPresent), right.riskRank, right.riskOrigin,
|
||
fieldWinnerReviewReason(right, "risk", right.Risk, right.riskOrigin, right.riskRank), path); err != nil {
|
||
return ToolMetadata{}, err
|
||
}
|
||
if err := mergeConfirmationValue(&left, right.Confirmation, scalarIsPresent(right.Confirmation, right.confirmationPresent), right.confirmationRank, right.confirmationOrigin,
|
||
fieldWinnerReviewReason(right, "confirmation", right.Confirmation, right.confirmationOrigin, right.confirmationRank), path); err != nil {
|
||
return ToolMetadata{}, err
|
||
}
|
||
idempotencyPresent := scalarIsPresent(right.Idempotency, right.idempotencyPresent)
|
||
recordStringFieldCandidate(&left, "idempotency", right.Idempotency, idempotencyPresent, right.idempotencyRank, right.idempotencyOrigin,
|
||
fieldWinnerReviewReason(right, "idempotency", right.Idempotency, right.idempotencyOrigin, right.idempotencyRank))
|
||
if err := mergeRankedStringValue(
|
||
&left.Idempotency, &left.idempotencyPresent, &left.idempotencyRank, &left.idempotencyOrigin,
|
||
right.Idempotency, idempotencyPresent, right.idempotencyRank, right.idempotencyOrigin, path, "idempotency",
|
||
); err != nil {
|
||
return ToolMetadata{}, err
|
||
}
|
||
if right.Reviewed != nil {
|
||
recordTypedFieldCandidateValue(&left, "reviewed", *right.Reviewed, true, right.reviewedRank, right.reviewedOrigin,
|
||
fieldWinnerReviewReasonValue(right, "reviewed", *right.Reviewed, right.reviewedOrigin, right.reviewedRank))
|
||
if left.Reviewed == nil || right.reviewedRank > left.reviewedRank {
|
||
value := *right.Reviewed
|
||
left.Reviewed = &value
|
||
left.reviewedRank = right.reviewedRank
|
||
left.reviewedOrigin = right.reviewedOrigin
|
||
} else if right.reviewedRank == left.reviewedRank && *left.Reviewed == *right.Reviewed {
|
||
left.reviewedOrigin = stableSource(left.reviewedOrigin, right.reviewedOrigin)
|
||
}
|
||
}
|
||
if err := mergeRankedInterfaceRef(&left, right, path); err != nil {
|
||
return ToolMetadata{}, err
|
||
}
|
||
for _, field := range []struct {
|
||
name string
|
||
leftValue *string
|
||
leftPresent *bool
|
||
leftRank *int
|
||
leftOrigin *string
|
||
rightValue string
|
||
rightPresent bool
|
||
rightRank int
|
||
rightOrigin string
|
||
}{
|
||
{"interface_mode", &left.InterfaceMode, &left.interfaceModePresent, &left.interfaceModeRank, &left.interfaceModeOrigin, right.InterfaceMode, scalarIsPresent(right.InterfaceMode, right.interfaceModePresent), right.interfaceModeRank, right.interfaceModeOrigin},
|
||
{"availability", &left.Availability, &left.availabilityPresent, &left.availabilityRank, &left.availabilityOrigin, right.Availability, scalarIsPresent(right.Availability, right.availabilityPresent), right.availabilityRank, right.availabilityOrigin},
|
||
{"interface_reason", &left.InterfaceReason, &left.interfaceReasonPresent, &left.interfaceReasonRank, &left.interfaceReasonOrigin, right.InterfaceReason, scalarIsPresent(right.InterfaceReason, right.interfaceReasonPresent), right.interfaceReasonRank, right.interfaceReasonOrigin},
|
||
} {
|
||
rank := effectiveSelectionScalarRank(field.rightValue, field.rightPresent, field.rightRank)
|
||
recordStringFieldCandidate(&left, field.name, field.rightValue, field.rightPresent, rank, field.rightOrigin,
|
||
fieldWinnerReviewReason(right, field.name, field.rightValue, field.rightOrigin, rank))
|
||
if err := mergeRankedStringValue(
|
||
field.leftValue, field.leftPresent, field.leftRank, field.leftOrigin,
|
||
field.rightValue, field.rightPresent, rank, field.rightOrigin,
|
||
path, field.name,
|
||
); err != nil {
|
||
return ToolMetadata{}, err
|
||
}
|
||
}
|
||
left.UseWhen = normalizeAuthoredStrings(left.UseWhen)
|
||
left.AvoidWhen = normalizeAuthoredStrings(left.AvoidWhen)
|
||
left.Prerequisites = normalizeAuthoredStrings(left.Prerequisites)
|
||
left.Tips = normalizeAuthoredStrings(left.Tips)
|
||
left.WorkflowRefs = normalizeAuthoredStrings(left.WorkflowRefs)
|
||
left.Examples = normalizeAuthoredStrings(left.Examples)
|
||
preserveExplicitEmptyToolLists(&left)
|
||
left.SourceRefs = normalizedStrings(left.SourceRefs)
|
||
syncToolFieldProvenance(&left)
|
||
return left, nil
|
||
}
|
||
|
||
func seedToolFieldCandidates(metadata *ToolMetadata) {
|
||
if metadata == nil {
|
||
return
|
||
}
|
||
recordStringFieldCandidate(metadata, "agent_summary", metadata.AgentSummary, scalarIsPresent(metadata.AgentSummary, metadata.agentSummaryPresent), effectiveSelectionScalarRank(metadata.AgentSummary, metadata.agentSummaryPresent, metadata.agentSummaryRank), firstNonEmpty(metadata.agentSummaryOrigin, metadata.AgentSummarySource), "")
|
||
recordStringFieldCandidate(metadata, "effect", metadata.Effect, scalarIsPresent(metadata.Effect, metadata.effectPresent), metadata.effectRank, firstNonEmpty(metadata.effectOrigin, metadata.EffectSource), "")
|
||
recordStringFieldCandidate(metadata, "risk", metadata.Risk, scalarIsPresent(metadata.Risk, metadata.riskPresent), metadata.riskRank, metadata.riskOrigin, "")
|
||
recordStringFieldCandidate(metadata, "confirmation", metadata.Confirmation, scalarIsPresent(metadata.Confirmation, metadata.confirmationPresent), metadata.confirmationRank, metadata.confirmationOrigin, "")
|
||
recordStringFieldCandidate(metadata, "idempotency", metadata.Idempotency, scalarIsPresent(metadata.Idempotency, metadata.idempotencyPresent), metadata.idempotencyRank, metadata.idempotencyOrigin, "")
|
||
if metadata.InterfaceRef != nil || metadata.interfaceRefPresent {
|
||
rank := metadata.interfaceRefRank
|
||
if rank == selectionRankDefault {
|
||
rank = selectionRankSkill
|
||
}
|
||
var value any
|
||
if metadata.InterfaceRef != nil {
|
||
value = metadata.InterfaceRef.ProductID + "." + metadata.InterfaceRef.RPCName
|
||
}
|
||
recordTypedFieldCandidateValue(metadata, "interface_ref", value, true, rank, metadata.interfaceRefOrigin, "")
|
||
}
|
||
recordStringFieldCandidate(metadata, "interface_mode", metadata.InterfaceMode, scalarIsPresent(metadata.InterfaceMode, metadata.interfaceModePresent), effectiveSelectionScalarRank(metadata.InterfaceMode, metadata.interfaceModePresent, metadata.interfaceModeRank), metadata.interfaceModeOrigin, "")
|
||
recordStringFieldCandidate(metadata, "availability", metadata.Availability, scalarIsPresent(metadata.Availability, metadata.availabilityPresent), effectiveSelectionScalarRank(metadata.Availability, metadata.availabilityPresent, metadata.availabilityRank), metadata.availabilityOrigin, "")
|
||
recordStringFieldCandidate(metadata, "interface_reason", metadata.InterfaceReason, scalarIsPresent(metadata.InterfaceReason, metadata.interfaceReasonPresent), effectiveSelectionScalarRank(metadata.InterfaceReason, metadata.interfaceReasonPresent, metadata.interfaceReasonRank), metadata.interfaceReasonOrigin, "")
|
||
if metadata.Reviewed != nil {
|
||
recordTypedFieldCandidateValue(metadata, "reviewed", *metadata.Reviewed, true, metadata.reviewedRank, metadata.reviewedOrigin, "")
|
||
}
|
||
for _, list := range []struct {
|
||
name string
|
||
value []string
|
||
present bool
|
||
rank int
|
||
origin string
|
||
}{
|
||
{"use_when", metadata.UseWhen, listIsPresent(metadata.UseWhen, metadata.useWhenPresent), metadata.useWhenRank, metadata.useWhenOrigin},
|
||
{"avoid_when", metadata.AvoidWhen, listIsPresent(metadata.AvoidWhen, metadata.avoidWhenPresent), metadata.avoidWhenRank, metadata.avoidWhenOrigin},
|
||
{"prerequisites", metadata.Prerequisites, listIsPresent(metadata.Prerequisites, metadata.prerequisitesPresent), metadata.prerequisitesRank, metadata.prerequisitesOrigin},
|
||
{"tips", metadata.Tips, listIsPresent(metadata.Tips, metadata.tipsPresent), metadata.tipsRank, metadata.tipsOrigin},
|
||
{"workflow_refs", metadata.WorkflowRefs, listIsPresent(metadata.WorkflowRefs, metadata.workflowRefsPresent), metadata.workflowRefsRank, metadata.workflowRefsOrigin},
|
||
{"examples", metadata.Examples, listIsPresent(metadata.Examples, metadata.examplesPresent), metadata.examplesRank, metadata.examplesOrigin},
|
||
} {
|
||
recordListFieldCandidate(metadata, list.name, list.value, list.present, effectiveSelectionListRank(list.value, list.present, list.rank), list.origin, "")
|
||
}
|
||
}
|
||
|
||
func mergeRankedString(target *string, targetRank *int, targetOrigin *string, incoming string, incomingRank int, incomingOrigin, path, field string) error {
|
||
return mergeRankedStringValue(
|
||
target, nil, targetRank, targetOrigin,
|
||
incoming, strings.TrimSpace(incoming) != "", incomingRank, incomingOrigin, path, field,
|
||
)
|
||
}
|
||
|
||
// mergeRankedStringValue keeps authored presence separate from the string
|
||
// value. An explicit "" is therefore a real candidate which can clear a
|
||
// lower-ranked non-empty value. Callers that do not expose authored presence
|
||
// continue to use mergeRankedString, where an empty string means absence.
|
||
func mergeRankedStringValue(target *string, targetPresent *bool, targetRank *int, targetOrigin *string, incoming string, incomingPresent bool, incomingRank int, incomingOrigin, path, field string) error {
|
||
if !incomingPresent {
|
||
return nil
|
||
}
|
||
incoming = strings.TrimSpace(incoming)
|
||
incomingOrigin = strings.TrimSpace(incomingOrigin)
|
||
current := strings.TrimSpace(*target)
|
||
currentPresent := current != ""
|
||
if targetPresent != nil {
|
||
currentPresent = currentPresent || *targetPresent
|
||
}
|
||
if !currentPresent || incomingRank > *targetRank {
|
||
*target = incoming
|
||
if targetPresent != nil {
|
||
*targetPresent = true
|
||
}
|
||
*targetRank = incomingRank
|
||
*targetOrigin = incomingOrigin
|
||
return nil
|
||
}
|
||
if incomingRank < *targetRank {
|
||
return nil
|
||
}
|
||
if current != incoming {
|
||
return fmt.Errorf("agent metadata conflict for %s field %s at precedence %d: %q from %s conflicts with %q from %s",
|
||
path, field, incomingRank, *target, firstNonEmpty(*targetOrigin, "<unknown>"), incoming, firstNonEmpty(incomingOrigin, "<unknown>"))
|
||
}
|
||
if targetPresent != nil {
|
||
*targetPresent = true
|
||
}
|
||
*targetOrigin = stableSource(*targetOrigin, incomingOrigin)
|
||
return nil
|
||
}
|
||
|
||
func mergeRankedStringList(target *[]string, targetPresent *bool, targetRank *int, targetOrigin *string, incoming []string, incomingPresent bool, incomingRank int, incomingOrigin, path, field string) error {
|
||
if !incomingPresent {
|
||
return nil
|
||
}
|
||
incoming = normalizeAuthoredStrings(incoming)
|
||
incomingOrigin = strings.TrimSpace(incomingOrigin)
|
||
current := normalizeAuthoredStrings(*target)
|
||
currentPresent := listIsPresent(*target, *targetPresent)
|
||
if !currentPresent || incomingRank > *targetRank {
|
||
*target = cloneStringList(incoming)
|
||
*targetPresent = true
|
||
*targetRank = incomingRank
|
||
*targetOrigin = incomingOrigin
|
||
return nil
|
||
}
|
||
if incomingRank < *targetRank {
|
||
return nil
|
||
}
|
||
if incomingRank == selectionRankSkill {
|
||
*target = normalizeAuthoredStrings(append(current, incoming...))
|
||
*targetPresent = true
|
||
*targetOrigin = combinedSkillSource(*targetOrigin, incomingOrigin)
|
||
return nil
|
||
}
|
||
if !fieldValueEqual(current, incoming) {
|
||
return fmt.Errorf("agent metadata conflict for %s field %s at precedence %d: %s from %s conflicts with %s from %s",
|
||
path, field, incomingRank,
|
||
fieldValueDisplay(current), firstNonEmpty(*targetOrigin, "<unknown>"),
|
||
fieldValueDisplay(incoming), firstNonEmpty(incomingOrigin, "<unknown>"))
|
||
}
|
||
*target = cloneStringList(current)
|
||
*targetPresent = true
|
||
*targetOrigin = stableSource(*targetOrigin, incomingOrigin)
|
||
return nil
|
||
}
|
||
|
||
func effectiveSelectionRank(value string, rank int) int {
|
||
return effectiveSelectionScalarRank(value, strings.TrimSpace(value) != "", rank)
|
||
}
|
||
|
||
func effectiveSelectionScalarRank(value string, present bool, rank int) int {
|
||
if scalarIsPresent(value, present) && rank == selectionRankDefault {
|
||
return selectionRankSkill
|
||
}
|
||
return rank
|
||
}
|
||
|
||
func scalarIsPresent(value string, explicit bool) bool {
|
||
return explicit || strings.TrimSpace(value) != ""
|
||
}
|
||
|
||
func effectiveSelectionListRank(values []string, present bool, rank int) int {
|
||
if listIsPresent(values, present) && rank == selectionRankDefault {
|
||
return selectionRankSkill
|
||
}
|
||
return rank
|
||
}
|
||
|
||
func mergeRankedInterfaceRef(left *ToolMetadata, right ToolMetadata, path string) error {
|
||
if left == nil || (right.InterfaceRef == nil && !right.interfaceRefPresent) {
|
||
return nil
|
||
}
|
||
rank := right.interfaceRefRank
|
||
if rank == selectionRankDefault {
|
||
rank = selectionRankSkill
|
||
}
|
||
rightValue := interfaceRefCandidateValue(right.InterfaceRef)
|
||
recordTypedFieldCandidateValue(left, "interface_ref", rightValue, true, rank, right.interfaceRefOrigin,
|
||
fieldWinnerReviewReasonValue(right, "interface_ref", rightValue, right.interfaceRefOrigin, rank))
|
||
leftPresent := left.InterfaceRef != nil || left.interfaceRefPresent
|
||
if !leftPresent || rank > left.interfaceRefRank {
|
||
left.InterfaceRef = cloneInterfaceRef(right.InterfaceRef)
|
||
left.interfaceRefPresent = true
|
||
left.interfaceRefRank = rank
|
||
left.interfaceRefOrigin = right.interfaceRefOrigin
|
||
return nil
|
||
}
|
||
if rank < left.interfaceRefRank {
|
||
return nil
|
||
}
|
||
leftValue := interfaceRefCandidateValue(left.InterfaceRef)
|
||
if !fieldValueEqual(leftValue, rightValue) {
|
||
return fmt.Errorf("agent metadata conflict for %s field interface_ref at precedence %d: %s from %s conflicts with %s from %s",
|
||
path, rank,
|
||
fieldValueDisplay(leftValue), firstNonEmpty(left.interfaceRefOrigin, "<unknown>"),
|
||
fieldValueDisplay(rightValue), firstNonEmpty(right.interfaceRefOrigin, "<unknown>"))
|
||
}
|
||
left.interfaceRefPresent = true
|
||
left.interfaceRefOrigin = stableSource(left.interfaceRefOrigin, right.interfaceRefOrigin)
|
||
return nil
|
||
}
|
||
|
||
func interfaceRefCandidateValue(ref *InterfaceRef) any {
|
||
if ref == nil {
|
||
return nil
|
||
}
|
||
return strings.TrimSpace(ref.ProductID) + "." + strings.TrimSpace(ref.RPCName)
|
||
}
|
||
|
||
func cloneInterfaceRef(ref *InterfaceRef) *InterfaceRef {
|
||
if ref == nil {
|
||
return nil
|
||
}
|
||
value := *ref
|
||
return &value
|
||
}
|
||
|
||
func firstNonEmpty(values ...string) string {
|
||
for _, value := range values {
|
||
if value = strings.TrimSpace(value); value != "" {
|
||
return value
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func stableSource(left, right string) string {
|
||
left = strings.TrimSpace(left)
|
||
right = strings.TrimSpace(right)
|
||
if left == "" || (right != "" && right < left) {
|
||
return right
|
||
}
|
||
return left
|
||
}
|
||
|
||
func listIsPresent(values []string, explicit bool) bool {
|
||
return explicit || values != nil
|
||
}
|
||
|
||
func cloneStringList(values []string) []string {
|
||
if values == nil {
|
||
return nil
|
||
}
|
||
return append([]string{}, values...)
|
||
}
|
||
|
||
func preserveExplicitEmptyToolLists(metadata *ToolMetadata) {
|
||
if metadata == nil {
|
||
return
|
||
}
|
||
for _, list := range []struct {
|
||
value *[]string
|
||
present bool
|
||
}{
|
||
{&metadata.UseWhen, metadata.useWhenPresent},
|
||
{&metadata.AvoidWhen, metadata.avoidWhenPresent},
|
||
{&metadata.Prerequisites, metadata.prerequisitesPresent},
|
||
{&metadata.Tips, metadata.tipsPresent},
|
||
{&metadata.WorkflowRefs, metadata.workflowRefsPresent},
|
||
{&metadata.Examples, metadata.examplesPresent},
|
||
} {
|
||
if list.present && *list.value == nil {
|
||
*list.value = []string{}
|
||
}
|
||
}
|
||
}
|
||
|
||
func preserveExplicitEmptyProductLists(metadata *ProductMetadata) {
|
||
if metadata == nil {
|
||
return
|
||
}
|
||
if metadata.useWhenPresent && metadata.UseWhen == nil {
|
||
metadata.UseWhen = []string{}
|
||
}
|
||
if metadata.avoidWhenPresent && metadata.AvoidWhen == nil {
|
||
metadata.AvoidWhen = []string{}
|
||
}
|
||
}
|
||
|
||
func cloneFieldValue(value any) any {
|
||
switch typed := value.(type) {
|
||
case []string:
|
||
return cloneStringList(typed)
|
||
default:
|
||
return typed
|
||
}
|
||
}
|
||
|
||
func fieldValueKey(value any) string {
|
||
encoded, err := json.Marshal(value)
|
||
if err != nil {
|
||
return fmt.Sprintf("%v", value)
|
||
}
|
||
return string(encoded)
|
||
}
|
||
|
||
func fieldValueDisplay(value any) string {
|
||
return fieldValueKey(value)
|
||
}
|
||
|
||
func fieldStringValue(value any) string {
|
||
text, _ := value.(string)
|
||
return strings.TrimSpace(text)
|
||
}
|
||
|
||
func fieldValueEqual(left, right any) bool {
|
||
return fieldValueKey(left) == fieldValueKey(right)
|
||
}
|
||
|
||
func fieldCandidateEqual(left, right FieldCandidateProvenance) bool {
|
||
return fieldValueEqual(left.Value, right.Value) &&
|
||
left.Source == right.Source &&
|
||
left.Precedence == right.Precedence &&
|
||
left.Selected == right.Selected &&
|
||
left.ReviewReason == right.ReviewReason
|
||
}
|
||
|
||
func normalizeListFieldCandidates(metadata *ToolMetadata, maxExamples int) {
|
||
if metadata == nil {
|
||
return
|
||
}
|
||
for field, candidates := range metadata.fieldCandidates {
|
||
for index := range candidates {
|
||
values, ok := candidates[index].Value.([]string)
|
||
if !ok {
|
||
continue
|
||
}
|
||
values = normalizeAuthoredStrings(values)
|
||
if field == "examples" && maxExamples > 0 && len(values) > maxExamples {
|
||
values = values[:maxExamples]
|
||
}
|
||
candidates[index].Value = cloneStringList(values)
|
||
}
|
||
metadata.fieldCandidates[field] = candidates
|
||
}
|
||
}
|
||
|
||
func mergeEffect(metadata *ToolMetadata, incoming, effectSource string, rank int, origin, reviewReason, path string) error {
|
||
return mergeEffectValue(metadata, incoming, strings.TrimSpace(incoming) != "", effectSource, rank, origin, reviewReason, path)
|
||
}
|
||
|
||
func mergeEffectValue(metadata *ToolMetadata, incoming string, incomingPresent bool, effectSource string, rank int, origin, reviewReason, path string) error {
|
||
if metadata == nil || !incomingPresent {
|
||
return nil
|
||
}
|
||
previousValue := strings.TrimSpace(metadata.Effect)
|
||
previousPresent := scalarIsPresent(metadata.Effect, metadata.effectPresent)
|
||
previousRank := metadata.effectRank
|
||
previousOrigin := metadata.effectOrigin
|
||
recordStringFieldCandidate(metadata, "effect", incoming, true, rank, origin, reviewReason)
|
||
if err := mergeRankedStringValue(
|
||
&metadata.Effect, &metadata.effectPresent, &metadata.effectRank, &metadata.effectOrigin,
|
||
incoming, true, rank, origin, path, "effect",
|
||
); err != nil {
|
||
return err
|
||
}
|
||
if scalarIncomingWon(previousValue, previousPresent, previousRank, previousOrigin, incoming, true, rank, origin, metadata.effectOrigin) {
|
||
metadata.EffectSource = strings.TrimSpace(effectSource)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func mergeRisk(metadata *ToolMetadata, incoming string, rank int, origin, reviewReason, path string) error {
|
||
return mergeRiskValue(metadata, incoming, strings.TrimSpace(incoming) != "", rank, origin, reviewReason, path)
|
||
}
|
||
|
||
func mergeRiskValue(metadata *ToolMetadata, incoming string, incomingPresent bool, rank int, origin, reviewReason, path string) error {
|
||
if metadata == nil || !incomingPresent {
|
||
return nil
|
||
}
|
||
recordStringFieldCandidate(metadata, "risk", incoming, true, rank, origin, reviewReason)
|
||
return mergeRankedStringValue(
|
||
&metadata.Risk, &metadata.riskPresent, &metadata.riskRank, &metadata.riskOrigin,
|
||
incoming, true, rank, origin, path, "risk",
|
||
)
|
||
}
|
||
|
||
func mergeConfirmation(metadata *ToolMetadata, incoming string, rank int, origin, reviewReason, path string) error {
|
||
return mergeConfirmationValue(metadata, incoming, strings.TrimSpace(incoming) != "", rank, origin, reviewReason, path)
|
||
}
|
||
|
||
func mergeConfirmationValue(metadata *ToolMetadata, incoming string, incomingPresent bool, rank int, origin, reviewReason, path string) error {
|
||
if metadata == nil || !incomingPresent {
|
||
return nil
|
||
}
|
||
recordStringFieldCandidate(metadata, "confirmation", incoming, true, rank, origin, reviewReason)
|
||
return mergeRankedStringValue(
|
||
&metadata.Confirmation, &metadata.confirmationPresent, &metadata.confirmationRank, &metadata.confirmationOrigin,
|
||
incoming, true, rank, origin, path, "confirmation",
|
||
)
|
||
}
|
||
|
||
func scalarIncomingWon(previousValue string, previousPresent bool, previousRank int, previousOrigin, incoming string, incomingPresent bool, incomingRank int, incomingOrigin, finalOrigin string) bool {
|
||
if !incomingPresent {
|
||
return false
|
||
}
|
||
incoming = strings.TrimSpace(incoming)
|
||
if !previousPresent || incomingRank > previousRank {
|
||
return true
|
||
}
|
||
return incomingRank == previousRank && previousValue == incoming &&
|
||
strings.TrimSpace(finalOrigin) == strings.TrimSpace(incomingOrigin) &&
|
||
strings.TrimSpace(previousOrigin) != strings.TrimSpace(finalOrigin)
|
||
}
|
||
|
||
func selectionPrecedence(rank int) string {
|
||
switch rank {
|
||
case selectionRankContractFinal:
|
||
return selectionPrecedenceContractFinal
|
||
case selectionRankReviewedManual:
|
||
return selectionPrecedenceReviewedManual
|
||
case selectionRankReviewedExplicit:
|
||
return selectionPrecedenceReviewedExplicit
|
||
case selectionRankExplicit:
|
||
return selectionPrecedenceExplicit
|
||
case selectionRankImported:
|
||
return selectionPrecedenceImported
|
||
case selectionRankUnreviewedExplicit:
|
||
return selectionPrecedenceUnreviewedExplicit
|
||
case selectionRankSkill:
|
||
return selectionPrecedenceSkill
|
||
case selectionRankMCPFallback:
|
||
return selectionPrecedenceMCPFallback
|
||
default:
|
||
return selectionPrecedenceDefault
|
||
}
|
||
}
|
||
|
||
func recordFieldCandidate(metadata *ToolMetadata, field, value string, rank int, source, reviewReason string) {
|
||
recordStringFieldCandidate(metadata, field, value, strings.TrimSpace(value) != "", rank, source, reviewReason)
|
||
}
|
||
|
||
func recordStringFieldCandidate(metadata *ToolMetadata, field, value string, present bool, rank int, source, reviewReason string) {
|
||
if !present {
|
||
return
|
||
}
|
||
recordTypedFieldCandidateValue(metadata, field, strings.TrimSpace(value), true, rank, source, reviewReason)
|
||
}
|
||
|
||
func recordListFieldCandidate(metadata *ToolMetadata, field string, value []string, present bool, rank int, source, reviewReason string) {
|
||
if !listIsPresent(value, present) {
|
||
return
|
||
}
|
||
value = normalizeAuthoredStrings(value)
|
||
recordTypedFieldCandidate(metadata, field, cloneStringList(value), rank, source, reviewReason)
|
||
}
|
||
|
||
func recordProductListCandidate(metadata *ProductMetadata, field string, value []string, present bool, rank int, source string) {
|
||
if metadata == nil || !listIsPresent(value, present) {
|
||
return
|
||
}
|
||
value = normalizeAuthoredStrings(value)
|
||
if metadata.fieldCandidates == nil {
|
||
metadata.fieldCandidates = map[string][]FieldCandidateProvenance{}
|
||
}
|
||
candidate := FieldCandidateProvenance{
|
||
Value: cloneStringList(value),
|
||
Source: firstNonEmpty(source, "unknown"),
|
||
Precedence: selectionPrecedence(rank),
|
||
}
|
||
for _, existing := range metadata.fieldCandidates[field] {
|
||
if fieldCandidateEqual(existing, candidate) {
|
||
return
|
||
}
|
||
}
|
||
metadata.fieldCandidates[field] = append(metadata.fieldCandidates[field], candidate)
|
||
}
|
||
|
||
func recordProductStringCandidate(metadata *ProductMetadata, field, value string, present bool, rank int, source string) {
|
||
if metadata == nil || !present {
|
||
return
|
||
}
|
||
value = strings.TrimSpace(value)
|
||
if metadata.fieldCandidates == nil {
|
||
metadata.fieldCandidates = map[string][]FieldCandidateProvenance{}
|
||
}
|
||
candidate := FieldCandidateProvenance{
|
||
Value: value,
|
||
Source: firstNonEmpty(source, "unknown"),
|
||
Precedence: selectionPrecedence(rank),
|
||
}
|
||
for _, existing := range metadata.fieldCandidates[field] {
|
||
if fieldCandidateEqual(existing, candidate) {
|
||
return
|
||
}
|
||
}
|
||
metadata.fieldCandidates[field] = append(metadata.fieldCandidates[field], candidate)
|
||
}
|
||
|
||
func recordTypedFieldCandidate(metadata *ToolMetadata, field string, value any, rank int, source, reviewReason string) {
|
||
recordTypedFieldCandidateValue(metadata, field, value, value != nil, rank, source, reviewReason)
|
||
}
|
||
|
||
func recordTypedFieldCandidateValue(metadata *ToolMetadata, field string, value any, present bool, rank int, source, reviewReason string) {
|
||
if metadata == nil || !present {
|
||
return
|
||
}
|
||
if metadata.fieldCandidates == nil {
|
||
metadata.fieldCandidates = map[string][]FieldCandidateProvenance{}
|
||
}
|
||
candidate := FieldCandidateProvenance{
|
||
Value: cloneFieldValue(value),
|
||
Source: firstNonEmpty(source, "unknown"),
|
||
Precedence: selectionPrecedence(rank),
|
||
ReviewReason: strings.TrimSpace(reviewReason),
|
||
}
|
||
for index, existing := range metadata.fieldCandidates[field] {
|
||
if fieldValueEqual(existing.Value, candidate.Value) && existing.Source == candidate.Source && existing.Precedence == candidate.Precedence {
|
||
if existing.ReviewReason == "" && candidate.ReviewReason != "" {
|
||
metadata.fieldCandidates[field][index].ReviewReason = candidate.ReviewReason
|
||
}
|
||
return
|
||
}
|
||
}
|
||
metadata.fieldCandidates[field] = append(metadata.fieldCandidates[field], candidate)
|
||
}
|
||
|
||
func mergeFieldCandidateHistory(target *ToolMetadata, incoming ToolMetadata) {
|
||
if target == nil || len(incoming.fieldCandidates) == 0 {
|
||
return
|
||
}
|
||
if target.fieldCandidates == nil {
|
||
target.fieldCandidates = map[string][]FieldCandidateProvenance{}
|
||
}
|
||
for field, candidates := range incoming.fieldCandidates {
|
||
for _, candidate := range candidates {
|
||
exists := false
|
||
for _, current := range target.fieldCandidates[field] {
|
||
if fieldCandidateEqual(current, candidate) {
|
||
exists = true
|
||
break
|
||
}
|
||
}
|
||
if !exists {
|
||
target.fieldCandidates[field] = append(target.fieldCandidates[field], candidate)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// validateToolFieldCandidateConflicts checks every scalar and authored-list
|
||
// precedence layer, including non-winning layers. A higher-ranked candidate
|
||
// selects the final value, but it must not hide contradictory inputs at a
|
||
// lower rank.
|
||
func validateToolFieldCandidateConflicts(file File) error {
|
||
productIDs := make([]string, 0, len(file.Products))
|
||
for productID := range file.Products {
|
||
productIDs = append(productIDs, productID)
|
||
}
|
||
sort.Strings(productIDs)
|
||
for _, productID := range productIDs {
|
||
if err := validateFieldCandidateConflicts("product "+productID, file.Products[productID].fieldCandidates); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
paths := make([]string, 0, len(file.Tools))
|
||
for path := range file.Tools {
|
||
paths = append(paths, path)
|
||
}
|
||
sort.Strings(paths)
|
||
for _, path := range paths {
|
||
if err := validateFieldCandidateConflicts(path, file.Tools[path].fieldCandidates); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func validateFieldCandidateConflicts(path string, history map[string][]FieldCandidateProvenance) error {
|
||
fields := make([]string, 0, len(history))
|
||
for field := range history {
|
||
fields = append(fields, field)
|
||
}
|
||
sort.Strings(fields)
|
||
for _, field := range fields {
|
||
candidates := append([]FieldCandidateProvenance(nil), history[field]...)
|
||
sort.Slice(candidates, func(i, j int) bool {
|
||
left, right := candidates[i], candidates[j]
|
||
leftRank, rightRank := precedenceRank(left.Precedence), precedenceRank(right.Precedence)
|
||
if leftRank != rightRank {
|
||
return leftRank > rightRank
|
||
}
|
||
if left.Source != right.Source {
|
||
return left.Source < right.Source
|
||
}
|
||
if !fieldValueEqual(left.Value, right.Value) {
|
||
return fieldValueKey(left.Value) < fieldValueKey(right.Value)
|
||
}
|
||
return left.Precedence < right.Precedence
|
||
})
|
||
for start := 0; start < len(candidates); {
|
||
rank := precedenceRank(candidates[start].Precedence)
|
||
end := start + 1
|
||
for end < len(candidates) && precedenceRank(candidates[end].Precedence) == rank {
|
||
end++
|
||
}
|
||
reference := candidates[start]
|
||
for _, candidate := range candidates[start+1 : end] {
|
||
if !fieldValueEqual(candidate.Value, reference.Value) {
|
||
return fmt.Errorf(
|
||
"agent metadata conflict for %s field %s at precedence %d: %s from %s conflicts with %s from %s",
|
||
path, field, rank,
|
||
fieldValueDisplay(reference.Value), firstNonEmpty(reference.Source, "<unknown>"),
|
||
fieldValueDisplay(candidate.Value), firstNonEmpty(candidate.Source, "<unknown>"),
|
||
)
|
||
}
|
||
}
|
||
start = end
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func fieldWinnerReviewReason(metadata ToolMetadata, field, value, source string, rank int) string {
|
||
return fieldWinnerReviewReasonValue(metadata, field, strings.TrimSpace(value), source, rank)
|
||
}
|
||
|
||
func fieldWinnerReviewReasonValue(metadata ToolMetadata, field string, value any, source string, rank int) string {
|
||
precedence := selectionPrecedence(rank)
|
||
for _, candidate := range metadata.fieldCandidates[field] {
|
||
if fieldValueEqual(candidate.Value, value) && candidate.Source == firstNonEmpty(source, "unknown") && candidate.Precedence == precedence {
|
||
return candidate.ReviewReason
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func syncToolFieldProvenance(metadata *ToolMetadata) {
|
||
if metadata == nil {
|
||
return
|
||
}
|
||
for _, field := range []string{"use_when", "avoid_when", "prerequisites", "tips", "workflow_refs", "examples"} {
|
||
coalesceSkillListCandidates(metadata.fieldCandidates, field)
|
||
}
|
||
provenance := map[string]FieldProvenance{}
|
||
addValue := func(field string, value any, present bool, source string, rank int) {
|
||
if !present {
|
||
return
|
||
}
|
||
source = firstNonEmpty(source, "unknown")
|
||
reviewReason := fieldWinnerReviewReasonValue(*metadata, field, value, source, rank)
|
||
recordTypedFieldCandidateValue(metadata, field, value, true, rank, source, reviewReason)
|
||
winner := FieldCandidateProvenance{
|
||
Value: cloneFieldValue(value),
|
||
Source: source,
|
||
Precedence: selectionPrecedence(rank),
|
||
ReviewReason: reviewReason,
|
||
}
|
||
candidates := make([]FieldCandidateProvenance, 0, len(metadata.fieldCandidates[field]))
|
||
for _, candidate := range metadata.fieldCandidates[field] {
|
||
candidate.Selected = fieldValueEqual(candidate.Value, winner.Value) && candidate.Source == winner.Source && candidate.Precedence == winner.Precedence
|
||
candidates = append(candidates, candidate)
|
||
}
|
||
sort.Slice(candidates, func(i, j int) bool {
|
||
if candidates[i].Selected != candidates[j].Selected {
|
||
return candidates[i].Selected
|
||
}
|
||
leftRank := precedenceRank(candidates[i].Precedence)
|
||
rightRank := precedenceRank(candidates[j].Precedence)
|
||
if leftRank != rightRank {
|
||
return leftRank > rightRank
|
||
}
|
||
if candidates[i].Source != candidates[j].Source {
|
||
return candidates[i].Source < candidates[j].Source
|
||
}
|
||
return fieldValueKey(candidates[i].Value) < fieldValueKey(candidates[j].Value)
|
||
})
|
||
provenance[field] = FieldProvenance{
|
||
Value: cloneFieldValue(winner.Value),
|
||
Source: source,
|
||
Precedence: selectionPrecedence(rank),
|
||
Resolution: "highest_precedence",
|
||
ReviewReason: reviewReason,
|
||
Candidates: candidates,
|
||
}
|
||
}
|
||
addString := func(field, value string, present bool, source string, rank int) {
|
||
addValue(field, strings.TrimSpace(value), present, source, rank)
|
||
}
|
||
addList := func(field string, value []string, present bool, source string, rank int) {
|
||
if !listIsPresent(value, present) {
|
||
return
|
||
}
|
||
value = normalizeAuthoredStrings(value)
|
||
rank = effectiveSelectionListRank(value, true, rank)
|
||
source = firstNonEmpty(source, "unknown")
|
||
reviewReason := fieldWinnerReviewReasonValue(*metadata, field, value, source, rank)
|
||
recordListFieldCandidate(metadata, field, value, true, rank, source, reviewReason)
|
||
winner := FieldCandidateProvenance{
|
||
Value: cloneStringList(value),
|
||
Source: source,
|
||
Precedence: selectionPrecedence(rank),
|
||
ReviewReason: reviewReason,
|
||
}
|
||
candidates := make([]FieldCandidateProvenance, 0, len(metadata.fieldCandidates[field]))
|
||
for _, candidate := range metadata.fieldCandidates[field] {
|
||
candidate.Selected = fieldValueEqual(candidate.Value, winner.Value) && candidate.Source == winner.Source && candidate.Precedence == winner.Precedence
|
||
candidates = append(candidates, candidate)
|
||
}
|
||
sort.Slice(candidates, func(i, j int) bool {
|
||
if candidates[i].Selected != candidates[j].Selected {
|
||
return candidates[i].Selected
|
||
}
|
||
leftRank := precedenceRank(candidates[i].Precedence)
|
||
rightRank := precedenceRank(candidates[j].Precedence)
|
||
if leftRank != rightRank {
|
||
return leftRank > rightRank
|
||
}
|
||
if candidates[i].Source != candidates[j].Source {
|
||
return candidates[i].Source < candidates[j].Source
|
||
}
|
||
return fieldValueKey(candidates[i].Value) < fieldValueKey(candidates[j].Value)
|
||
})
|
||
provenance[field] = FieldProvenance{
|
||
Value: winner.Value,
|
||
Source: source,
|
||
Precedence: selectionPrecedence(rank),
|
||
Resolution: "highest_precedence",
|
||
ReviewReason: reviewReason,
|
||
Candidates: candidates,
|
||
}
|
||
}
|
||
addString("effect", metadata.Effect, scalarIsPresent(metadata.Effect, metadata.effectPresent), firstNonEmpty(metadata.effectOrigin, metadata.EffectSource), metadata.effectRank)
|
||
addString("risk", metadata.Risk, scalarIsPresent(metadata.Risk, metadata.riskPresent), metadata.riskOrigin, metadata.riskRank)
|
||
addString("confirmation", metadata.Confirmation, scalarIsPresent(metadata.Confirmation, metadata.confirmationPresent), metadata.confirmationOrigin, metadata.confirmationRank)
|
||
addValue("interface_ref", interfaceRefCandidateValue(metadata.InterfaceRef), metadata.InterfaceRef != nil || metadata.interfaceRefPresent, metadata.interfaceRefOrigin, metadata.interfaceRefRank)
|
||
addString("interface_mode", metadata.InterfaceMode, scalarIsPresent(metadata.InterfaceMode, metadata.interfaceModePresent), metadata.interfaceModeOrigin, metadata.interfaceModeRank)
|
||
addString("availability", metadata.Availability, scalarIsPresent(metadata.Availability, metadata.availabilityPresent), metadata.availabilityOrigin, metadata.availabilityRank)
|
||
addString("interface_reason", metadata.InterfaceReason, scalarIsPresent(metadata.InterfaceReason, metadata.interfaceReasonPresent), metadata.interfaceReasonOrigin, metadata.interfaceReasonRank)
|
||
addString("agent_summary", metadata.AgentSummary, scalarIsPresent(metadata.AgentSummary, metadata.agentSummaryPresent), firstNonEmpty(metadata.agentSummaryOrigin, metadata.AgentSummarySource), metadata.agentSummaryRank)
|
||
addString("idempotency", metadata.Idempotency, scalarIsPresent(metadata.Idempotency, metadata.idempotencyPresent), metadata.idempotencyOrigin, metadata.idempotencyRank)
|
||
if metadata.Reviewed != nil {
|
||
addValue("reviewed", *metadata.Reviewed, true, metadata.reviewedOrigin, metadata.reviewedRank)
|
||
}
|
||
addList("use_when", metadata.UseWhen, metadata.useWhenPresent, metadata.useWhenOrigin, metadata.useWhenRank)
|
||
addList("avoid_when", metadata.AvoidWhen, metadata.avoidWhenPresent, metadata.avoidWhenOrigin, metadata.avoidWhenRank)
|
||
addList("prerequisites", metadata.Prerequisites, metadata.prerequisitesPresent, metadata.prerequisitesOrigin, metadata.prerequisitesRank)
|
||
addList("tips", metadata.Tips, metadata.tipsPresent, metadata.tipsOrigin, metadata.tipsRank)
|
||
addList("workflow_refs", metadata.WorkflowRefs, metadata.workflowRefsPresent, metadata.workflowRefsOrigin, metadata.workflowRefsRank)
|
||
addList("examples", metadata.Examples, metadata.examplesPresent, metadata.examplesOrigin, metadata.examplesRank)
|
||
if len(provenance) == 0 {
|
||
metadata.FieldProvenance = nil
|
||
return
|
||
}
|
||
metadata.FieldProvenance = provenance
|
||
}
|
||
|
||
func syncProductFieldProvenance(metadata *ProductMetadata) {
|
||
if metadata == nil {
|
||
return
|
||
}
|
||
for _, field := range []string{"use_when", "avoid_when"} {
|
||
coalesceSkillListCandidates(metadata.fieldCandidates, field)
|
||
}
|
||
provenance := map[string]FieldProvenance{}
|
||
if scalarIsPresent(metadata.AgentSummary, metadata.agentSummaryPresent) {
|
||
value := strings.TrimSpace(metadata.AgentSummary)
|
||
rank := metadata.agentSummaryRank
|
||
source := firstNonEmpty(metadata.agentSummaryOrigin, metadata.AgentSummarySource, "unknown")
|
||
recordProductStringCandidate(metadata, "agent_summary", value, true, rank, source)
|
||
winner := FieldCandidateProvenance{
|
||
Value: value,
|
||
Source: source,
|
||
Precedence: selectionPrecedence(rank),
|
||
}
|
||
candidates := make([]FieldCandidateProvenance, 0, len(metadata.fieldCandidates["agent_summary"]))
|
||
for _, candidate := range metadata.fieldCandidates["agent_summary"] {
|
||
candidate.Selected = fieldValueEqual(candidate.Value, winner.Value) && candidate.Source == winner.Source && candidate.Precedence == winner.Precedence
|
||
candidates = append(candidates, candidate)
|
||
}
|
||
sort.Slice(candidates, func(i, j int) bool {
|
||
if candidates[i].Selected != candidates[j].Selected {
|
||
return candidates[i].Selected
|
||
}
|
||
leftRank, rightRank := precedenceRank(candidates[i].Precedence), precedenceRank(candidates[j].Precedence)
|
||
if leftRank != rightRank {
|
||
return leftRank > rightRank
|
||
}
|
||
if candidates[i].Source != candidates[j].Source {
|
||
return candidates[i].Source < candidates[j].Source
|
||
}
|
||
return fieldValueKey(candidates[i].Value) < fieldValueKey(candidates[j].Value)
|
||
})
|
||
provenance["agent_summary"] = FieldProvenance{
|
||
Value: winner.Value,
|
||
Source: source,
|
||
Precedence: selectionPrecedence(rank),
|
||
Resolution: "highest_precedence",
|
||
Candidates: candidates,
|
||
}
|
||
}
|
||
for _, list := range []struct {
|
||
field string
|
||
value []string
|
||
present bool
|
||
source string
|
||
rank int
|
||
}{
|
||
{"use_when", metadata.UseWhen, metadata.useWhenPresent, metadata.useWhenOrigin, metadata.useWhenRank},
|
||
{"avoid_when", metadata.AvoidWhen, metadata.avoidWhenPresent, metadata.avoidWhenOrigin, metadata.avoidWhenRank},
|
||
} {
|
||
if !listIsPresent(list.value, list.present) {
|
||
continue
|
||
}
|
||
value := normalizeAuthoredStrings(list.value)
|
||
rank := effectiveSelectionListRank(value, true, list.rank)
|
||
source := firstNonEmpty(list.source, "unknown")
|
||
recordProductListCandidate(metadata, list.field, value, true, rank, source)
|
||
winner := FieldCandidateProvenance{
|
||
Value: cloneStringList(value),
|
||
Source: source,
|
||
Precedence: selectionPrecedence(rank),
|
||
}
|
||
candidates := make([]FieldCandidateProvenance, 0, len(metadata.fieldCandidates[list.field]))
|
||
for _, candidate := range metadata.fieldCandidates[list.field] {
|
||
candidate.Selected = fieldValueEqual(candidate.Value, winner.Value) && candidate.Source == winner.Source && candidate.Precedence == winner.Precedence
|
||
candidates = append(candidates, candidate)
|
||
}
|
||
sort.Slice(candidates, func(i, j int) bool {
|
||
if candidates[i].Selected != candidates[j].Selected {
|
||
return candidates[i].Selected
|
||
}
|
||
leftRank, rightRank := precedenceRank(candidates[i].Precedence), precedenceRank(candidates[j].Precedence)
|
||
if leftRank != rightRank {
|
||
return leftRank > rightRank
|
||
}
|
||
if candidates[i].Source != candidates[j].Source {
|
||
return candidates[i].Source < candidates[j].Source
|
||
}
|
||
return fieldValueKey(candidates[i].Value) < fieldValueKey(candidates[j].Value)
|
||
})
|
||
provenance[list.field] = FieldProvenance{
|
||
Value: winner.Value,
|
||
Source: source,
|
||
Precedence: selectionPrecedence(rank),
|
||
Resolution: "highest_precedence",
|
||
Candidates: candidates,
|
||
}
|
||
}
|
||
if len(provenance) == 0 {
|
||
metadata.FieldProvenance = nil
|
||
return
|
||
}
|
||
metadata.FieldProvenance = provenance
|
||
}
|
||
|
||
func precedenceRank(precedence string) int {
|
||
switch strings.TrimSpace(precedence) {
|
||
case selectionPrecedenceContractFinal:
|
||
return selectionRankContractFinal
|
||
case selectionPrecedenceReviewedManual:
|
||
return selectionRankReviewedManual
|
||
case selectionPrecedenceReviewedExplicit:
|
||
return selectionRankReviewedExplicit
|
||
case selectionPrecedenceExplicit:
|
||
return selectionRankExplicit
|
||
case selectionPrecedenceImported:
|
||
return selectionRankImported
|
||
case selectionPrecedenceUnreviewedExplicit:
|
||
return selectionRankUnreviewedExplicit
|
||
case selectionPrecedenceSkill:
|
||
return selectionRankSkill
|
||
case selectionPrecedenceMCPFallback:
|
||
return selectionRankMCPFallback
|
||
default:
|
||
return selectionRankDefault
|
||
}
|
||
}
|
||
|
||
func loadSources(opts Options) ([]sourceFile, error) {
|
||
paths := []string{
|
||
resolvePath(opts.Root, opts.SkillPath),
|
||
resolvePath(opts.Root, opts.IntentGuidePath),
|
||
}
|
||
productPaths := []string{}
|
||
productsRoot := resolvePath(opts.Root, opts.ProductsDir)
|
||
err := filepath.WalkDir(productsRoot, func(path string, entry fs.DirEntry, walkErr error) error {
|
||
if walkErr != nil {
|
||
return walkErr
|
||
}
|
||
if entry.IsDir() || !strings.EqualFold(filepath.Ext(entry.Name()), ".md") {
|
||
return nil
|
||
}
|
||
productPaths = append(productPaths, path)
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("walk product references: %w", err)
|
||
}
|
||
paths = append(paths, productPaths...)
|
||
if hintsDir := strings.TrimSpace(opts.HintsDir); hintsDir != "" {
|
||
return nil, fmt.Errorf("schema_hints/ is retired; clear HintsDir and declare ProductDecl/ContractFinal instead (got %q)", hintsDir)
|
||
}
|
||
if manualHints := strings.TrimSpace(opts.ManualHintsPath); manualHints != "" {
|
||
return nil, fmt.Errorf("manual hints are retired; clear ManualHintsPath and declare ProductDecl/ContractFinal instead (got %q)", manualHints)
|
||
}
|
||
if strings.TrimSpace(opts.InterfaceMetadataPath) != "" {
|
||
paths = append(paths, resolvePath(opts.Root, opts.InterfaceMetadataPath))
|
||
}
|
||
sort.Strings(paths)
|
||
|
||
files := make([]sourceFile, 0, len(paths))
|
||
seen := map[string]bool{}
|
||
for _, path := range paths {
|
||
path = filepath.Clean(path)
|
||
if path == "." || seen[path] {
|
||
continue
|
||
}
|
||
seen[path] = true
|
||
data, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("read %s: %w", path, err)
|
||
}
|
||
files = append(files, sourceFile{path: path, display: displayPath(opts.Root, path), data: data})
|
||
}
|
||
return files, nil
|
||
}
|
||
|
||
func sourceProductIDs(file sourceFile, productsRoot string, references []commandReference, surfaceProducts map[string]bool) []string {
|
||
relative, err := filepath.Rel(productsRoot, file.path)
|
||
if err == nil {
|
||
parts := strings.Split(filepath.ToSlash(relative), "/")
|
||
if len(parts) > 1 && strings.TrimSpace(parts[0]) != "" {
|
||
return []string{strings.TrimSpace(parts[0])}
|
||
}
|
||
}
|
||
|
||
base := strings.TrimSuffix(filepath.Base(file.path), filepath.Ext(file.path))
|
||
commandProducts := map[string]bool{}
|
||
for _, reference := range references {
|
||
if productID := firstCommandToken(normalizeCommandPath(reference.text)); productID != "" {
|
||
commandProducts[productID] = true
|
||
}
|
||
}
|
||
if commandProducts[base] {
|
||
return []string{base}
|
||
}
|
||
if len(surfaceProducts) > 0 {
|
||
matches := []string{}
|
||
for productID := range surfaceProducts {
|
||
if base == productID || strings.HasPrefix(base, productID+"-") {
|
||
matches = append(matches, productID)
|
||
}
|
||
}
|
||
if len(matches) > 0 {
|
||
sort.Slice(matches, func(i, j int) bool {
|
||
return len(matches[i]) > len(matches[j])
|
||
})
|
||
return matches[:1]
|
||
}
|
||
}
|
||
if len(commandProducts) > 0 {
|
||
return sortedBoolKeys(commandProducts)
|
||
}
|
||
if base == "" {
|
||
return nil
|
||
}
|
||
return []string{base}
|
||
}
|
||
|
||
func collectCommandReferences(body string) []commandReference {
|
||
lines := strings.Split(body, "\n")
|
||
references := []commandReference{}
|
||
inFence := false
|
||
shellFence := false
|
||
commentIntent := ""
|
||
for index := 0; index < len(lines); index++ {
|
||
line := strings.TrimSpace(lines[index])
|
||
if strings.HasPrefix(line, "```") {
|
||
if inFence {
|
||
inFence = false
|
||
shellFence = false
|
||
} else {
|
||
inFence = true
|
||
shellFence = isShellFence(strings.TrimSpace(strings.TrimPrefix(line, "```")))
|
||
}
|
||
commentIntent = ""
|
||
continue
|
||
}
|
||
if shellFence {
|
||
switch {
|
||
case line == "":
|
||
commentIntent = ""
|
||
case strings.HasPrefix(line, "#"):
|
||
commentIntent = shellCommentIntent(line)
|
||
continue
|
||
}
|
||
}
|
||
if !strings.HasPrefix(line, "dws ") {
|
||
continue
|
||
}
|
||
|
||
startLine := index + 1
|
||
parts := []string{}
|
||
for {
|
||
part := strings.TrimSpace(lines[index])
|
||
continued := strings.HasSuffix(part, "\\")
|
||
if continued {
|
||
part = strings.TrimSpace(strings.TrimSuffix(part, "\\"))
|
||
}
|
||
if part != "" {
|
||
parts = append(parts, part)
|
||
}
|
||
if !continued || index+1 >= len(lines) {
|
||
break
|
||
}
|
||
index++
|
||
}
|
||
command := strings.TrimSpace(strings.Join(parts, " "))
|
||
if command != "" {
|
||
references = append(references, commandReference{
|
||
text: command,
|
||
line: startLine,
|
||
commentIntent: commentIntent,
|
||
})
|
||
}
|
||
}
|
||
return references
|
||
}
|
||
|
||
func sortedBoolKeys(values map[string]bool) []string {
|
||
keys := make([]string, 0, len(values))
|
||
for key, included := range values {
|
||
if included && strings.TrimSpace(key) != "" {
|
||
keys = append(keys, strings.TrimSpace(key))
|
||
}
|
||
}
|
||
sort.Strings(keys)
|
||
return keys
|
||
}
|
||
|
||
func (tracker sourceTracker) add(toolPath, source string, line int) {
|
||
toolPath = normalizeCommandPath(toolPath)
|
||
if len(strings.Fields(toolPath)) < 2 {
|
||
return
|
||
}
|
||
if tracker[toolPath] == nil {
|
||
tracker[toolPath] = map[string]sourceLocation{}
|
||
}
|
||
location := sourceLocation{source: strings.TrimSpace(source), line: line}
|
||
key := location.source + "\x00" + fmt.Sprintf("%09d", location.line)
|
||
tracker[toolPath][key] = location
|
||
}
|
||
|
||
func (tracker sourceTracker) locations(toolPath string) []sourceLocation {
|
||
byKey := tracker[toolPath]
|
||
locations := make([]sourceLocation, 0, len(byKey))
|
||
for _, location := range byKey {
|
||
locations = append(locations, location)
|
||
}
|
||
sort.Slice(locations, func(i, j int) bool {
|
||
if locations[i].source != locations[j].source {
|
||
return locations[i].source < locations[j].source
|
||
}
|
||
return locations[i].line < locations[j].line
|
||
})
|
||
return locations
|
||
}
|
||
|
||
func candidateToolPaths(skillPath string, toolPaths map[string]string, limit int) []string {
|
||
if limit <= 0 {
|
||
return nil
|
||
}
|
||
canonical := map[string]bool{}
|
||
for _, path := range toolPaths {
|
||
if path = strings.TrimSpace(path); path != "" {
|
||
canonical[path] = true
|
||
}
|
||
}
|
||
type scoredPath struct {
|
||
path string
|
||
score int
|
||
}
|
||
scored := []scoredPath{}
|
||
for path := range canonical {
|
||
score := commandPathSimilarity(skillPath, path)
|
||
if score > 0 {
|
||
scored = append(scored, scoredPath{path: path, score: score})
|
||
}
|
||
}
|
||
sort.Slice(scored, func(i, j int) bool {
|
||
if scored[i].score != scored[j].score {
|
||
return scored[i].score > scored[j].score
|
||
}
|
||
return scored[i].path < scored[j].path
|
||
})
|
||
if len(scored) > limit {
|
||
scored = scored[:limit]
|
||
}
|
||
out := make([]string, 0, len(scored))
|
||
for _, item := range scored {
|
||
out = append(out, item.path)
|
||
}
|
||
return out
|
||
}
|
||
|
||
func commandPathSimilarity(left, right string) int {
|
||
leftParts := strings.Fields(left)
|
||
rightParts := strings.Fields(right)
|
||
if len(leftParts) == 0 || len(rightParts) == 0 {
|
||
return 0
|
||
}
|
||
score := 0
|
||
if leftParts[0] == rightParts[0] {
|
||
score += 6
|
||
}
|
||
for leftIndex, rightIndex := len(leftParts)-1, len(rightParts)-1; leftIndex >= 0 && rightIndex >= 0; leftIndex, rightIndex = leftIndex-1, rightIndex-1 {
|
||
if leftParts[leftIndex] != rightParts[rightIndex] {
|
||
break
|
||
}
|
||
score += 5
|
||
}
|
||
rightTokens := map[string]bool{}
|
||
for _, token := range rightParts {
|
||
rightTokens[token] = true
|
||
}
|
||
for _, token := range leftParts {
|
||
if rightTokens[token] {
|
||
score++
|
||
}
|
||
}
|
||
if score < 5 {
|
||
return 0
|
||
}
|
||
return score
|
||
}
|
||
|
||
func resolvePath(root, path string) string {
|
||
if filepath.IsAbs(path) {
|
||
return filepath.Clean(path)
|
||
}
|
||
return filepath.Clean(filepath.Join(root, path))
|
||
}
|
||
|
||
func displayPath(root, path string) string {
|
||
rel, err := filepath.Rel(filepath.Clean(root), filepath.Clean(path))
|
||
if err != nil || strings.HasPrefix(rel, "..") {
|
||
return filepath.ToSlash(filepath.Clean(path))
|
||
}
|
||
return filepath.ToSlash(rel)
|
||
}
|
||
|
||
func hashSources(files []sourceFile) string {
|
||
h := sha256.New()
|
||
for _, file := range files {
|
||
_, _ = h.Write([]byte(file.display))
|
||
_, _ = h.Write([]byte{0})
|
||
_, _ = h.Write(file.data)
|
||
_, _ = h.Write([]byte{0})
|
||
}
|
||
return "sha256:" + hex.EncodeToString(h.Sum(nil))
|
||
}
|
||
|
||
func parseProductRouting(out *File, body, source string) {
|
||
section := markdownSection(body, "## 意图判断决策树")
|
||
for _, line := range strings.Split(section, "\n") {
|
||
match := quotedIntentRE.FindStringSubmatch(line)
|
||
target := routeCodeTarget(line)
|
||
if len(match) < 2 || target == "" {
|
||
continue
|
||
}
|
||
productID := firstCommandToken(target)
|
||
if productID == "" {
|
||
continue
|
||
}
|
||
addProductUse(out, productID, cleanIntent(match[1]), source)
|
||
}
|
||
}
|
||
|
||
func parseIntentGuide(out *File, body, source string, origins sourceTracker) {
|
||
section, startLine := markdownSectionAt(body, "## 易混淆场景快速对照表")
|
||
for index, line := range strings.Split(section, "\n") {
|
||
columns := markdownTableColumns(line)
|
||
if len(columns) < 5 || columns[0] == "用户说..." || strings.Trim(columns[0], "- ") == "" {
|
||
continue
|
||
}
|
||
scenario := cleanIntent(columns[0])
|
||
if intent := cleanIntent(columns[1]); intent != "" && intent != scenario {
|
||
scenario += ";" + intent
|
||
}
|
||
for _, target := range codeSpans(columns[2]) {
|
||
addTargetUse(out, target, scenario, source)
|
||
origins.add(normalizeCommandPath(target), source, startLine+index)
|
||
}
|
||
for _, target := range codeSpans(columns[3]) {
|
||
addTargetAvoid(out, target, scenario, source)
|
||
origins.add(normalizeCommandPath(target), source, startLine+index)
|
||
}
|
||
}
|
||
}
|
||
|
||
func parseToolIntents(out *File, productIDs, known []string, body, source string, origins sourceTracker) {
|
||
for _, heading := range []string{"## 意图判断", "## 使用场景"} {
|
||
section, startLine := markdownSectionAt(body, heading)
|
||
if section == "" {
|
||
continue
|
||
}
|
||
currentIntent := ""
|
||
scanner := bufio.NewScanner(strings.NewReader(section))
|
||
lineIndex := 0
|
||
for scanner.Scan() {
|
||
line := strings.TrimSpace(scanner.Text())
|
||
lineNumber := startLine + lineIndex
|
||
lineIndex++
|
||
if match := quotedIntentRE.FindStringSubmatch(line); len(match) > 1 {
|
||
currentIntent = cleanIntent(match[1])
|
||
if strings.Contains(line, "→") || strings.Contains(line, "->") {
|
||
if target := routeCodeTarget(line); target != "" {
|
||
if path := resolveToolPath(productIDs, target, known); path != "" {
|
||
addToolUse(out, path, currentIntent, source)
|
||
origins.add(path, source, lineNumber)
|
||
}
|
||
}
|
||
currentIntent = ""
|
||
}
|
||
continue
|
||
}
|
||
if currentIntent == "" || (!strings.Contains(line, "→") && !strings.Contains(line, "->")) {
|
||
continue
|
||
}
|
||
target := routeCodeTarget(line)
|
||
if target == "" {
|
||
continue
|
||
}
|
||
action := strings.TrimSpace(strings.TrimLeft(strings.Split(strings.Split(line, "→")[0], "->")[0], "-* "))
|
||
intent := currentIntent
|
||
if action != "" {
|
||
intent += ";" + action
|
||
}
|
||
if path := resolveToolPath(productIDs, target, known); path != "" {
|
||
addToolUse(out, path, intent, source)
|
||
origins.add(path, source, lineNumber)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func parseExamples(out *File, known []string, references []commandReference, source string, maxExamples int, origins sourceTracker) error {
|
||
if len(known) == 0 {
|
||
return nil
|
||
}
|
||
for _, reference := range references {
|
||
line := reference.text
|
||
if strings.Contains(line, "[flags]") || len(line) > 320 {
|
||
continue
|
||
}
|
||
path := longestKnownPrefix(commandTokens(line), known)
|
||
if path == "" {
|
||
continue
|
||
}
|
||
metadata := out.Tools[path]
|
||
if reference.commentIntent != "" {
|
||
_ = appendToolListValue(&metadata, "use_when", &metadata.UseWhen, &metadata.useWhenPresent, &metadata.useWhenRank, &metadata.useWhenOrigin, reference.commentIntent, selectionRankSkill, source, path)
|
||
metadata.SourceRefs = append(metadata.SourceRefs, source)
|
||
}
|
||
if len(metadata.Examples) < maxExamples {
|
||
_ = appendToolListValue(&metadata, "examples", &metadata.Examples, &metadata.examplesPresent, &metadata.examplesRank, &metadata.examplesOrigin, line, selectionRankSkill, source, path)
|
||
metadata.SourceRefs = append(metadata.SourceRefs, source)
|
||
}
|
||
ensureEffect(&metadata, path)
|
||
if err := applyExplicitCommentSafety(&metadata, reference.commentIntent, source, path); err != nil {
|
||
return err
|
||
}
|
||
out.Tools[path] = metadata
|
||
origins.add(path, source, reference.line)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func isShellFence(language string) bool {
|
||
switch strings.ToLower(strings.TrimSpace(language)) {
|
||
case "", "bash", "console", "sh", "shell", "zsh":
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
func shellCommentIntent(line string) string {
|
||
intent := cleanIntent(strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "#")))
|
||
intent = strings.TrimSpace(stepCommentRE.ReplaceAllString(intent, ""))
|
||
if intent == "" || strings.HasPrefix(intent, "!") || strings.HasPrefix(intent, "→") || len(intent) > 200 {
|
||
return ""
|
||
}
|
||
return intent
|
||
}
|
||
|
||
func applyExplicitCommentSafety(metadata *ToolMetadata, intent, source, path string) error {
|
||
if metadata == nil || metadata.Effect == "" || metadata.Effect == "read" {
|
||
return nil
|
||
}
|
||
explicitRisk := false
|
||
for _, marker := range []string{"不可逆", "不可恢复", "不能撤销", "二次确认", "立即失效", "需确认", "需要确认", "高风险", "高影响"} {
|
||
if strings.Contains(intent, marker) {
|
||
explicitRisk = true
|
||
break
|
||
}
|
||
}
|
||
if !explicitRisk {
|
||
return nil
|
||
}
|
||
const reason = "Skill command comment explicitly marks the operation as risky"
|
||
if err := mergeEffect(metadata, dangerEffect(intent), "skill-comment", selectionRankSkill, source, reason, path); err != nil {
|
||
return err
|
||
}
|
||
if err := mergeRisk(metadata, "high", selectionRankSkill, source, reason, path); err != nil {
|
||
return err
|
||
}
|
||
return mergeConfirmation(metadata, "user_required", selectionRankSkill, source, reason, path)
|
||
}
|
||
|
||
func parseDangerRules(out *File, body, source string, stats *Stats, origins sourceTracker) error {
|
||
section, startLine := markdownSectionAt(body, "## 危险操作确认")
|
||
for index, line := range strings.Split(section, "\n") {
|
||
columns := markdownTableColumns(line)
|
||
if len(columns) < 3 || columns[0] == "产品" || strings.Trim(columns[0], "- ") == "" {
|
||
continue
|
||
}
|
||
products := codeSpans(columns[0])
|
||
commands := codeSpans(columns[1])
|
||
if len(products) == 0 || len(commands) == 0 {
|
||
continue
|
||
}
|
||
productID := firstCommandToken(products[0])
|
||
for _, command := range commands {
|
||
path := normalizeToolPath(productID, command)
|
||
if path == "" {
|
||
continue
|
||
}
|
||
metadata := out.Tools[path]
|
||
const reason = "Skill danger table explicitly reviews this operation"
|
||
if err := mergeEffect(&metadata, dangerEffect(columns[2]), "skill-explicit", selectionRankSkill, source, reason, path); err != nil {
|
||
return err
|
||
}
|
||
if err := mergeRisk(&metadata, "high", selectionRankSkill, source, reason, path); err != nil {
|
||
return err
|
||
}
|
||
if err := mergeConfirmation(&metadata, "user_required", selectionRankSkill, source, reason, path); err != nil {
|
||
return err
|
||
}
|
||
metadata.SourceRefs = append(metadata.SourceRefs, source)
|
||
out.Tools[path] = metadata
|
||
origins.add(path, source, startLine+index)
|
||
stats.RiskRules++
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func dangerEffect(description string) string {
|
||
for _, token := range []string{"删除", "撤回", "拒绝", "移除", "替换", "不可逆", "清空"} {
|
||
if strings.Contains(description, token) {
|
||
return "destructive"
|
||
}
|
||
}
|
||
return "write"
|
||
}
|
||
|
||
func collectCommandPaths(productIDs []string, references []commandReference) []string {
|
||
allowedProducts := map[string]bool{}
|
||
for _, productID := range productIDs {
|
||
if productID = strings.TrimSpace(productID); productID != "" {
|
||
allowedProducts[productID] = true
|
||
}
|
||
}
|
||
paths := map[string]bool{}
|
||
for _, reference := range references {
|
||
path := normalizeCommandPath(reference.text)
|
||
if allowedProducts[firstCommandToken(path)] {
|
||
paths[path] = true
|
||
}
|
||
}
|
||
out := make([]string, 0, len(paths))
|
||
for path := range paths {
|
||
out = append(out, path)
|
||
}
|
||
sort.Slice(out, func(i, j int) bool {
|
||
leftParts := len(strings.Fields(out[i]))
|
||
rightParts := len(strings.Fields(out[j]))
|
||
if leftParts != rightParts {
|
||
return leftParts > rightParts
|
||
}
|
||
return out[i] < out[j]
|
||
})
|
||
return out
|
||
}
|
||
|
||
func routeCodeTarget(line string) string {
|
||
arrow := strings.Index(line, "→")
|
||
arrowWidth := len("→")
|
||
if asciiArrow := strings.Index(line, "->"); arrow < 0 || (asciiArrow >= 0 && asciiArrow < arrow) {
|
||
arrow = asciiArrow
|
||
arrowWidth = len("->")
|
||
}
|
||
if arrow < 0 {
|
||
return ""
|
||
}
|
||
spans := codeSpans(line[arrow+arrowWidth:])
|
||
if len(spans) == 0 {
|
||
return ""
|
||
}
|
||
return spans[0]
|
||
}
|
||
|
||
func resolveToolPath(productIDs []string, raw string, known []string) string {
|
||
candidate := normalizeCommandPath(raw)
|
||
if candidate == "" {
|
||
return ""
|
||
}
|
||
for _, path := range known {
|
||
if candidate == path {
|
||
return path
|
||
}
|
||
}
|
||
productSet := map[string]bool{}
|
||
for _, productID := range productIDs {
|
||
productID = strings.TrimSpace(productID)
|
||
if productID == "" {
|
||
continue
|
||
}
|
||
productSet[productID] = true
|
||
prefixed := strings.TrimSpace(productID + " " + candidate)
|
||
for _, path := range known {
|
||
if prefixed == path {
|
||
return path
|
||
}
|
||
}
|
||
}
|
||
if productSet[firstCommandToken(candidate)] {
|
||
return candidate
|
||
}
|
||
matches := []string{}
|
||
for _, path := range known {
|
||
if strings.HasSuffix(path, " "+candidate) {
|
||
matches = append(matches, path)
|
||
}
|
||
}
|
||
if len(matches) == 1 {
|
||
return matches[0]
|
||
}
|
||
if len(productIDs) == 1 {
|
||
return strings.TrimSpace(productIDs[0] + " " + candidate)
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func normalizeToolPath(productID, raw string) string {
|
||
raw = strings.TrimSpace(raw)
|
||
raw = strings.TrimPrefix(raw, "dws ")
|
||
path := normalizeCommandPath(raw)
|
||
if path == "" {
|
||
return ""
|
||
}
|
||
if strings.HasPrefix(path, productID+" ") || path == productID {
|
||
return path
|
||
}
|
||
return strings.TrimSpace(productID + " " + path)
|
||
}
|
||
|
||
func normalizeCommandPath(raw string) string {
|
||
tokens := commandTokens(raw)
|
||
if len(tokens) > 0 && tokens[0] == "dws" {
|
||
tokens = tokens[1:]
|
||
}
|
||
return strings.Join(tokens, " ")
|
||
}
|
||
|
||
func commandTokens(raw string) []string {
|
||
fields := strings.Fields(strings.TrimSpace(raw))
|
||
out := make([]string, 0, len(fields))
|
||
for _, field := range fields {
|
||
field = strings.Trim(field, "`(),;:")
|
||
if field == "" || strings.HasPrefix(field, "#") || strings.HasPrefix(field, "--") || strings.HasPrefix(field, "<") || strings.HasPrefix(field, "[") || strings.HasPrefix(field, "{") || strings.HasPrefix(field, "\"") || strings.HasPrefix(field, "'") || strings.Contains(field, "|") || strings.Contains(field, "=") {
|
||
break
|
||
}
|
||
out = append(out, field)
|
||
}
|
||
return out
|
||
}
|
||
|
||
func longestKnownPrefix(tokens []string, known []string) string {
|
||
if len(tokens) > 0 && tokens[0] == "dws" {
|
||
tokens = tokens[1:]
|
||
}
|
||
joined := strings.Join(tokens, " ")
|
||
for _, path := range known {
|
||
if joined == path || strings.HasPrefix(joined, path+" ") {
|
||
return path
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func ensureEffect(metadata *ToolMetadata, path string) {
|
||
if metadata == nil || scalarIsPresent(metadata.Effect, metadata.effectPresent) {
|
||
return
|
||
}
|
||
if effect := classifyEffectPath(path); effect != "" {
|
||
_ = mergeEffect(metadata, effect, "command-verb", selectionRankDefault, "command-verb", "", path)
|
||
}
|
||
}
|
||
|
||
func classifyEffectPath(path string) string {
|
||
parts := strings.Fields(strings.ToLower(strings.TrimSpace(path)))
|
||
for _, part := range parts[1:] {
|
||
if effect := classifyEffectVerb(part); effect != "" {
|
||
return effect
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func classifyEffectVerb(verb string) string {
|
||
verb = strings.ToLower(strings.TrimSpace(verb))
|
||
read := map[string]bool{
|
||
"list": true, "get": true, "search": true, "read": true, "query": true,
|
||
"detail": true, "status": true, "download": true, "export": true,
|
||
"info": true, "summary": true, "check": true, "inspect": true,
|
||
"diagnose": true, "types": true, "records": true, "tasks": true,
|
||
"find": true, "result": true, "resolve": true, "suggest": true,
|
||
"fields": true, "stats": true, "rules": true, "keywords": true,
|
||
"transcription": true, "todos": true, "audio": true, "mine": true,
|
||
"person": true, "enterprise": true, "behavior": true, "bots": true,
|
||
"invite-url": true, "conversation-info": true, "read-status": true,
|
||
"upload-info": true, "search-options": true, "history-list": true,
|
||
"share-url": true, "rag-pretest": true, "widgets-example": true,
|
||
"config-example": true, "legacy-search-open-platform": true,
|
||
}
|
||
write := map[string]bool{
|
||
"create": true, "update": true, "delete": true, "send": true, "submit": true,
|
||
"approve": true, "reject": true, "revoke": true, "add": true, "remove": true,
|
||
"insert": true, "upload": true, "move": true, "rename": true, "reply": true,
|
||
"recall": true, "publish": true, "enable": true, "disable": true, "save": true,
|
||
"replace": true, "respond": true, "redirect-task": true, "oa-comments": true,
|
||
"oa-cc-noticer": true, "config": true, "connect": true, "reset": true,
|
||
"start": true, "stop": true, "subscribe": true, "unsubscribe": true,
|
||
"browser-policy": true, "chmod": true, "copy": true, "sort": true,
|
||
"forward": true, "fill": true, "upsert": true, "transfer": true,
|
||
"resume": true, "pause": true, "reorder": true, "mute": true,
|
||
"mkdir": true, "duplicate": true, "complete": true, "commit": true,
|
||
"cancel": true, "arrange": true, "append": true, "done": true,
|
||
"import": true, "new": true, "lock": true, "dismiss": true,
|
||
"quit": true, "clear": true, "csv-put": true,
|
||
}
|
||
if read[verb] || hasAnyPrefix(verb, "list-", "get-", "query-", "search-", "read-", "download-", "export-", "inspect-", "check-") || hasAnySuffix(verb, "-list", "-get", "-query", "-search", "-read", "-download", "-export") {
|
||
return "read"
|
||
}
|
||
if write[verb] || hasAnyPrefix(verb,
|
||
"create-", "update-", "delete-", "send-", "add-", "remove-",
|
||
"set-", "unset-", "move-", "copy-", "insert-", "upload-",
|
||
"replace-", "reset-", "enable-", "disable-", "start-", "stop-",
|
||
"subscribe-", "unsubscribe-", "recall-", "reply-", "forward-",
|
||
"clear-", "merge-", "unmerge-", "write-", "append-", "commit-",
|
||
"complete-", "cancel-", "transfer-", "batch-", "role-create",
|
||
"role-update", "role-delete") || hasAnySuffix(verb,
|
||
"-create", "-update", "-delete", "-send", "-add", "-remove",
|
||
"-set", "-move", "-copy", "-insert", "-upload", "-replace",
|
||
"-enable", "-disable", "-forward", "-clear", "-write", "-put",
|
||
"-mute") || hasHyphenToken(verb, "mute") {
|
||
return "write"
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func hasHyphenToken(value string, tokens ...string) bool {
|
||
parts := strings.Split(value, "-")
|
||
for _, part := range parts {
|
||
for _, token := range tokens {
|
||
if part == token {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func hasAnyPrefix(value string, prefixes ...string) bool {
|
||
for _, prefix := range prefixes {
|
||
if strings.HasPrefix(value, prefix) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func hasAnySuffix(value string, suffixes ...string) bool {
|
||
for _, suffix := range suffixes {
|
||
if strings.HasSuffix(value, suffix) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func applyDefaultSafety(metadata *ToolMetadata) {
|
||
if metadata == nil || strings.TrimSpace(metadata.Effect) == "" {
|
||
return
|
||
}
|
||
if !scalarIsPresent(metadata.Risk, metadata.riskPresent) {
|
||
switch metadata.Effect {
|
||
case "read":
|
||
metadata.Risk = "low"
|
||
case "destructive":
|
||
metadata.Risk = "high"
|
||
default:
|
||
metadata.Risk = "medium"
|
||
}
|
||
metadata.riskRank = selectionRankDefault
|
||
metadata.riskOrigin = "effect-default"
|
||
metadata.riskPresent = true
|
||
recordFieldCandidate(metadata, "risk", metadata.Risk, metadata.riskRank, metadata.riskOrigin, "")
|
||
}
|
||
if !scalarIsPresent(metadata.Confirmation, metadata.confirmationPresent) {
|
||
if metadata.Risk == "high" {
|
||
metadata.Confirmation = "user_required"
|
||
} else {
|
||
metadata.Confirmation = "not_required"
|
||
}
|
||
metadata.confirmationRank = selectionRankDefault
|
||
metadata.confirmationOrigin = "risk-default"
|
||
metadata.confirmationPresent = true
|
||
recordFieldCandidate(metadata, "confirmation", metadata.Confirmation, metadata.confirmationRank, metadata.confirmationOrigin, "")
|
||
}
|
||
if !scalarIsPresent(metadata.Idempotency, metadata.idempotencyPresent) {
|
||
if metadata.Effect == "read" {
|
||
metadata.Idempotency = "idempotent"
|
||
} else {
|
||
metadata.Idempotency = "unknown"
|
||
}
|
||
metadata.idempotencyRank = selectionRankDefault
|
||
metadata.idempotencyOrigin = "effect-default"
|
||
metadata.idempotencyPresent = true
|
||
recordFieldCandidate(metadata, "idempotency", metadata.Idempotency, metadata.idempotencyRank, metadata.idempotencyOrigin, "")
|
||
}
|
||
}
|
||
|
||
func appendRankedStringListValue(target *[]string, targetPresent *bool, targetRank *int, targetOrigin *string, value string, rank int, source, path, field string) error {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" {
|
||
return nil
|
||
}
|
||
current := normalizeAuthoredStrings(*target)
|
||
if listIsPresent(*target, *targetPresent) && *targetRank == rank &&
|
||
(strings.TrimSpace(*targetOrigin) == strings.TrimSpace(source) || rank == selectionRankSkill) {
|
||
*target = normalizeAuthoredStrings(append(current, value))
|
||
*targetPresent = true
|
||
if rank == selectionRankSkill {
|
||
// Parsed Skill rows are fragments of one logical authored array, not
|
||
// independent whole-field candidates. Aggregate them before the
|
||
// precedence resolver; same-rank conflict remains strict for every
|
||
// complete candidate (hints and reconciled ToolMetadata).
|
||
*targetOrigin = combinedSkillSource(*targetOrigin, source)
|
||
}
|
||
return nil
|
||
}
|
||
return mergeRankedStringList(target, targetPresent, targetRank, targetOrigin, []string{value}, true, rank, source, path, field)
|
||
}
|
||
|
||
func appendToolListValue(metadata *ToolMetadata, field string, target *[]string, targetPresent *bool, targetRank *int, targetOrigin *string, value string, rank int, source, path string) error {
|
||
if metadata == nil {
|
||
return nil
|
||
}
|
||
if err := appendRankedStringListValue(target, targetPresent, targetRank, targetOrigin, value, rank, source, path, field); err != nil {
|
||
upsertToolListCandidate(metadata, field, []string{value}, rank, source)
|
||
return err
|
||
}
|
||
candidate := []string{value}
|
||
candidateSource := source
|
||
if *targetRank == rank && (strings.TrimSpace(*targetOrigin) == strings.TrimSpace(source) || rank == selectionRankSkill) {
|
||
candidate = *target
|
||
candidateSource = *targetOrigin
|
||
}
|
||
upsertToolListCandidate(metadata, field, candidate, rank, candidateSource)
|
||
return nil
|
||
}
|
||
|
||
func appendProductListValue(metadata *ProductMetadata, field string, target *[]string, targetPresent *bool, targetRank *int, targetOrigin *string, value string, rank int, source, path string) error {
|
||
if metadata == nil {
|
||
return nil
|
||
}
|
||
if err := appendRankedStringListValue(target, targetPresent, targetRank, targetOrigin, value, rank, source, path, field); err != nil {
|
||
upsertProductListCandidate(metadata, field, []string{value}, rank, source)
|
||
return err
|
||
}
|
||
candidate := []string{value}
|
||
candidateSource := source
|
||
if *targetRank == rank && (strings.TrimSpace(*targetOrigin) == strings.TrimSpace(source) || rank == selectionRankSkill) {
|
||
candidate = *target
|
||
candidateSource = *targetOrigin
|
||
}
|
||
upsertProductListCandidate(metadata, field, candidate, rank, candidateSource)
|
||
return nil
|
||
}
|
||
|
||
func upsertToolListCandidate(metadata *ToolMetadata, field string, values []string, rank int, source string) {
|
||
if metadata == nil {
|
||
return
|
||
}
|
||
if metadata.fieldCandidates == nil {
|
||
metadata.fieldCandidates = map[string][]FieldCandidateProvenance{}
|
||
}
|
||
metadata.fieldCandidates[field] = upsertListCandidate(metadata.fieldCandidates[field], values, rank, source)
|
||
}
|
||
|
||
func upsertProductListCandidate(metadata *ProductMetadata, field string, values []string, rank int, source string) {
|
||
if metadata == nil {
|
||
return
|
||
}
|
||
if metadata.fieldCandidates == nil {
|
||
metadata.fieldCandidates = map[string][]FieldCandidateProvenance{}
|
||
}
|
||
metadata.fieldCandidates[field] = upsertListCandidate(metadata.fieldCandidates[field], values, rank, source)
|
||
}
|
||
|
||
func upsertListCandidate(candidates []FieldCandidateProvenance, values []string, rank int, source string) []FieldCandidateProvenance {
|
||
values = normalizeAuthoredStrings(values)
|
||
if len(values) == 0 {
|
||
return candidates
|
||
}
|
||
source = firstNonEmpty(source, "unknown")
|
||
precedence := selectionPrecedence(rank)
|
||
candidate := FieldCandidateProvenance{Value: append([]string(nil), values...), Source: source, Precedence: precedence}
|
||
if rank == selectionRankSkill {
|
||
filtered := candidates[:0]
|
||
for _, existing := range candidates {
|
||
if existing.Precedence != precedence {
|
||
filtered = append(filtered, existing)
|
||
}
|
||
}
|
||
return append(filtered, candidate)
|
||
}
|
||
for index := range candidates {
|
||
if candidates[index].Source == source && candidates[index].Precedence == precedence {
|
||
candidates[index] = candidate
|
||
return candidates
|
||
}
|
||
}
|
||
return append(candidates, candidate)
|
||
}
|
||
|
||
func combinedSkillSource(existing, incoming string) string {
|
||
parts := make([]string, 0, 2)
|
||
for _, source := range []string{existing, incoming} {
|
||
for _, item := range strings.Split(source, " + ") {
|
||
if item = strings.TrimSpace(item); item != "" {
|
||
parts = append(parts, item)
|
||
}
|
||
}
|
||
}
|
||
return strings.Join(normalizedStrings(parts), " + ")
|
||
}
|
||
|
||
func coalesceSkillListCandidates(history map[string][]FieldCandidateProvenance, field string) {
|
||
if len(history) == 0 {
|
||
return
|
||
}
|
||
candidates := history[field]
|
||
indices := make([]int, 0, len(candidates))
|
||
for index, candidate := range candidates {
|
||
if candidate.Precedence == selectionPrecedenceSkill {
|
||
indices = append(indices, index)
|
||
}
|
||
}
|
||
if len(indices) <= 1 {
|
||
return
|
||
}
|
||
sort.Slice(indices, func(i, j int) bool {
|
||
return candidates[indices[i]].Source < candidates[indices[j]].Source
|
||
})
|
||
values := make([]string, 0)
|
||
source := ""
|
||
for _, index := range indices {
|
||
if list, ok := candidates[index].Value.([]string); ok {
|
||
values = append(values, list...)
|
||
}
|
||
source = combinedSkillSource(source, candidates[index].Source)
|
||
}
|
||
aggregated := FieldCandidateProvenance{
|
||
Value: normalizeAuthoredStrings(values),
|
||
Source: source,
|
||
Precedence: selectionPrecedenceSkill,
|
||
}
|
||
filtered := candidates[:0]
|
||
for _, candidate := range candidates {
|
||
if candidate.Precedence != selectionPrecedenceSkill {
|
||
filtered = append(filtered, candidate)
|
||
}
|
||
}
|
||
history[field] = append(filtered, aggregated)
|
||
}
|
||
|
||
func addTargetUse(out *File, target, intent, source string) {
|
||
target = normalizeCommandPath(target)
|
||
if target == "" || target == "先追问" {
|
||
return
|
||
}
|
||
if len(strings.Fields(target)) == 1 {
|
||
addProductUse(out, target, intent, source)
|
||
return
|
||
}
|
||
addToolUse(out, target, intent, source)
|
||
}
|
||
|
||
func addTargetAvoid(out *File, target, intent, source string) {
|
||
target = normalizeCommandPath(target)
|
||
if target == "" {
|
||
return
|
||
}
|
||
if len(strings.Fields(target)) == 1 {
|
||
metadata := out.Products[target]
|
||
_ = appendProductListValue(&metadata, "avoid_when", &metadata.AvoidWhen, &metadata.avoidWhenPresent, &metadata.avoidWhenRank, &metadata.avoidWhenOrigin, intent, selectionRankSkill, source, target)
|
||
metadata.SourceRefs = append(metadata.SourceRefs, source)
|
||
out.Products[target] = metadata
|
||
return
|
||
}
|
||
metadata := out.Tools[target]
|
||
_ = appendToolListValue(&metadata, "avoid_when", &metadata.AvoidWhen, &metadata.avoidWhenPresent, &metadata.avoidWhenRank, &metadata.avoidWhenOrigin, intent, selectionRankSkill, source, target)
|
||
metadata.SourceRefs = append(metadata.SourceRefs, source)
|
||
ensureEffect(&metadata, target)
|
||
out.Tools[target] = metadata
|
||
}
|
||
|
||
func addProductUse(out *File, productID, intent, source string) {
|
||
productID = strings.TrimSpace(productID)
|
||
intent = cleanIntent(intent)
|
||
if productID == "" || intent == "" {
|
||
return
|
||
}
|
||
metadata := out.Products[productID]
|
||
_ = appendProductListValue(&metadata, "use_when", &metadata.UseWhen, &metadata.useWhenPresent, &metadata.useWhenRank, &metadata.useWhenOrigin, intent, selectionRankSkill, source, productID)
|
||
metadata.SourceRefs = append(metadata.SourceRefs, source)
|
||
out.Products[productID] = metadata
|
||
}
|
||
|
||
func addToolUse(out *File, path, intent, source string) {
|
||
path = normalizeCommandPath(path)
|
||
intent = cleanIntent(intent)
|
||
if path == "" || intent == "" {
|
||
return
|
||
}
|
||
metadata := out.Tools[path]
|
||
_ = appendToolListValue(&metadata, "use_when", &metadata.UseWhen, &metadata.useWhenPresent, &metadata.useWhenRank, &metadata.useWhenOrigin, intent, selectionRankSkill, source, path)
|
||
metadata.SourceRefs = append(metadata.SourceRefs, source)
|
||
ensureEffect(&metadata, path)
|
||
out.Tools[path] = metadata
|
||
}
|
||
|
||
func normalizeFile(file *File, maxExamples int) {
|
||
for key, metadata := range file.Products {
|
||
metadata.AgentSummary = strings.TrimSpace(metadata.AgentSummary)
|
||
metadata.AgentSummarySource = strings.TrimSpace(metadata.AgentSummarySource)
|
||
metadata.UseWhen = normalizeAuthoredStrings(metadata.UseWhen)
|
||
metadata.AvoidWhen = normalizeAuthoredStrings(metadata.AvoidWhen)
|
||
preserveExplicitEmptyProductLists(&metadata)
|
||
metadata.SourceRefs = normalizedStrings(metadata.SourceRefs)
|
||
syncProductFieldProvenance(&metadata)
|
||
file.Products[key] = metadata
|
||
}
|
||
for key, metadata := range file.Tools {
|
||
ensureEffect(&metadata, key)
|
||
applyDefaultSafety(&metadata)
|
||
metadata.AgentSummary = strings.TrimSpace(metadata.AgentSummary)
|
||
metadata.AgentSummarySource = strings.TrimSpace(metadata.AgentSummarySource)
|
||
metadata.UseWhen = normalizeAuthoredStrings(metadata.UseWhen)
|
||
metadata.AvoidWhen = normalizeAuthoredStrings(metadata.AvoidWhen)
|
||
metadata.Prerequisites = normalizeAuthoredStrings(metadata.Prerequisites)
|
||
metadata.Tips = normalizeAuthoredStrings(metadata.Tips)
|
||
metadata.WorkflowRefs = normalizeAuthoredStrings(metadata.WorkflowRefs)
|
||
metadata.Examples = normalizeAuthoredStrings(metadata.Examples)
|
||
if maxExamples > 0 && len(metadata.Examples) > maxExamples {
|
||
metadata.Examples = metadata.Examples[:maxExamples]
|
||
}
|
||
preserveExplicitEmptyToolLists(&metadata)
|
||
normalizeListFieldCandidates(&metadata, maxExamples)
|
||
metadata.SourceRefs = normalizedStrings(metadata.SourceRefs)
|
||
if metadata.InterfaceRef != nil {
|
||
metadata.InterfaceRef.ProductID = strings.TrimSpace(metadata.InterfaceRef.ProductID)
|
||
metadata.InterfaceRef.RPCName = strings.TrimSpace(metadata.InterfaceRef.RPCName)
|
||
}
|
||
syncToolFieldProvenance(&metadata)
|
||
file.Tools[key] = metadata
|
||
}
|
||
}
|
||
|
||
// finalizeInterfaceDispositions applies the cross-field interface matrix only
|
||
// after every source and alias has been reconciled. Keeping it out of
|
||
// normalizeFile makes resolution associative: an early local candidate cannot
|
||
// erase a lower-ranked MCP ref before a later higher-ranked mode is merged.
|
||
func finalizeInterfaceDispositions(file *File) {
|
||
if file == nil {
|
||
return
|
||
}
|
||
for key, metadata := range file.Tools {
|
||
mode := strings.TrimSpace(metadata.InterfaceMode)
|
||
availability := strings.TrimSpace(metadata.Availability)
|
||
if mode != "local" && mode != "composite" && availability != "unavailable" {
|
||
continue
|
||
}
|
||
current := metadata.FieldProvenance["interface_ref"]
|
||
allCandidates := append([]FieldCandidateProvenance(nil), current.Candidates...)
|
||
allCandidates = append(allCandidates, current.OverriddenCandidates...)
|
||
|
||
source := firstNonEmpty(metadata.interfaceModeOrigin, "interface_mode")
|
||
rank := effectiveSelectionRank(metadata.InterfaceMode, metadata.interfaceModeRank)
|
||
reason := "final interface mode " + mode + " forbids a direct MCP interface_ref"
|
||
if availability == "unavailable" {
|
||
source = firstNonEmpty(metadata.availabilityOrigin, "availability")
|
||
rank = effectiveSelectionRank(metadata.Availability, metadata.availabilityRank)
|
||
reason = "final availability unavailable forbids Agent invocation and a direct MCP interface_ref"
|
||
}
|
||
winner := FieldCandidateProvenance{
|
||
Value: nil,
|
||
Source: source,
|
||
Precedence: selectionPrecedence(rank),
|
||
Selected: true,
|
||
ReviewReason: reason,
|
||
}
|
||
candidates := []FieldCandidateProvenance{winner}
|
||
overridden := make([]FieldCandidateProvenance, 0, len(allCandidates))
|
||
for _, candidate := range allCandidates {
|
||
candidate.Selected = false
|
||
if candidate.Value == nil || fieldStringValue(candidate.Value) == "" || fieldStringValue(candidate.Value) == "<none>" {
|
||
candidate.Value = nil
|
||
// The matrix winner already represents an authored nil from the
|
||
// same source/rank. Keep one selected candidate, but retain a nil
|
||
// authored at another source/rank as an auditable candidate.
|
||
if candidate.Source != winner.Source || candidate.Precedence != winner.Precedence {
|
||
candidates = append(candidates, candidate)
|
||
}
|
||
continue
|
||
}
|
||
overridden = append(overridden, candidate)
|
||
}
|
||
metadata.InterfaceRef = nil
|
||
metadata.interfaceRefPresent = true
|
||
if metadata.FieldProvenance == nil {
|
||
metadata.FieldProvenance = map[string]FieldProvenance{}
|
||
}
|
||
metadata.FieldProvenance["interface_ref"] = FieldProvenance{
|
||
Value: winner.Value,
|
||
Source: winner.Source,
|
||
Precedence: winner.Precedence,
|
||
Resolution: "interface_disposition_matrix",
|
||
ReviewReason: reason,
|
||
Candidates: candidates,
|
||
OverriddenCandidates: overridden,
|
||
}
|
||
file.Tools[key] = metadata
|
||
}
|
||
}
|
||
|
||
// validateInterfaceDispositions keeps implementation mechanism and execution
|
||
// availability orthogonal. Only mcp/local/composite are modes; unavailable is
|
||
// an Agent execution gate and therefore always needs a reviewed explanation.
|
||
func validateInterfaceDispositions(file File) error {
|
||
var problems []string
|
||
for key, metadata := range file.Tools {
|
||
mode := strings.TrimSpace(metadata.InterfaceMode)
|
||
availability := strings.TrimSpace(metadata.Availability)
|
||
reason := strings.TrimSpace(metadata.InterfaceReason)
|
||
// The generator can be used for partial metadata audits which have not
|
||
// selected an interface disposition yet. Once any interface field is
|
||
// declared, however, it must satisfy the complete final matrix. The
|
||
// release Catalog gate separately requires every effective command to
|
||
// carry such a disposition.
|
||
if mode == "" && availability == "" && reason == "" && metadata.InterfaceRef == nil {
|
||
continue
|
||
}
|
||
if mode == "unavailable" {
|
||
problems = append(problems, fmt.Sprintf("Agent metadata tool %s uses legacy interface_mode=unavailable; migrate to interface_mode=mcp, local, or composite with availability=unavailable", key))
|
||
continue
|
||
}
|
||
if mode != "mcp" && mode != "local" && mode != "composite" {
|
||
problems = append(problems, fmt.Sprintf("Agent metadata tool %s has unsupported interface_mode %q", key, mode))
|
||
continue
|
||
}
|
||
if availability != "available" && availability != "unavailable" {
|
||
problems = append(problems, fmt.Sprintf("Agent metadata tool %s has unsupported availability %q", key, availability))
|
||
continue
|
||
}
|
||
if availability == "unavailable" {
|
||
if metadata.InterfaceRef != nil {
|
||
problems = append(problems, fmt.Sprintf("Agent metadata tool %s is unavailable but declares interface_ref", key))
|
||
}
|
||
if reason == "" {
|
||
problems = append(problems, fmt.Sprintf("Agent metadata tool %s is unavailable without interface_reason", key))
|
||
}
|
||
continue
|
||
}
|
||
switch mode {
|
||
case "mcp":
|
||
if metadata.InterfaceRef == nil {
|
||
problems = append(problems, fmt.Sprintf("Agent metadata tool %s is available mcp without interface_ref", key))
|
||
}
|
||
case "local":
|
||
if metadata.InterfaceRef != nil {
|
||
problems = append(problems, fmt.Sprintf("Agent metadata tool %s is available local but declares interface_ref", key))
|
||
}
|
||
case "composite":
|
||
if metadata.InterfaceRef != nil {
|
||
problems = append(problems, fmt.Sprintf("Agent metadata tool %s is available composite but declares a single interface_ref", key))
|
||
}
|
||
if reason == "" {
|
||
problems = append(problems, fmt.Sprintf("Agent metadata tool %s is available composite without interface_reason", key))
|
||
}
|
||
}
|
||
}
|
||
if len(problems) == 0 {
|
||
return nil
|
||
}
|
||
sort.Strings(problems)
|
||
return fmt.Errorf("invalid final Agent interface disposition: %s", strings.Join(problems, "; "))
|
||
}
|
||
|
||
func uniqueStringsInOrder(values []string) []string {
|
||
seen := map[string]bool{}
|
||
out := make([]string, 0, len(values))
|
||
for _, value := range values {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" || seen[value] {
|
||
continue
|
||
}
|
||
seen[value] = true
|
||
out = append(out, value)
|
||
}
|
||
if len(out) == 0 {
|
||
return nil
|
||
}
|
||
return out
|
||
}
|
||
|
||
func metadataProductID(path string) string {
|
||
first := firstCommandToken(path)
|
||
if index := strings.Index(first, "."); index > 0 {
|
||
return first[:index]
|
||
}
|
||
return first
|
||
}
|
||
|
||
func normalizedStrings(values []string) []string {
|
||
seen := map[string]bool{}
|
||
out := make([]string, 0, len(values))
|
||
for _, value := range values {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" || seen[value] {
|
||
continue
|
||
}
|
||
seen[value] = true
|
||
out = append(out, value)
|
||
}
|
||
sort.Strings(out)
|
||
if len(out) == 0 {
|
||
return nil
|
||
}
|
||
return out
|
||
}
|
||
|
||
func normalizeAuthoredStrings(values []string) []string {
|
||
if values == nil {
|
||
return nil
|
||
}
|
||
values = uniqueStringsInOrder(values)
|
||
if values == nil {
|
||
return []string{}
|
||
}
|
||
return values
|
||
}
|
||
|
||
func markdownSection(body, heading string) string {
|
||
section, _ := markdownSectionAt(body, heading)
|
||
return section
|
||
}
|
||
|
||
func markdownSectionAt(body, heading string) (string, int) {
|
||
lines := strings.Split(body, "\n")
|
||
start := -1
|
||
for index, line := range lines {
|
||
if strings.TrimSpace(line) == heading {
|
||
start = index + 1
|
||
break
|
||
}
|
||
}
|
||
if start < 0 {
|
||
return "", 0
|
||
}
|
||
end := len(lines)
|
||
for index := start; index < len(lines); index++ {
|
||
if strings.HasPrefix(strings.TrimSpace(lines[index]), "## ") {
|
||
end = index
|
||
break
|
||
}
|
||
}
|
||
for start < end && strings.TrimSpace(lines[start]) == "" {
|
||
start++
|
||
}
|
||
for end > start && strings.TrimSpace(lines[end-1]) == "" {
|
||
end--
|
||
}
|
||
return strings.Join(lines[start:end], "\n"), start + 1
|
||
}
|
||
|
||
func markdownTableColumns(line string) []string {
|
||
line = strings.TrimSpace(line)
|
||
if !strings.HasPrefix(line, "|") || !strings.HasSuffix(line, "|") {
|
||
return nil
|
||
}
|
||
raw := strings.Split(strings.Trim(line, "|"), "|")
|
||
out := make([]string, 0, len(raw))
|
||
for _, value := range raw {
|
||
out = append(out, strings.TrimSpace(value))
|
||
}
|
||
return out
|
||
}
|
||
|
||
func codeSpans(value string) []string {
|
||
matches := codeSpanRE.FindAllStringSubmatch(value, -1)
|
||
out := make([]string, 0, len(matches))
|
||
for _, match := range matches {
|
||
if len(match) > 1 && strings.TrimSpace(match[1]) != "" {
|
||
out = append(out, strings.TrimSpace(match[1]))
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
func firstCommandToken(value string) string {
|
||
tokens := commandTokens(value)
|
||
if len(tokens) > 0 && tokens[0] == "dws" {
|
||
tokens = tokens[1:]
|
||
}
|
||
if len(tokens) == 0 {
|
||
return ""
|
||
}
|
||
return tokens[0]
|
||
}
|
||
|
||
func cleanIntent(value string) string {
|
||
value = strings.TrimSpace(value)
|
||
value = strings.Trim(value, "\"“”' ")
|
||
value = strings.ReplaceAll(value, "**", "")
|
||
value = strings.ReplaceAll(value, "`", "")
|
||
return strings.TrimSpace(value)
|
||
}
|