Own safety/gates/parameters in metadata/ and Agent prose in selection/, drop the monolithic Manual file, and keep confirmation aligned with per-tool runtime_gate. Co-authored-by: Cursor <cursoragent@cursor.com>
690 lines
28 KiB
Go
690 lines
28 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 (
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
const HintFileVersion = 1
|
|
const HintIndexFormat = "dws-agent-hint-index"
|
|
|
|
// HintFile is a versioned source contract. Imported files provide a reviewed
|
|
// baseline; explicit files can override scalar fields while Skills continue to
|
|
// supply routing and workflow context.
|
|
type HintFile struct {
|
|
Version int `json:"version"`
|
|
Source HintSource `json:"source"`
|
|
Coverage HintCoverage `json:"coverage,omitempty"`
|
|
Products map[string]HintProduct `json:"products,omitempty"`
|
|
Tools map[string]HintTool `json:"tools,omitempty"`
|
|
ReferenceReview map[string]ReferenceReview `json:"reference_review,omitempty"`
|
|
}
|
|
|
|
// HintIndex is the human-authored entrypoint. It maps product IDs to metadata
|
|
// (safety/interface/parameters/runtime_gate) and selection (Agent routing prose)
|
|
// HintFile paths. Index-level runtime_gates were removed; gates live on each
|
|
// metadata tool as runtime_gate.
|
|
type HintIndex struct {
|
|
Version int `json:"version"`
|
|
Format string `json:"format"`
|
|
Source HintSource `json:"source"`
|
|
Coverage HintCoverage `json:"coverage,omitempty"`
|
|
Metadata map[string]string `json:"metadata"`
|
|
Selection map[string]string `json:"selection"`
|
|
ReferenceReview map[string]ReferenceReview `json:"reference_review,omitempty"`
|
|
}
|
|
|
|
type HintSource struct {
|
|
Kind string `json:"kind"`
|
|
Name string `json:"name"`
|
|
Reviewed bool `json:"reviewed,omitempty"`
|
|
Repository string `json:"repository,omitempty"`
|
|
Revision string `json:"revision,omitempty"`
|
|
Channel string `json:"channel,omitempty"`
|
|
SourceHash string `json:"source_hash,omitempty"`
|
|
}
|
|
|
|
type HintCoverage struct {
|
|
SourceProducts int `json:"source_products,omitempty"`
|
|
MatchedProducts int `json:"matched_products,omitempty"`
|
|
SourceTools int `json:"source_tools,omitempty"`
|
|
EligibleTools int `json:"eligible_tools,omitempty"`
|
|
MatchedTools int `json:"matched_tools,omitempty"`
|
|
UnmatchedTools int `json:"unmatched_tools,omitempty"`
|
|
}
|
|
|
|
type HintProduct struct {
|
|
AgentSummary string `json:"agent_summary,omitempty"`
|
|
UseWhen []string `json:"use_when,omitempty"`
|
|
AvoidWhen []string `json:"avoid_when,omitempty"`
|
|
SourceRefs []string `json:"source_refs,omitempty"`
|
|
agentSummaryPresent bool
|
|
}
|
|
|
|
type HintTool struct {
|
|
AgentSummary string `json:"agent_summary,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"`
|
|
Risk string `json:"risk,omitempty"`
|
|
Confirmation string `json:"confirmation,omitempty"`
|
|
Idempotency string `json:"idempotency,omitempty"`
|
|
RuntimeGate string `json:"runtime_gate,omitempty"`
|
|
CLIPath string `json:"cli_path,omitempty"`
|
|
Parameters map[string]json.RawMessage `json:"parameters,omitempty"`
|
|
WorkflowRefs []string `json:"workflow_refs,omitempty"`
|
|
Examples []string `json:"examples,omitempty"`
|
|
Reviewed *bool `json:"reviewed,omitempty"`
|
|
ReviewReason string `json:"review_reason,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"`
|
|
agentSummaryPresent bool
|
|
effectPresent bool
|
|
riskPresent bool
|
|
confirmationPresent bool
|
|
idempotencyPresent bool
|
|
interfaceRefPresent bool
|
|
interfaceModePresent bool
|
|
availabilityPresent bool
|
|
interfaceReasonPresent bool
|
|
}
|
|
|
|
func (hint *HintProduct) UnmarshalJSON(data []byte) error {
|
|
type wire HintProduct
|
|
var decoded wire
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
return err
|
|
}
|
|
var fields map[string]json.RawMessage
|
|
if err := json.Unmarshal(data, &fields); err != nil {
|
|
return err
|
|
}
|
|
*hint = HintProduct(decoded)
|
|
_, hint.agentSummaryPresent = fields["agent_summary"]
|
|
return nil
|
|
}
|
|
|
|
func (hint *HintTool) UnmarshalJSON(data []byte) error {
|
|
type wire HintTool
|
|
var decoded wire
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
return err
|
|
}
|
|
var fields map[string]json.RawMessage
|
|
if err := json.Unmarshal(data, &fields); err != nil {
|
|
return err
|
|
}
|
|
*hint = HintTool(decoded)
|
|
_, hint.agentSummaryPresent = fields["agent_summary"]
|
|
_, hint.effectPresent = fields["effect"]
|
|
_, hint.riskPresent = fields["risk"]
|
|
_, hint.confirmationPresent = fields["confirmation"]
|
|
_, hint.idempotencyPresent = fields["idempotency"]
|
|
_, hint.interfaceRefPresent = fields["interface_ref"]
|
|
_, hint.interfaceModePresent = fields["interface_mode"]
|
|
_, hint.availabilityPresent = fields["availability"]
|
|
_, hint.interfaceReasonPresent = fields["interface_reason"]
|
|
return nil
|
|
}
|
|
|
|
type hintFileRole string
|
|
|
|
const (
|
|
hintRoleImported hintFileRole = "imported"
|
|
hintRoleMetadata hintFileRole = "metadata"
|
|
hintRoleSelection hintFileRole = "selection"
|
|
hintRoleIndex hintFileRole = "index"
|
|
hintRoleLegacy hintFileRole = "legacy"
|
|
)
|
|
|
|
type parsedHintSource struct {
|
|
file sourceFile
|
|
hint HintFile
|
|
role hintFileRole
|
|
}
|
|
|
|
func parseHintSources(out *File, files []sourceFile, opts Options, stats *Stats, origins sourceTracker) (usedSelection bool, err error) {
|
|
if strings.TrimSpace(opts.HintsDir) == "" {
|
|
return false, nil
|
|
}
|
|
hintsRoot := filepath.Clean(resolvePath(opts.Root, opts.HintsDir))
|
|
hintsPrefix := hintsRoot + string(filepath.Separator)
|
|
byPath := map[string]sourceFile{}
|
|
for _, file := range files {
|
|
cleaned := filepath.Clean(file.path)
|
|
if cleaned == hintsRoot || strings.HasPrefix(cleaned, hintsPrefix) {
|
|
byPath[cleaned] = file
|
|
}
|
|
}
|
|
|
|
indexPath := filepath.Join(hintsRoot, "index.json")
|
|
type selectedHint struct {
|
|
file sourceFile
|
|
role hintFileRole
|
|
}
|
|
selected := []selectedHint{}
|
|
if indexFile, ok := byPath[indexPath]; ok {
|
|
var index HintIndex
|
|
if err := json.Unmarshal(indexFile.data, &index); err != nil {
|
|
return false, fmt.Errorf("decode Agent hint index %s: %w", indexFile.display, err)
|
|
}
|
|
if index.Version != HintFileVersion {
|
|
return false, fmt.Errorf("decode Agent hint index %s: unsupported version %d", indexFile.display, index.Version)
|
|
}
|
|
if strings.TrimSpace(index.Format) != HintIndexFormat {
|
|
return false, fmt.Errorf("decode Agent hint index %s: unsupported format %q", indexFile.display, index.Format)
|
|
}
|
|
kind := strings.ToLower(strings.TrimSpace(index.Source.Kind))
|
|
if kind == "" {
|
|
kind = "explicit"
|
|
}
|
|
if kind != "explicit" {
|
|
return false, fmt.Errorf("decode Agent hint index %s: unsupported source kind %q", indexFile.display, index.Source.Kind)
|
|
}
|
|
appendMapped := func(label string, mapping map[string]string, role hintFileRole) error {
|
|
productIDs := make([]string, 0, len(mapping))
|
|
for productID := range mapping {
|
|
productIDs = append(productIDs, productID)
|
|
}
|
|
sort.Strings(productIDs)
|
|
for _, productID := range productIDs {
|
|
rel := strings.TrimSpace(mapping[productID])
|
|
if rel == "" {
|
|
return fmt.Errorf("decode Agent hint index %s: %s %s missing path", indexFile.display, label, productID)
|
|
}
|
|
productPath := filepath.Clean(filepath.Join(hintsRoot, filepath.FromSlash(rel)))
|
|
if productPath != hintsRoot && !strings.HasPrefix(productPath, hintsPrefix) {
|
|
return fmt.Errorf("decode Agent hint index %s: %s %s path escapes hints root", indexFile.display, label, productID)
|
|
}
|
|
file, ok := byPath[productPath]
|
|
if !ok {
|
|
return fmt.Errorf("decode Agent hint index %s: %s %s file missing: %s", indexFile.display, label, productID, rel)
|
|
}
|
|
selected = append(selected, selectedHint{file: file, role: role})
|
|
}
|
|
return nil
|
|
}
|
|
if len(index.Metadata) == 0 {
|
|
return false, fmt.Errorf("decode Agent hint index %s: metadata map is required", indexFile.display)
|
|
}
|
|
if len(index.Selection) == 0 {
|
|
return false, fmt.Errorf("decode Agent hint index %s: selection map is required", indexFile.display)
|
|
}
|
|
if err := appendMapped("metadata", index.Metadata, hintRoleMetadata); err != nil {
|
|
return false, err
|
|
}
|
|
if err := appendMapped("selection", index.Selection, hintRoleSelection); err != nil {
|
|
return false, err
|
|
}
|
|
usedSelection = true
|
|
importedRoot := filepath.Join(hintsRoot, "imported")
|
|
importedPrefix := importedRoot + string(filepath.Separator)
|
|
imported := []sourceFile{}
|
|
for path, file := range byPath {
|
|
if path == importedRoot || strings.HasPrefix(path, importedPrefix) {
|
|
imported = append(imported, file)
|
|
}
|
|
}
|
|
sort.Slice(imported, func(i, j int) bool { return imported[i].display < imported[j].display })
|
|
prefixed := make([]selectedHint, 0, len(imported)+len(selected))
|
|
for _, file := range imported {
|
|
prefixed = append(prefixed, selectedHint{file: file, role: hintRoleImported})
|
|
}
|
|
selected = append(prefixed, selected...)
|
|
if len(index.ReferenceReview) > 0 || index.Coverage != (HintCoverage{}) {
|
|
index.Source.Kind = kind
|
|
payload, err := json.Marshal(HintFile{
|
|
Version: HintFileVersion,
|
|
Source: index.Source,
|
|
Coverage: index.Coverage,
|
|
ReferenceReview: index.ReferenceReview,
|
|
})
|
|
if err != nil {
|
|
return false, fmt.Errorf("encode Agent hint index projection %s: %w", indexFile.display, err)
|
|
}
|
|
selected = append(selected, selectedHint{
|
|
file: sourceFile{
|
|
path: indexPath,
|
|
display: indexFile.display,
|
|
data: payload,
|
|
},
|
|
role: hintRoleIndex,
|
|
})
|
|
}
|
|
} else {
|
|
for _, file := range files {
|
|
cleaned := filepath.Clean(file.path)
|
|
if cleaned == hintsRoot || strings.HasPrefix(cleaned, hintsPrefix) {
|
|
selected = append(selected, selectedHint{file: file, role: hintRoleLegacy})
|
|
}
|
|
}
|
|
}
|
|
|
|
parsed := []parsedHintSource{}
|
|
for _, item := range selected {
|
|
file := item.file
|
|
var hint HintFile
|
|
if err := json.Unmarshal(file.data, &hint); err != nil {
|
|
return false, fmt.Errorf("decode Agent hint %s: %w", file.display, err)
|
|
}
|
|
if hint.Version != HintFileVersion {
|
|
return false, fmt.Errorf("decode Agent hint %s: unsupported version %d", file.display, hint.Version)
|
|
}
|
|
if err := validateHintFileRole(file.display, hint, item.role); err != nil {
|
|
return false, err
|
|
}
|
|
kind := strings.ToLower(strings.TrimSpace(hint.Source.Kind))
|
|
if kind == "" {
|
|
kind = "explicit"
|
|
}
|
|
if kind != "explicit" && kind != "imported" {
|
|
return false, fmt.Errorf("decode Agent hint %s: unsupported source kind %q", file.display, hint.Source.Kind)
|
|
}
|
|
for path, tool := range hint.Tools {
|
|
tool.InterfaceMode = strings.TrimSpace(tool.InterfaceMode)
|
|
tool.Availability = strings.TrimSpace(tool.Availability)
|
|
tool.InterfaceReason = strings.TrimSpace(tool.InterfaceReason)
|
|
if tool.InterfaceMode == "unavailable" {
|
|
return false, fmt.Errorf("decode Agent hint %s: tool %s uses legacy interface_mode=unavailable; migrate to interface_mode=mcp, local, or composite with availability=unavailable", file.display, path)
|
|
}
|
|
if tool.InterfaceMode != "" && tool.InterfaceMode != "mcp" && tool.InterfaceMode != "composite" && tool.InterfaceMode != "local" {
|
|
return false, fmt.Errorf("decode Agent hint %s: tool %s has unsupported interface_mode %q", file.display, path, tool.InterfaceMode)
|
|
}
|
|
if tool.Availability != "" && tool.Availability != "available" && tool.Availability != "unavailable" {
|
|
return false, fmt.Errorf("decode Agent hint %s: tool %s has unsupported availability %q", file.display, path, tool.Availability)
|
|
}
|
|
if tool.InterfaceRef == nil {
|
|
hint.Tools[path] = tool
|
|
continue
|
|
}
|
|
tool.InterfaceRef.ProductID = strings.TrimSpace(tool.InterfaceRef.ProductID)
|
|
tool.InterfaceRef.RPCName = strings.TrimSpace(tool.InterfaceRef.RPCName)
|
|
if tool.InterfaceRef.ProductID == "" || tool.InterfaceRef.RPCName == "" {
|
|
return false, fmt.Errorf("decode Agent hint %s: tool %s has incomplete interface_ref", file.display, path)
|
|
}
|
|
hint.Tools[path] = tool
|
|
}
|
|
for path, review := range hint.ReferenceReview {
|
|
status := strings.TrimSpace(review.Status)
|
|
target := normalizeCommandPath(review.Target)
|
|
reason := strings.TrimSpace(review.Reason)
|
|
switch status {
|
|
case "alias":
|
|
if target == "" {
|
|
return false, fmt.Errorf("decode Agent hint %s: reference %s alias is missing target", file.display, path)
|
|
}
|
|
case "group", "stale", "out_of_surface":
|
|
if target != "" {
|
|
return false, fmt.Errorf("decode Agent hint %s: reference %s status %s cannot have target", file.display, path, status)
|
|
}
|
|
default:
|
|
return false, fmt.Errorf("decode Agent hint %s: reference %s has unsupported status %q", file.display, path, status)
|
|
}
|
|
if reason == "" {
|
|
return false, fmt.Errorf("decode Agent hint %s: reference %s is missing reason", file.display, path)
|
|
}
|
|
review.Status, review.Target, review.Reason = status, target, reason
|
|
hint.ReferenceReview[path] = review
|
|
}
|
|
hint.Source.Kind = kind
|
|
parsed = append(parsed, parsedHintSource{file: file, hint: hint, role: item.role})
|
|
}
|
|
sort.Slice(parsed, func(i, j int) bool {
|
|
left, right := hintKindPriority(parsed[i].hint.Source.Kind), hintKindPriority(parsed[j].hint.Source.Kind)
|
|
if left != right {
|
|
return left < right
|
|
}
|
|
return parsed[i].file.display < parsed[j].file.display
|
|
})
|
|
for _, source := range parsed {
|
|
if err := applyHintSource(out, source, stats, origins); err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
return usedSelection, nil
|
|
}
|
|
|
|
func validateHintFileRole(display string, hint HintFile, role hintFileRole) error {
|
|
switch role {
|
|
case hintRoleMetadata:
|
|
for path, tool := range hint.Tools {
|
|
if hasSelectionOnlyFields(tool) {
|
|
return fmt.Errorf("decode Agent hint %s: metadata tool %s must not carry selection fields", display, path)
|
|
}
|
|
gate := strings.TrimSpace(tool.RuntimeGate)
|
|
if gate != "" && gate != "none" && gate != "confirm_delete" && gate != "typed_yes" && gate != "confirm_dangerous" {
|
|
return fmt.Errorf("decode Agent hint %s: tool %s has unsupported runtime_gate %q", display, path, gate)
|
|
}
|
|
conf := strings.TrimSpace(tool.Confirmation)
|
|
if gate != "" && gate != "none" && conf != "" && conf != "user_required" {
|
|
return fmt.Errorf("decode Agent hint %s: tool %s runtime_gate=%s requires confirmation=user_required", display, path, gate)
|
|
}
|
|
if (gate == "" || gate == "none") && conf == "user_required" {
|
|
return fmt.Errorf("decode Agent hint %s: tool %s confirmation=user_required requires runtime_gate != none", display, path)
|
|
}
|
|
}
|
|
for path := range hint.Products {
|
|
return fmt.Errorf("decode Agent hint %s: metadata file must not carry products routing block (%s)", display, path)
|
|
}
|
|
case hintRoleSelection:
|
|
for path, tool := range hint.Tools {
|
|
if hasMetadataOnlyFields(tool) {
|
|
return fmt.Errorf("decode Agent hint %s: selection tool %s must not carry metadata fields", display, path)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasSelectionOnlyFields(tool HintTool) bool {
|
|
return scalarIsPresent(tool.AgentSummary, tool.agentSummaryPresent) ||
|
|
tool.UseWhen != nil ||
|
|
tool.AvoidWhen != nil ||
|
|
tool.Examples != nil ||
|
|
tool.Prerequisites != nil ||
|
|
tool.Tips != nil ||
|
|
tool.WorkflowRefs != nil
|
|
}
|
|
|
|
func hasMetadataOnlyFields(tool HintTool) bool {
|
|
return scalarIsPresent(tool.Effect, tool.effectPresent) ||
|
|
scalarIsPresent(tool.Risk, tool.riskPresent) ||
|
|
scalarIsPresent(tool.Confirmation, tool.confirmationPresent) ||
|
|
scalarIsPresent(tool.Idempotency, tool.idempotencyPresent) ||
|
|
strings.TrimSpace(tool.RuntimeGate) != "" ||
|
|
len(tool.Parameters) > 0 ||
|
|
strings.TrimSpace(tool.CLIPath) != "" ||
|
|
tool.InterfaceRef != nil || tool.interfaceRefPresent ||
|
|
scalarIsPresent(tool.InterfaceMode, tool.interfaceModePresent) ||
|
|
scalarIsPresent(tool.Availability, tool.availabilityPresent) ||
|
|
scalarIsPresent(tool.InterfaceReason, tool.interfaceReasonPresent)
|
|
}
|
|
|
|
func hintKindPriority(kind string) int {
|
|
if kind == "imported" {
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func applyHintSource(out *File, parsed parsedHintSource, stats *Stats, origins sourceTracker) error {
|
|
hint := parsed.hint
|
|
sourceLabel := hintSourceLabel(hint.Source, parsed.file.display)
|
|
sourceRef := parsed.file.display
|
|
stats.HintFiles++
|
|
for rawPath, review := range hint.ReferenceReview {
|
|
path := normalizeCommandPath(rawPath)
|
|
if path == "" {
|
|
continue
|
|
}
|
|
review.Status = strings.TrimSpace(review.Status)
|
|
review.Target = normalizeCommandPath(review.Target)
|
|
review.Reason = strings.TrimSpace(review.Reason)
|
|
stats.referenceReviews[path] = review
|
|
}
|
|
|
|
productIDs := make([]string, 0, len(hint.Products))
|
|
for productID := range hint.Products {
|
|
productIDs = append(productIDs, productID)
|
|
}
|
|
sort.Strings(productIDs)
|
|
for _, rawProductID := range productIDs {
|
|
productID := strings.TrimSpace(rawProductID)
|
|
if productID == "" {
|
|
continue
|
|
}
|
|
incoming := hint.Products[rawProductID]
|
|
metadata := out.Products[productID]
|
|
rank := hintSelectionRank(hint.Source, nil)
|
|
incomingSummaryPresent := scalarIsPresent(incoming.AgentSummary, incoming.agentSummaryPresent)
|
|
previousRank := metadata.agentSummaryRank
|
|
if err := mergeRankedStringValue(
|
|
&metadata.AgentSummary, &metadata.agentSummaryPresent, &metadata.agentSummaryRank, &metadata.agentSummaryOrigin,
|
|
incoming.AgentSummary, incomingSummaryPresent, rank, sourceRef, productID, "agent_summary",
|
|
); err != nil {
|
|
return err
|
|
}
|
|
if incomingSummaryPresent && metadata.agentSummaryOrigin == sourceRef && rank >= previousRank {
|
|
metadata.AgentSummarySource = sourceLabel
|
|
}
|
|
useWhenPresent := incoming.UseWhen != nil
|
|
recordProductListCandidate(&metadata, "use_when", incoming.UseWhen, useWhenPresent, rank, sourceRef)
|
|
if err := mergeRankedStringList(&metadata.UseWhen, &metadata.useWhenPresent, &metadata.useWhenRank, &metadata.useWhenOrigin, incoming.UseWhen, useWhenPresent, rank, sourceRef, productID, "use_when"); err != nil {
|
|
return err
|
|
}
|
|
avoidWhenPresent := incoming.AvoidWhen != nil
|
|
recordProductListCandidate(&metadata, "avoid_when", incoming.AvoidWhen, avoidWhenPresent, rank, sourceRef)
|
|
if err := mergeRankedStringList(&metadata.AvoidWhen, &metadata.avoidWhenPresent, &metadata.avoidWhenRank, &metadata.avoidWhenOrigin, incoming.AvoidWhen, avoidWhenPresent, rank, sourceRef, productID, "avoid_when"); err != nil {
|
|
return err
|
|
}
|
|
metadata.SourceRefs = append(metadata.SourceRefs, parsed.file.display)
|
|
metadata.SourceRefs = append(metadata.SourceRefs, incoming.SourceRefs...)
|
|
out.Products[productID] = metadata
|
|
stats.HintProducts++
|
|
}
|
|
|
|
toolPaths := make([]string, 0, len(hint.Tools))
|
|
for path := range hint.Tools {
|
|
toolPaths = append(toolPaths, path)
|
|
}
|
|
sort.Strings(toolPaths)
|
|
for _, rawPath := range toolPaths {
|
|
path := normalizeHintToolPath(rawPath)
|
|
if path == "" {
|
|
continue
|
|
}
|
|
incoming := hint.Tools[rawPath]
|
|
if !hasAgentHintFields(incoming) {
|
|
continue
|
|
}
|
|
metadata := out.Tools[path]
|
|
rank := hintSelectionRank(hint.Source, incoming.Reviewed)
|
|
reviewReason := hintCandidateReviewReason(hint.Source, incoming)
|
|
previousSummaryRank := metadata.agentSummaryRank
|
|
incomingSummaryPresent := scalarIsPresent(incoming.AgentSummary, incoming.agentSummaryPresent)
|
|
recordStringFieldCandidate(&metadata, "agent_summary", incoming.AgentSummary, incomingSummaryPresent, rank, sourceRef, reviewReason)
|
|
if err := mergeRankedStringValue(
|
|
&metadata.AgentSummary, &metadata.agentSummaryPresent, &metadata.agentSummaryRank, &metadata.agentSummaryOrigin,
|
|
incoming.AgentSummary, incomingSummaryPresent, rank, sourceRef, path, "agent_summary",
|
|
); err != nil {
|
|
return err
|
|
}
|
|
if incomingSummaryPresent && metadata.agentSummaryOrigin == sourceRef && rank >= previousSummaryRank {
|
|
metadata.AgentSummarySource = sourceLabel
|
|
}
|
|
for _, list := range []struct {
|
|
name string
|
|
target *[]string
|
|
targetPresent *bool
|
|
targetRank *int
|
|
origin *string
|
|
incoming []string
|
|
}{
|
|
{"use_when", &metadata.UseWhen, &metadata.useWhenPresent, &metadata.useWhenRank, &metadata.useWhenOrigin, incoming.UseWhen},
|
|
{"avoid_when", &metadata.AvoidWhen, &metadata.avoidWhenPresent, &metadata.avoidWhenRank, &metadata.avoidWhenOrigin, incoming.AvoidWhen},
|
|
{"prerequisites", &metadata.Prerequisites, &metadata.prerequisitesPresent, &metadata.prerequisitesRank, &metadata.prerequisitesOrigin, incoming.Prerequisites},
|
|
{"tips", &metadata.Tips, &metadata.tipsPresent, &metadata.tipsRank, &metadata.tipsOrigin, incoming.Tips},
|
|
{"workflow_refs", &metadata.WorkflowRefs, &metadata.workflowRefsPresent, &metadata.workflowRefsRank, &metadata.workflowRefsOrigin, incoming.WorkflowRefs},
|
|
{"examples", &metadata.Examples, &metadata.examplesPresent, &metadata.examplesRank, &metadata.examplesOrigin, incoming.Examples},
|
|
} {
|
|
incomingPresent := list.incoming != nil
|
|
recordListFieldCandidate(&metadata, list.name, list.incoming, incomingPresent, rank, sourceRef, reviewReason)
|
|
if err := mergeRankedStringList(list.target, list.targetPresent, list.targetRank, list.origin, list.incoming, incomingPresent, rank, sourceRef, path, list.name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := mergeEffectValue(&metadata, incoming.Effect, scalarIsPresent(incoming.Effect, incoming.effectPresent), "agent-hint", rank, sourceRef, reviewReason, path); err != nil {
|
|
return err
|
|
}
|
|
if err := mergeRiskValue(&metadata, incoming.Risk, scalarIsPresent(incoming.Risk, incoming.riskPresent), rank, sourceRef, reviewReason, path); err != nil {
|
|
return err
|
|
}
|
|
if err := mergeConfirmationValue(&metadata, incoming.Confirmation, scalarIsPresent(incoming.Confirmation, incoming.confirmationPresent), rank, sourceRef, reviewReason, path); err != nil {
|
|
return err
|
|
}
|
|
idempotencyPresent := scalarIsPresent(incoming.Idempotency, incoming.idempotencyPresent)
|
|
recordStringFieldCandidate(&metadata, "idempotency", incoming.Idempotency, idempotencyPresent, rank, sourceRef, reviewReason)
|
|
if err := mergeRankedStringValue(
|
|
&metadata.Idempotency, &metadata.idempotencyPresent, &metadata.idempotencyRank, &metadata.idempotencyOrigin,
|
|
incoming.Idempotency, idempotencyPresent, rank, sourceRef, path, "idempotency",
|
|
); err != nil {
|
|
return err
|
|
}
|
|
if incoming.Reviewed != nil {
|
|
recordTypedFieldCandidateValue(&metadata, "reviewed", *incoming.Reviewed, true, rank, sourceRef, reviewReason)
|
|
if metadata.Reviewed == nil || rank > metadata.reviewedRank {
|
|
value := *incoming.Reviewed
|
|
metadata.Reviewed = &value
|
|
metadata.reviewedRank = rank
|
|
metadata.reviewedOrigin = sourceRef
|
|
} else if rank == metadata.reviewedRank && *metadata.Reviewed == *incoming.Reviewed {
|
|
metadata.reviewedOrigin = stableSource(metadata.reviewedOrigin, sourceRef)
|
|
}
|
|
}
|
|
if incoming.InterfaceRef != nil || incoming.interfaceRefPresent {
|
|
candidate := ToolMetadata{
|
|
InterfaceRef: incoming.InterfaceRef,
|
|
interfaceRefPresent: true,
|
|
interfaceRefRank: rank,
|
|
interfaceRefOrigin: sourceRef,
|
|
}
|
|
var value any
|
|
if incoming.InterfaceRef != nil {
|
|
value = incoming.InterfaceRef.ProductID + "." + incoming.InterfaceRef.RPCName
|
|
}
|
|
recordTypedFieldCandidateValue(&candidate, "interface_ref", value, true, rank, sourceRef, reviewReason)
|
|
if err := mergeRankedInterfaceRef(&metadata, candidate, path); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, field := range []struct {
|
|
name string
|
|
target *string
|
|
targetPresent *bool
|
|
targetRank *int
|
|
origin *string
|
|
incoming string
|
|
incomingPresent bool
|
|
}{
|
|
{"interface_mode", &metadata.InterfaceMode, &metadata.interfaceModePresent, &metadata.interfaceModeRank, &metadata.interfaceModeOrigin, incoming.InterfaceMode, scalarIsPresent(incoming.InterfaceMode, incoming.interfaceModePresent)},
|
|
{"availability", &metadata.Availability, &metadata.availabilityPresent, &metadata.availabilityRank, &metadata.availabilityOrigin, incoming.Availability, scalarIsPresent(incoming.Availability, incoming.availabilityPresent)},
|
|
{"interface_reason", &metadata.InterfaceReason, &metadata.interfaceReasonPresent, &metadata.interfaceReasonRank, &metadata.interfaceReasonOrigin, incoming.InterfaceReason, scalarIsPresent(incoming.InterfaceReason, incoming.interfaceReasonPresent)},
|
|
} {
|
|
recordStringFieldCandidate(&metadata, field.name, field.incoming, field.incomingPresent, rank, sourceRef, reviewReason)
|
|
if err := mergeRankedStringValue(field.target, field.targetPresent, field.targetRank, field.origin, field.incoming, field.incomingPresent, rank, sourceRef, path, field.name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
metadata.SourceRefs = append(metadata.SourceRefs, parsed.file.display)
|
|
metadata.SourceRefs = append(metadata.SourceRefs, incoming.SourceRefs...)
|
|
out.Tools[path] = metadata
|
|
origins.add(path, parsed.file.display, 0)
|
|
stats.HintTools++
|
|
if strings.EqualFold(strings.TrimSpace(incoming.Risk), "high") {
|
|
stats.RiskRules++
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasAgentHintFields(hint HintTool) bool {
|
|
return scalarIsPresent(hint.AgentSummary, hint.agentSummaryPresent) ||
|
|
hint.UseWhen != nil ||
|
|
hint.AvoidWhen != nil ||
|
|
hint.Prerequisites != nil ||
|
|
hint.Tips != nil ||
|
|
scalarIsPresent(hint.Effect, hint.effectPresent) ||
|
|
scalarIsPresent(hint.Risk, hint.riskPresent) ||
|
|
scalarIsPresent(hint.Confirmation, hint.confirmationPresent) ||
|
|
scalarIsPresent(hint.Idempotency, hint.idempotencyPresent) ||
|
|
strings.TrimSpace(hint.RuntimeGate) != "" ||
|
|
len(hint.Parameters) > 0 ||
|
|
hint.WorkflowRefs != nil ||
|
|
hint.Examples != nil ||
|
|
hint.Reviewed != nil ||
|
|
len(hint.SourceRefs) > 0 ||
|
|
hint.InterfaceRef != nil || hint.interfaceRefPresent ||
|
|
scalarIsPresent(hint.InterfaceMode, hint.interfaceModePresent) ||
|
|
scalarIsPresent(hint.Availability, hint.availabilityPresent) ||
|
|
scalarIsPresent(hint.InterfaceReason, hint.interfaceReasonPresent)
|
|
}
|
|
|
|
func hintSelectionRank(source HintSource, reviewed *bool) int {
|
|
if reviewed != nil {
|
|
if *reviewed {
|
|
return selectionRankReviewedExplicit
|
|
}
|
|
return selectionRankUnreviewedExplicit
|
|
}
|
|
if source.Reviewed {
|
|
return selectionRankReviewedExplicit
|
|
}
|
|
if strings.EqualFold(strings.TrimSpace(source.Kind), "imported") {
|
|
return selectionRankImported
|
|
}
|
|
return selectionRankExplicit
|
|
}
|
|
|
|
func hintCandidateReviewReason(source HintSource, tool HintTool) string {
|
|
if reason := strings.TrimSpace(tool.ReviewReason); reason != "" {
|
|
return reason
|
|
}
|
|
label := hintSourceLabel(source, "Agent hint")
|
|
if tool.Reviewed != nil && *tool.Reviewed {
|
|
return label + " marks this tool as reviewed"
|
|
}
|
|
if source.Reviewed {
|
|
return label + " is a reviewed Agent hint source"
|
|
}
|
|
if strings.EqualFold(strings.TrimSpace(source.Kind), "explicit") && tool.Reviewed == nil {
|
|
return label + " is an explicit Agent hint source"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func normalizeHintToolPath(raw string) string {
|
|
raw = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(raw), "dws "))
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
if !strings.ContainsAny(raw, " \t") && strings.Contains(raw, ".") {
|
|
return raw
|
|
}
|
|
return normalizeCommandPath(raw)
|
|
}
|
|
|
|
func hintSourceLabel(source HintSource, fallback string) string {
|
|
name := strings.TrimSpace(source.Name)
|
|
if name == "" {
|
|
name = fallback
|
|
}
|
|
if revision := strings.TrimSpace(source.Revision); revision != "" {
|
|
if len(revision) > 12 {
|
|
revision = revision[:12]
|
|
}
|
|
name += "@" + revision
|
|
}
|
|
return name
|
|
}
|