Files
iris-context/internal/ai/utils.go
T
VERSE 1e04949242 refactor: better project structure (#35)
This project is starting to grow, and I've seen flaws in the initial
project structure design
I think I'll restructure the entire project directory for better
scalability and also cleaner
2026-07-11 16:15:05 +07:00

168 lines
5.1 KiB
Go

package ai
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/versenilvis/iris/spec"
)
func CleanSuggestion(raw string) string {
s := strings.TrimSpace(raw)
if strings.HasPrefix(s, "```") {
lines := strings.Split(s, "\n")
if len(lines) > 1 {
endIdx := len(lines)
if strings.HasPrefix(strings.TrimSpace(lines[len(lines)-1]), "```") {
endIdx = len(lines) - 1
}
s = strings.TrimSpace(strings.Join(lines[1:endIdx], "\n"))
}
}
if len(s) >= 2 && strings.HasPrefix(s, "`") && strings.HasSuffix(s, "`") && !strings.HasPrefix(s, "``") {
s = s[1 : len(s)-1]
}
if len(s) >= 2 && ((strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"")) || (strings.HasPrefix(s, "'") && strings.HasSuffix(s, "'"))) {
inner := s[1 : len(s)-1]
if !strings.ContainsAny(inner, "\"'") {
s = inner
}
}
return strings.TrimSpace(s)
}
func NormalizeSuggestion(buf string, suggCmd string) string {
suggCmd = CleanSuggestion(suggCmd)
if strings.Contains(buf, "-m \"") || strings.Contains(buf, "-am \"") || strings.Contains(buf, "--message \"") {
if !strings.HasPrefix(strings.ToLower(suggCmd), strings.ToLower(buf)) {
for _, flag := range []string{"-m ", "-am ", "--message "} {
idx := strings.Index(suggCmd, flag)
if idx != -1 {
afterFlag := suggCmd[idx+len(flag):]
if !strings.HasPrefix(afterFlag, "\"") && !strings.HasPrefix(afterFlag, "'") {
suggCmd = suggCmd[:idx+len(flag)] + "\"" + afterFlag + "\""
break
}
}
}
}
}
return suggCmd
}
func ShouldOverwrite(originalBuf string, currentBuf string, newSugg *spec.Suggestion, currentConfidence int) bool {
if newSugg == nil {
return false
}
if !strings.HasPrefix(currentBuf, originalBuf) {
return false
}
if !strings.HasPrefix(strings.ToLower(newSugg.Cmd), strings.ToLower(currentBuf)) {
return false
}
return newSugg.Confidence > currentConfidence
}
func ExtractScriptsAndTargets(sb *strings.Builder, dir string, prefix string) {
if data, err := os.ReadFile(filepath.Join(dir, "package.json")); err == nil {
var pkg struct {
Scripts map[string]string `json:"scripts"`
}
if err := json.Unmarshal(data, &pkg); err == nil && len(pkg.Scripts) > 0 {
var scriptNames []string
for name, cmd := range pkg.Scripts {
scriptNames = append(scriptNames, fmt.Sprintf("%s: %s", name, cmd))
}
sort.Strings(scriptNames)
if len(scriptNames) > 20 {
scriptNames = append(scriptNames[:20], "... (truncated)")
}
label := "package.json"
if prefix != "" {
label = prefix + "/package.json"
}
fmt.Fprintf(sb, "Available %s scripts:\n%s\n\n", label, strings.Join(scriptNames, "\n"))
}
}
if file, err := os.Open(filepath.Join(dir, "Makefile")); err == nil {
defer func() { _ = file.Close() }()
var targets []string
seen := make(map[string]bool)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// Skip variable assignments because operators like := or colons in values trick the colon parser into misclassifying variables as build targets
if strings.Contains(line, "=") {
continue
}
if idx := strings.Index(line, ":"); idx > 0 && !strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, " ") {
target := strings.TrimSpace(line[:idx])
if target != "" && target != ".PHONY" && !strings.Contains(target, " ") && !seen[target] && !strings.HasPrefix(target, ".") {
seen[target] = true
targets = append(targets, target)
}
}
}
_ = scanner.Err()
if len(targets) > 0 {
sort.Strings(targets)
// Cap at 10 targets to keep AI prompt short and avoid exceeding 6000 TPM limit (basedd on Groq api docs because Im using it now)
if len(targets) > 10 {
targets = append(targets[:10], "... (truncated)")
}
label := "Makefile"
if prefix != "" {
label = prefix + "/Makefile"
}
fmt.Fprintf(sb, "Available %s targets:\n%s\n\n", label, strings.Join(targets, ", "))
}
}
openJustfile := func() (*os.File, error) {
if f, err := os.Open(filepath.Join(dir, "justfile")); err == nil {
return f, nil
}
return os.Open(filepath.Join(dir, "Justfile"))
}
if file, err := openJustfile(); err == nil {
defer func() { _ = file.Close() }()
var recipes []string
seen := make(map[string]bool)
scanner := bufio.NewScanner(file)
recipeRegex := regexp.MustCompile(`^([a-zA-Z0-9_-]+):`)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "[") {
continue
}
if matches := recipeRegex.FindStringSubmatch(line); len(matches) > 1 {
recipe := matches[1]
if !seen[recipe] {
seen[recipe] = true
recipes = append(recipes, recipe)
}
}
}
_ = scanner.Err()
if len(recipes) > 0 {
sort.Strings(recipes)
// Cap at 10 recipes to keep AI prompt short and avoid exceeding 6000 TPM limit
if len(recipes) > 10 {
recipes = append(recipes[:10], "... (truncated)")
}
label := "justfile"
if prefix != "" {
label = prefix + "/justfile"
}
fmt.Fprintf(sb, "Available %s recipes:\n%s\n\n", label, strings.Join(recipes, ", "))
}
}
}