package ai import ( "fmt" "strings" "time" "gitea.oblak.solutions/dimitar/gitFlow/pkg/status" ) // BuildPrompt renders a scan result as an LLM instruction: a repository // status table plus strict output rules for the suggestion schema. func BuildPrompt(result status.ScanResult) string { var b strings.Builder fmt.Fprintf(&b, "You are gitflow, a Git repository health assistant. "+ "A scan of %q at %s found %d repositories.\n\n", result.ParentDir, result.ScannedAt.Format(time.RFC3339), len(result.Repos)) b.WriteString("Repository status table:\n") b.WriteString("| name | status | branch | ahead | behind | changes |\n") b.WriteString("|---|---|---|---|---|---|\n") for _, r := range result.Repos { fmt.Fprintf(&b, "| %s | %s | %s | %d | %d | %s |\n", r.Name, r.Status, branchLabel(r), r.AheadBy, r.BehindBy, changeSummary(r)) } attention := result.NeedsAttention() fmt.Fprintf(&b, "\n%d of %d repositories need attention.\n\n", len(attention), len(result.Repos)) b.WriteString(`Return ONLY a JSON array of suggestions. Each suggestion has exactly these fields: - "repo_path": absolute path of the repository - "action": one of "commit", "push", "pull", "stash", "create_pr", "cleanup", "inspect" - "message": one short sentence explaining the next step - "command": a concrete git command to run, or "" if none is safe - "priority": 0 (low), 1 (medium), or 2 (high) Rules: - Only suggest actions for repositories that need attention. - Prefer the smallest safe step; never suggest destructive commands. - Do not invent repositories that are not in the table. - If nothing needs attention, return []. `) return b.String() } func branchLabel(r status.RepoInfo) string { if r.Branch == "" { return "(none)" } return r.Branch } // changeSummary renders staged/modified/untracked counts for the prompt. func changeSummary(r status.RepoInfo) string { parts := make([]string, 0, 3) if n := len(r.StagedFiles); n > 0 { parts = append(parts, fmt.Sprintf("%d staged", n)) } if n := len(r.ModifiedFiles); n > 0 { parts = append(parts, fmt.Sprintf("%d modified", n)) } if n := len(r.UntrackedFiles); n > 0 { parts = append(parts, fmt.Sprintf("%d untracked", n)) } if len(parts) == 0 { return "none" } return strings.Join(parts, ", ") }