package cmd import ( "encoding/json" "fmt" "net/url" "os" "os/exec" "sort" "strings" "unicode" "github.com/spf13/cobra" ) var ( autoDryRun bool autoJSON bool autoLang string ) var autoCmd = &cobra.Command{ Use: "auto ", Short: "Route natural-language intent to the best Devour command", Long: `Auto-classify intent and run the best matching command (get/scrape/ask/quality). Examples: devour auto "how to parse json in go" devour auto "https://pkg.go.dev/net/http" devour auto "check code quality" --dry-run devour auto "what is useEffect" --lang react`, Args: cobra.MinimumNArgs(1), RunE: runAuto, } func init() { autoCmd.Flags().BoolVar(&autoDryRun, "dry-run", false, "print selected command without executing") autoCmd.Flags().BoolVar(&autoJSON, "json", false, "output route decision as JSON") autoCmd.Flags().StringVar(&autoLang, "lang", "", "optional language override for ask/get routes") } type autoDecision struct { Intent string `json:"intent"` Route string `json:"route"` Reason string `json:"reason"` Command []string `json:"command"` } func runAuto(cmd *cobra.Command, args []string) error { intent := strings.TrimSpace(strings.Join(args, " ")) if intent == "" { return fmt.Errorf("intent is required") } decision, err := classifyIntent(intent, strings.TrimSpace(autoLang)) if err != nil { return err } if autoJSON { enc := json.NewEncoder(cmd.OutOrStdout()) enc.SetIndent("", " ") return enc.Encode(decision) } fmt.Printf("Route: %s\n", decision.Route) fmt.Printf("Reason: %s\n", decision.Reason) fmt.Printf("Command: devour %s\n", strings.Join(decision.Command, " ")) if autoDryRun { return nil } exe, err := os.Executable() if err != nil { return err } run := exec.Command(exe, decision.Command...) run.Stdout = cmd.OutOrStdout() run.Stderr = cmd.ErrOrStderr() return run.Run() } func classifyIntent(intent, langOverride string) (*autoDecision, error) { lower := strings.ToLower(intent) trimmed := strings.TrimSpace(intent) if u, err := url.Parse(trimmed); err == nil && (u.Scheme == "http" || u.Scheme == "https") { route := []string{"scrape", trimmed} return &autoDecision{Intent: intent, Route: "scrape", Reason: "detected URL input", Command: route}, nil } if strings.Contains(lower, "quality") || strings.Contains(lower, "technical debt") || strings.Contains(lower, "lint") || strings.Contains(lower, "code smell") { route := []string{"quality", "status"} if strings.Contains(lower, "scan") { route = []string{"quality", "scan", "."} } return &autoDecision{Intent: intent, Route: "quality", Reason: "detected quality-analysis intent", Command: route}, nil } language := strings.TrimSpace(langOverride) if language == "" { language = inferLanguageFromText(lower) } if language != "" { if canonical, ok := normalizeLanguage(language); ok { language = canonical } else { language = "" } } if strings.Contains(lower, "?") || strings.Contains(lower, "how") || strings.Contains(lower, "why") || strings.Contains(lower, "what") { if language == "" { language = "go" } route := []string{"ask", "--lang", language, intent, "--format", "text"} return &autoDecision{Intent: intent, Route: "ask", Reason: "question-style intent", Command: route}, nil } if language == "" { language = "go" } keyword := inferKeyword(intent) if canonical, ok := normalizeLanguage(keyword); ok && canonical == language { keyword = "overview" } route := []string{"get", language, keyword} return &autoDecision{Intent: intent, Route: "get", Reason: "default docs retrieval route", Command: route}, nil } func inferLanguageFromText(text string) string { text = strings.ToLower(text) if strings.Contains(text, "c#") { return "csharp" } if strings.Contains(text, "next.js") { return "nextjs" } tokens := strings.FieldsFunc(text, func(r rune) bool { return !(unicode.IsLetter(r) || unicode.IsDigit(r)) }) tokenSet := make(map[string]bool, len(tokens)) for _, tok := range tokens { if tok != "" { tokenSet[tok] = true } } aliases := make([]string, 0, len(languageAliases())) for alias := range languageAliases() { aliases = append(aliases, alias) } sort.Slice(aliases, func(i, j int) bool { return len(aliases[i]) > len(aliases[j]) }) for _, alias := range aliases { if tokenSet[alias] { return alias } } return "" } func inferKeyword(intent string) string { words := strings.Fields(strings.ToLower(intent)) stop := map[string]bool{ "get": true, "docs": true, "documentation": true, "about": true, "for": true, "on": true, "the": true, "a": true, "an": true, "show": true, "me": true, "please": true, } for _, w := range words { w = strings.Trim(w, ",.!?;:") if w == "" || stop[w] || len(w) < 2 { continue } return w } return "overview" }