package gsp import ( "fmt" "os" "path/filepath" "gopkg.in/yaml.v3" ) const GSPMessageVersion = "0.1" var allowedMessageIntents = map[string]bool{ "design": true, "implement": true, "review": true, "test": true, "acceptance": true, "handoff": true, "inspect": true, } type Message struct { MessageVersion string `json:"gspMessageVersion,omitempty" yaml:"gspMessageVersion"` ID string `json:"id,omitempty" yaml:"id"` From string `json:"from,omitempty" yaml:"from"` To string `json:"to,omitempty" yaml:"to"` Intent string `json:"intent,omitempty" yaml:"intent"` Entry string `json:"entry,omitempty" yaml:"entry"` Stage string `json:"stage,omitempty" yaml:"stage"` Requires []string `json:"requires,omitempty" yaml:"requires"` ContextPack ContextPack `json:"contextPack,omitempty" yaml:"contextPack"` File string `json:"file,omitempty" yaml:"-"` } type ContextPack struct { Mode string `json:"mode,omitempty" yaml:"mode"` Depth int `json:"depth,omitempty" yaml:"depth"` Budget int `json:"budget,omitempty" yaml:"budget"` } func ReadMessage(root, file string) (Message, error) { path := file if !filepath.IsAbs(path) { path = filepath.Join(root, file) } data, err := os.ReadFile(path) if err != nil { return Message{}, err } var message Message if err := yaml.Unmarshal(data, &message); err != nil { return Message{}, err } message.File = relPath(root, path) return message, nil } func (p *Project) ValidateMessage(message Message) Report { report := Report{OK: true} if message.MessageVersion == "" { report.addError("missing_message_version", message.ID, message.File, "message requires gspMessageVersion") } else if message.MessageVersion != GSPMessageVersion { report.addError("unsupported_message_version", message.ID, message.File, fmt.Sprintf("GSP message version %q is not supported", message.MessageVersion)) } if message.ID == "" { report.addError("missing_id", "", message.File, "message requires id") } if message.From == "" { report.addError("missing_from", message.ID, message.File, "message requires from") } if message.To == "" { report.addError("missing_to", message.ID, message.File, "message requires to") } if message.Intent == "" { report.addError("missing_intent", message.ID, message.File, "message requires intent") } else if !allowedMessageIntents[message.Intent] { report.addError("invalid_intent", message.ID, message.File, fmt.Sprintf("intent %q is not allowed", message.Intent)) } if message.Entry == "" { report.addError("missing_entry", message.ID, message.File, "message requires entry") } else if _, ok := p.ByID[message.Entry]; !ok { report.addError("missing_entry_gsp", message.Entry, message.File, fmt.Sprintf("entry references missing GSP %q", message.Entry)) } if message.Stage != "" { if _, ok := p.Manifest.minResolution(message.Stage); !ok { report.addError("unknown_stage", message.ID, message.File, fmt.Sprintf("unknown stage %q", message.Stage)) } } for _, id := range message.Requires { if _, ok := p.ByID[id]; !ok { report.addError("missing_required_gsp", id, message.File, fmt.Sprintf("requires references missing GSP %q", id)) } } if message.ContextPack.Mode != "" && !allowedMessageIntents[message.ContextPack.Mode] { report.addError("invalid_context_pack_mode", message.ID, message.File, fmt.Sprintf("contextPack mode %q is not allowed", message.ContextPack.Mode)) } return report }