From c393bd0dd9ff1b9e1052be78e75b52fb1c95f0c8 Mon Sep 17 00:00:00 2001 From: Mario <92220954+mario-gc@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:47:37 +0000 Subject: [PATCH 1/2] fix: add comments to exported functions and fix staticcheck issues - Added comments to all exported functions across all packages - Fixed 3 staticcheck QF1012 issues in cli/prompt.go - Used fmt.Fprintf instead of WriteString(fmt.Sprintf(...)) - All 50 revive exported comment warnings resolved - All tests passing --- agents/agents.go | 17 +++++++++++++++ cli/prompt.go | 49 +++++++++++++++++++++++++++++++++++++++--- config/config.go | 16 ++++++++++++++ templates/templates.go | 17 +++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/agents/agents.go b/agents/agents.go index aa8e9f7..f46bdbb 100644 --- a/agents/agents.go +++ b/agents/agents.go @@ -17,6 +17,8 @@ const maxFrontmatterSize = 64 * 1024 var validSegment = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-\.]*$`) +// ValidateModelID validates a model ID string format. +// Returns an error if the model ID is empty, exceeds 256 characters, or has invalid segments. func ValidateModelID(modelID string) error { if modelID == "" { return fmt.Errorf("model ID cannot be empty") @@ -58,6 +60,8 @@ func isPathWithinDir(path, dir string) (bool, error) { return strings.HasPrefix(absPath, absDir+string(filepath.Separator)), nil } +// LoadAllAgents loads agents from all available sources. +// Returns a deduplicated list with project configurations taking precedence over global ones. func LoadAllAgents() ([]models.Agent, error) { agentMap := make(map[string]models.Agent) @@ -114,10 +118,15 @@ func getProjectAgentsDir() string { return filepath.Join(".", ".opencode", "agents") } +// LoadAgents loads agents from a directory of markdown files. +// Deprecated: Use LoadAgentsFromDir for more control over source metadata. func LoadAgents(agentsDir string) ([]models.Agent, error) { return LoadAgentsFromDir(agentsDir, models.SourceGlobal, models.FormatMarkdown) } +// LoadAgentsFromDir loads agents from markdown files in a directory. +// Parameters location and format specify the source metadata for each agent. +// Returns a list of agents with valid model fields. func LoadAgentsFromDir(agentsDir, location, format string) ([]models.Agent, error) { absAgentsDir, err := filepath.Abs(agentsDir) if err != nil { @@ -185,6 +194,8 @@ func LoadAgentsFromDir(agentsDir, location, format string) ([]models.Agent, erro return agentList, nil } +// ParseFrontmatter extracts YAML frontmatter from markdown content. +// Returns a map of frontmatter fields or an error if parsing fails. func ParseFrontmatter(content string) (map[string]interface{}, error) { if !strings.HasPrefix(content, "---") { return nil, fmt.Errorf("no frontmatter found") @@ -208,6 +219,9 @@ func ParseFrontmatter(content string) (map[string]interface{}, error) { return frontmatter, nil } +// UpdateAgentModel updates the model field for an agent. +// Supports both markdown files and JSON configuration files. +// Returns an error if the model ID is invalid or the file cannot be updated. func UpdateAgentModel(agentPath, agentName, newModel string) error { if err := ValidateModelID(newModel); err != nil { return fmt.Errorf("invalid model ID: %w", err) @@ -231,6 +245,9 @@ func UpdateAgentModel(agentPath, agentName, newModel string) error { return updateAgentFieldInMarkdown(agentPath, "model", newModel) } +// UpdateAgentMode updates the mode field for an agent. +// Supports both markdown files and JSON configuration files. +// Returns an error if the mode is invalid (must be 'primary', 'subagent', or 'all'). func UpdateAgentMode(agentPath, agentName, newMode string) error { if !isValidMode(newMode) { return fmt.Errorf("invalid mode: must be 'primary', 'subagent', or 'all'") diff --git a/cli/prompt.go b/cli/prompt.go index 980c103..6e8c915 100644 --- a/cli/prompt.go +++ b/cli/prompt.go @@ -28,6 +28,8 @@ const ( TemplateInspect = "inspect" ) +// PromptAgentSelection prompts the user to select an agent from the list. +// Returns the selected agent name, a special choice (SortChoice, TemplatesChoice, ExitChoice), or an error. func PromptAgentSelection(agents []models.Agent, currentSort string, caseSensitive bool) (string, error) { var selectedName string @@ -79,6 +81,8 @@ func formatSourceTag(source models.AgentSource) string { return fmt.Sprintf("%s/%s", loc, fmtChar) } +// PromptActionSelection prompts the user to choose an action for a selected agent. +// Returns the selected action (ActionModel, ActionMode, or BackChoice) or an error. func PromptActionSelection(currentModel, currentMode string) (string, error) { var action string @@ -107,6 +111,8 @@ func PromptActionSelection(currentModel, currentMode string) (string, error) { return action, nil } +// PromptModeSelection prompts the user to select a mode for an agent. +// Returns the selected mode (primary, subagent, or all) or an error. func PromptModeSelection(currentMode string) (string, error) { var mode string @@ -136,6 +142,8 @@ func PromptModeSelection(currentMode string) (string, error) { return mode, nil } +// PromptAddModeField prompts the user whether to add an explicit mode field. +// Returns true if the user wants to add the field, false otherwise. func PromptAddModeField() (bool, error) { var choice string @@ -158,6 +166,8 @@ func PromptAddModeField() (bool, error) { return choice == "yes", nil } +// PromptModelSelection prompts the user to select a model from the list. +// Returns the selected model ID, CustomModelChoice, or an error. func PromptModelSelection(modelOptions []models.ModelOption) (string, error) { var selectedID string options := make([]huh.Option[string], len(modelOptions)+1) @@ -184,6 +194,8 @@ func PromptModelSelection(modelOptions []models.ModelOption) (string, error) { return selectedID, nil } +// PromptCustomModelInput prompts the user to enter a custom model ID. +// Returns the entered model ID or an error. func PromptCustomModelInput() (string, error) { var modelID string @@ -203,6 +215,8 @@ func PromptCustomModelInput() (string, error) { return modelID, nil } +// PromptConfirm prompts the user with a yes/no confirmation. +// Returns true if the user confirms, false otherwise. func PromptConfirm(message string) (bool, error) { var confirmed bool @@ -223,6 +237,8 @@ func PromptConfirm(message string) (bool, error) { return confirmed, nil } +// PromptContinueOrExit prompts the user to continue or exit the application. +// Returns true to continue, false to exit. func PromptContinueOrExit() (bool, error) { var choice string @@ -245,6 +261,8 @@ func PromptContinueOrExit() (bool, error) { return choice == ContinueChoice, nil } +// PromptUndo prompts the user whether to undo recent changes. +// Returns true if the user wants to undo, false to keep changes. func PromptUndo(message string) (bool, error) { var choice string @@ -288,6 +306,8 @@ func getSortDisplay(sortBy string, caseSensitive bool) string { return sortType } +// PromptSortSelection prompts the user to select a sorting method. +// Returns the selected sort option, updated case-sensitivity flag, or an error. func PromptSortSelection(currentSort string, caseSensitive bool) (string, bool, error) { var selected string @@ -327,6 +347,8 @@ func PromptSortSelection(currentSort string, caseSensitive bool) (string, bool, return selected, caseSensitive, nil } +// SortAgents sorts a list of agents by the specified criteria. +// Returns a new sorted slice without modifying the original. func SortAgents(agents []models.Agent, sortBy string, caseSensitive bool) []models.Agent { result := make([]models.Agent, len(agents)) copy(result, agents) @@ -364,6 +386,8 @@ func SortAgents(agents []models.Agent, sortBy string, caseSensitive bool) []mode return result } +// SortModels sorts a list of model options by the specified criteria. +// Returns a new sorted slice without modifying the original. func SortModels(modelOptions []models.ModelOption, sortBy string, caseSensitive bool) []models.ModelOption { result := make([]models.ModelOption, len(modelOptions)) copy(result, modelOptions) @@ -393,6 +417,8 @@ func SortModels(modelOptions []models.ModelOption, sortBy string, caseSensitive return result } +// PromptTemplateMenu prompts the user to choose a template operation. +// Returns the selected action (TemplateSave, TemplateShow, or BackChoice) or an error. func PromptTemplateMenu() (string, error) { var choice string @@ -416,6 +442,8 @@ func PromptTemplateMenu() (string, error) { return choice, nil } +// PromptTemplateName prompts the user to enter a name for a new template. +// Returns the entered name or an error. func PromptTemplateName() (string, error) { var name string @@ -435,6 +463,8 @@ func PromptTemplateName() (string, error) { return name, nil } +// PromptTemplateSelection prompts the user to select a template from the list. +// Returns the selected template name, BackChoice, or an error. func PromptTemplateSelection(templates []models.Template) (string, error) { if len(templates) == 0 { return "", nil @@ -477,6 +507,8 @@ func formatDate(timestamp string) string { return t.Format("2006-01-02") } +// PromptTemplateAction prompts the user to choose an action for a selected template. +// Returns the selected action (TemplateInspect, TemplateLoad, TemplateDelete, or BackChoice) or an error. func PromptTemplateAction(templateName string) (string, error) { var action string @@ -501,6 +533,8 @@ func PromptTemplateAction(templateName string) (string, error) { return action, nil } +// PromptTemplateOverwrite prompts the user whether to overwrite an existing template. +// Returns true if the user confirms overwrite, false otherwise. func PromptTemplateOverwrite(templateName string) (bool, error) { var choice string @@ -523,6 +557,9 @@ func PromptTemplateOverwrite(templateName string) (bool, error) { return choice == "yes", nil } +// PromptTemplateLoadConfirm prompts the user to confirm loading a template. +// Shows the number of matched and unmatched agents. +// Returns true if the user confirms, false otherwise. func PromptTemplateLoadConfirm(matchedCount, unmatchedCount int) (bool, error) { var choice string @@ -547,6 +584,8 @@ func PromptTemplateLoadConfirm(matchedCount, unmatchedCount int) (bool, error) { return choice == "yes", nil } +// PromptTemplateDeleteConfirm prompts the user to confirm deleting a template. +// Returns true if the user confirms, false otherwise. func PromptTemplateDeleteConfirm(templateName string) (bool, error) { var choice string @@ -569,6 +608,8 @@ func PromptTemplateDeleteConfirm(templateName string) (bool, error) { return choice == "yes", nil } +// PromptTemplateContinueOrExit prompts the user to continue or exit after template operations. +// Returns true to continue, false to exit. func PromptTemplateContinueOrExit() (bool, error) { var choice string @@ -591,12 +632,14 @@ func PromptTemplateContinueOrExit() (bool, error) { return choice == ContinueChoice, nil } +// FormatTemplateInspect formats a template for inspection display. +// Returns a formatted string showing template name, creation date, and all agent assignments. func FormatTemplateInspect(template models.Template) string { var builder strings.Builder - builder.WriteString(fmt.Sprintf("Template: %s\n", template.Name)) - builder.WriteString(fmt.Sprintf("Created: %s\n", formatDate(template.CreatedAt))) - builder.WriteString(fmt.Sprintf("Agents: %d\n\n", len(template.Agents))) + fmt.Fprintf(&builder, "Template: %s\n", template.Name) + fmt.Fprintf(&builder, "Created: %s\n", formatDate(template.CreatedAt)) + fmt.Fprintf(&builder, "Agents: %d\n\n", len(template.Agents)) agentNames := make([]string, 0, len(template.Agents)) for name := range template.Agents { diff --git a/config/config.go b/config/config.go index de42325..1640c01 100644 --- a/config/config.go +++ b/config/config.go @@ -33,6 +33,8 @@ func isValidModelID(modelID string) bool { return true } +// LoadGlobalConfig loads the global opencode configuration file. +// Returns the configuration or an error if the file cannot be read or parsed. func LoadGlobalConfig() (*models.OpencodeConfig, error) { home, err := os.UserHomeDir() if err != nil { @@ -43,6 +45,8 @@ func LoadGlobalConfig() (*models.OpencodeConfig, error) { return loadConfigFile(configPath) } +// LoadProjectConfig loads the project-level opencode configuration file. +// Returns nil if the file does not exist, or an error if it cannot be parsed. func LoadProjectConfig() (*models.OpencodeConfig, error) { configPath := filepath.Join(".", "opencode.json") if _, err := os.Stat(configPath); os.IsNotExist(err) { @@ -65,6 +69,9 @@ func loadConfigFile(configPath string) (*models.OpencodeConfig, error) { return &cfg, nil } +// GetAgentsFromConfig extracts agents from an opencode configuration. +// Parameters location and format specify the source metadata for each agent. +// Returns a list of agents defined in the configuration. func GetAgentsFromConfig(cfg *models.OpencodeConfig, location, format string) []models.Agent { if cfg == nil || cfg.Agent == nil { return nil @@ -87,6 +94,8 @@ func GetAgentsFromConfig(cfg *models.OpencodeConfig, location, format string) [] return agentList } +// GetAvailableModels extracts available models from an opencode configuration. +// Returns a list of model options with provider, ID, and display name. func GetAvailableModels(cfg *models.OpencodeConfig) []models.ModelOption { var options []models.ModelOption @@ -116,6 +125,8 @@ func GetAvailableModels(cfg *models.OpencodeConfig) []models.ModelOption { return options } +// GetModelsFromCLI retrieves available models by calling the opencode CLI. +// Returns a list of model options or an error if the CLI command fails. func GetModelsFromCLI() ([]models.ModelOption, error) { cmd := exec.Command("opencode", "models") output, err := cmd.Output() @@ -145,6 +156,8 @@ func GetModelsFromCLI() ([]models.ModelOption, error) { return options, nil } +// GetGlobalConfigPath returns the path to the global opencode configuration file. +// Returns an error if the user home directory cannot be determined. func GetGlobalConfigPath() (string, error) { home, err := os.UserHomeDir() if err != nil { @@ -153,10 +166,13 @@ func GetGlobalConfigPath() (string, error) { return filepath.Join(home, ".config", "opencode", "opencode.json"), nil } +// GetProjectConfigPath returns the path to the project-level opencode configuration file. func GetProjectConfigPath() string { return filepath.Join(".", "opencode.json") } +// UpdateAgentInJSON updates a field for an agent in a JSON configuration file. +// Returns an error if the agent does not exist or the file cannot be updated. func UpdateAgentInJSON(configPath, agentName, field, value string) error { data, err := os.ReadFile(configPath) if err != nil { diff --git a/templates/templates.go b/templates/templates.go index d60e0b2..1ae5dfd 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -17,6 +17,8 @@ var validTemplateName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_\-]*$`) const templatesDirName = "opencode-agent-switcher" +// GetTemplatesDir returns the path to the templates directory. +// Creates the directory if it doesn't exist. Returns an error if creation fails. func GetTemplatesDir() (string, error) { home, err := os.UserHomeDir() if err != nil { @@ -29,6 +31,8 @@ func GetTemplatesDir() (string, error) { return dir, nil } +// ValidateTemplateName validates a template name format. +// Returns an error if the name is empty, exceeds 64 characters, or contains invalid characters. func ValidateTemplateName(name string) error { if name == "" { return fmt.Errorf("template name cannot be empty") @@ -42,6 +46,8 @@ func ValidateTemplateName(name string) error { return nil } +// TemplateExists checks if a template with the given name exists. +// Returns true if the template exists, false otherwise. func TemplateExists(name string) (bool, error) { dir, err := GetTemplatesDir() if err != nil { @@ -55,6 +61,9 @@ func TemplateExists(name string) (bool, error) { return err == nil, err } +// SaveTemplate saves the current agent configuration as a named template. +// Stores the template as a JSON file in the templates directory. +// Returns an error if the name is invalid or the file cannot be written. func SaveTemplate(name string, agents []models.Agent) error { if err := ValidateTemplateName(name); err != nil { return err @@ -89,6 +98,8 @@ func SaveTemplate(name string, agents []models.Agent) error { return os.WriteFile(path, data, 0600) } +// LoadTemplates loads all available templates from the templates directory. +// Returns a sorted list of templates or an error if the directory cannot be read. func LoadTemplates() ([]models.Template, error) { dir, err := GetTemplatesDir() if err != nil { @@ -130,6 +141,8 @@ func LoadTemplates() ([]models.Template, error) { return templates, nil } +// LoadTemplateByName loads a specific template by name. +// Returns the template or an error if it doesn't exist or cannot be parsed. func LoadTemplateByName(name string) (models.Template, error) { dir, err := GetTemplatesDir() if err != nil { @@ -150,6 +163,8 @@ func LoadTemplateByName(name string) (models.Template, error) { return template, nil } +// DeleteTemplate removes a template file from the templates directory. +// Returns an error if the template cannot be deleted. func DeleteTemplate(name string) error { dir, err := GetTemplatesDir() if err != nil { @@ -164,6 +179,8 @@ func DeleteTemplate(name string) error { return nil } +// MatchAgents matches template agents with current agents by name and source. +// Returns a list of matched agents with template assignments and a list of unmatched agent names. func MatchAgents(template models.Template, currentAgents []models.Agent) ([]models.Agent, []string) { var matched []models.Agent var unmatched []string From 365ce77b666d929ec773120abb9bfc4df82ba819 Mon Sep 17 00:00:00 2001 From: Mario <92220954+mario-gc@users.noreply.github.com> Date: Thu, 2 Apr 2026 17:03:13 +0000 Subject: [PATCH 2/2] fix: add comments to models and fix remaining staticcheck issues - Added comments to all exported constants, variables, and types in models/models.go - Added comment block for UI choice constants in cli/prompt.go - Fixed 3 remaining QF1012 staticcheck issues in cli/prompt.go - All 16 golangci-lint issues resolved (13 revive + 3 staticcheck) --- cli/prompt.go | 7 ++++--- models/models.go | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cli/prompt.go b/cli/prompt.go index 6e8c915..a603fb0 100644 --- a/cli/prompt.go +++ b/cli/prompt.go @@ -11,6 +11,7 @@ import ( "github.com/mario-gc/opencode-agent-switcher/models" ) +// UI choice constants for user selections. const ( ExitChoice = "__EXIT__" ContinueChoice = "__CONTINUE__" @@ -654,9 +655,9 @@ func FormatTemplateInspect(template models.Template) string { if modeDisplay == "" { modeDisplay = "all (default)" } - builder.WriteString(fmt.Sprintf(" %s [%s]\n", name, sourceTag)) - builder.WriteString(fmt.Sprintf(" Model: %s\n", assignment.Model)) - builder.WriteString(fmt.Sprintf(" Mode: %s\n", modeDisplay)) + fmt.Fprintf(&builder, " %s [%s]\n", name, sourceTag) + fmt.Fprintf(&builder, " Model: %s\n", assignment.Model) + fmt.Fprintf(&builder, " Mode: %s\n", modeDisplay) } return builder.String() diff --git a/models/models.go b/models/models.go index 8986cfd..6a7a908 100644 --- a/models/models.go +++ b/models/models.go @@ -1,5 +1,6 @@ package models +// Source constants for agent location and format. const ( SourceGlobal = "global" SourceProject = "project" @@ -7,6 +8,7 @@ const ( FormatJSON = "json" ) +// Sort constants for agent and model sorting options. const ( SortAgentAsc = "agent-asc" SortAgentDesc = "agent-desc" @@ -14,18 +16,22 @@ const ( SortModelDesc = "model-desc" ) +// DefaultSort is the default sorting method for agent lists. var DefaultSort = SortAgentAsc +// OpencodeConfig represents the opencode configuration file structure. type OpencodeConfig struct { Provider map[string]Provider `json:"provider"` Agent map[string]AgentConfig `json:"agent"` } +// Provider represents a model provider configuration. type Provider struct { Name string `json:"name"` Models map[string]Model `json:"models"` } +// Model represents a model configuration with limits and modalities. type Model struct { Name string `json:"name"` Limit map[string]int `json:"limit"` @@ -33,23 +39,27 @@ type Model struct { Variants map[string]interface{} `json:"variants"` } +// AgentConfig represents an agent configuration in opencode.json. type AgentConfig struct { Description string `json:"description"` Model string `json:"model"` Mode string `json:"mode"` } +// ModelOption represents a selectable model option. type ModelOption struct { ID string Display string Provider string } +// AgentSource represents the source location and format of an agent. type AgentSource struct { Location string Format string } +// Agent represents a loaded agent with its configuration. type Agent struct { Name string Path string @@ -59,12 +69,14 @@ type Agent struct { Source AgentSource } +// AgentAssignment represents a model and mode assignment for an agent. type AgentAssignment struct { Model string `json:"model"` Mode string `json:"mode"` Source AgentSource `json:"source"` } +// Template represents a saved agent configuration template. type Template struct { Name string `json:"name"` CreatedAt string `json:"created_at"`