Speed up first use of repo backed VMs by bootstrapping obvious tools before the best effort LLM harness runs. Add a host side tooling plan for pinned Go, Node, Python, and Rust versions, summarize that plan in the uploaded prompt, and run repo mise install plus guest global mise use -g --pin steps before the bounded opencode inspection. Keep the harness non fatal, prefer host opencode attach when the client supports it, fall back to guest opencode over SSH for older clients, and cover the new flow with CLI plus planner tests. Validation: - go test ./internal/cli ./internal/toolingplan - GOCACHE=/tmp/banger-gocache go test ./... - make build
88 lines
1.9 KiB
Go
88 lines
1.9 KiB
Go
package toolingplan
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
toml "github.com/pelletier/go-toml"
|
|
)
|
|
|
|
func repoManagedTools(repoRoot string) (map[string]struct{}, []SkipNote) {
|
|
tools := make(map[string]struct{})
|
|
skips := make([]SkipNote, 0)
|
|
if err := collectToolVersions(filepath.Join(repoRoot, ".tool-versions"), tools); err != nil {
|
|
skips = append(skips, SkipNote{
|
|
Target: "repo mise declarations",
|
|
Reason: fmt.Sprintf("could not read .tool-versions: %v", err),
|
|
})
|
|
}
|
|
if err := collectMiseToml(filepath.Join(repoRoot, ".mise.toml"), tools); err != nil {
|
|
skips = append(skips, SkipNote{
|
|
Target: "repo mise declarations",
|
|
Reason: fmt.Sprintf("could not parse .mise.toml: %v", err),
|
|
})
|
|
}
|
|
return tools, skips
|
|
}
|
|
|
|
func collectToolVersions(path string, tools map[string]struct{}) error {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) == 0 {
|
|
continue
|
|
}
|
|
tools[fields[0]] = struct{}{}
|
|
}
|
|
return scanner.Err()
|
|
}
|
|
|
|
func collectMiseToml(path string, tools map[string]struct{}) error {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
tree, err := toml.LoadBytes(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value := tree.Get("tools")
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
switch typed := value.(type) {
|
|
case *toml.Tree:
|
|
for _, key := range typed.Keys() {
|
|
tools[key] = struct{}{}
|
|
}
|
|
case map[string]interface{}:
|
|
keys := make([]string, 0, len(typed))
|
|
for key := range typed {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
tools[key] = struct{}{}
|
|
}
|
|
}
|
|
return nil
|
|
}
|