Merge pull request 'chore: Go 1.26 modernization' (#1) from chore/go-1.26-modernization into main
All checks were successful
Security Scan / security (push) Successful in 7s
Test / test (push) Successful in 2m51s

This commit is contained in:
Charon 2026-02-24 18:01:42 +00:00
commit 8f3722f7bd
33 changed files with 126 additions and 109 deletions

View file

@ -161,7 +161,7 @@ func (b *HTTPBackend) doRequest(ctx context.Context, body []byte) (string, error
}
if len(chatResp.Choices) == 0 {
return "", fmt.Errorf("no choices in response")
return "", errors.New("no choices in response")
}
return chatResp.Choices[0].Message.Content, nil

View file

@ -4,7 +4,7 @@ package ml
import (
"context"
"fmt"
"errors"
"iter"
"forge.lthn.ai/core/go-inference"
@ -73,7 +73,7 @@ func (m *HTTPTextModel) Chat(ctx context.Context, messages []inference.Message,
// Classify is not supported by HTTP backends. Returns an error.
func (m *HTTPTextModel) Classify(_ context.Context, _ []string, _ ...inference.GenerateOption) ([]inference.ClassifyResult, error) {
return nil, fmt.Errorf("classify not supported by HTTP backend")
return nil, errors.New("classify not supported by HTTP backend")
}
// BatchGenerate processes multiple prompts sequentially via Generate.

View file

@ -5,6 +5,7 @@ package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"maps"
@ -319,7 +320,7 @@ func runAB(cmd *cli.Command, args []string) error {
}
if len(results) == 0 {
return fmt.Errorf("no results to compare")
return errors.New("no results to compare")
}
// Build condition summaries
@ -551,7 +552,7 @@ func loadABProbes() ([]abProbe, error) {
func loadABKernels() ([]abKernelDef, error) {
if len(abKernels) == 0 {
return nil, fmt.Errorf("at least one --kernel is required (raw file content is used as system message with zero instruction)")
return nil, errors.New("at least one --kernel is required (raw file content is used as system message with zero instruction)")
}
var defs []abKernelDef

View file

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
"path/filepath"
@ -32,7 +33,7 @@ func runApprove(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB required")
return errors.New("--db or LEM_DB required")
}
output := approveOutput

View file

@ -3,19 +3,21 @@
package cmd
import (
"cmp"
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"math"
"os"
"runtime"
"sort"
"slices"
"time"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/go-i18n/reversal"
"forge.lthn.ai/core/go-ml"
"forge.lthn.ai/core/cli/pkg/cli"
)
// grammarScore holds grammar v3 quality signals derived from a GrammarImprint.
@ -65,10 +67,10 @@ func computeGrammarScore(imp reversal.GrammarImprint) grammarScore {
}
tenseNorm := gs.TenseEntropy / 1.585 // max entropy for 3 tenses = log2(3)
vocabNorm := math.Min(gs.VocabRichness*10, 1.0)
questionNorm := math.Min(gs.QuestionRatio*5, 1.0)
verbNorm := math.Min(float64(gs.VerbDiversity)/30.0, 1.0)
nounNorm := math.Min(float64(gs.NounDiversity)/40.0, 1.0)
vocabNorm := min(gs.VocabRichness*10, 1.0)
questionNorm := min(gs.QuestionRatio*5, 1.0)
verbNorm := min(float64(gs.VerbDiversity)/30.0, 1.0)
nounNorm := min(float64(gs.NounDiversity)/40.0, 1.0)
gs.Composite = 0.25*tenseNorm +
0.25*vocabNorm +
@ -176,16 +178,16 @@ type benchmarkResult struct {
// benchmarkSummary holds aggregate comparison metrics.
type benchmarkSummary struct {
BaselineModel string `json:"baseline_model"`
TrainedModel string `json:"trained_model"`
TotalPrompts int `json:"total_prompts"`
AvgBaselineLEK float64 `json:"avg_baseline_lek"`
AvgTrainedLEK float64 `json:"avg_trained_lek"`
AvgDelta float64 `json:"avg_delta"`
Improved int `json:"improved"`
Regressed int `json:"regressed"`
Unchanged int `json:"unchanged"`
Duration string `json:"duration"`
BaselineModel string `json:"baseline_model"`
TrainedModel string `json:"trained_model"`
TotalPrompts int `json:"total_prompts"`
AvgBaselineLEK float64 `json:"avg_baseline_lek"`
AvgTrainedLEK float64 `json:"avg_trained_lek"`
AvgDelta float64 `json:"avg_delta"`
Improved int `json:"improved"`
Regressed int `json:"regressed"`
Unchanged int `json:"unchanged"`
Duration string `json:"duration"`
// Grammar v3 aggregates
AvgBaselineGrammar float64 `json:"avg_baseline_grammar"`
@ -351,7 +353,7 @@ func runBenchmark(cmd *cli.Command, args []string) error {
n := float64(len(results))
if n == 0 {
return fmt.Errorf("no results to compare")
return errors.New("no results to compare")
}
summary := benchmarkSummary{
@ -464,6 +466,6 @@ func loadBenchmarkPrompts() ([]benchPrompt, error) {
prompts = append(prompts, benchPrompt{id: id, prompt: r.Prompt})
}
sort.Slice(prompts, func(i, j int) bool { return prompts[i].id < prompts[j].id })
slices.SortFunc(prompts, func(a, b benchPrompt) int { return cmp.Compare(a.id, b.id) })
return prompts, nil
}

View file

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
@ -21,7 +22,7 @@ func runCoverage(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB required")
return errors.New("--db or LEM_DB required")
}
db, err := ml.OpenDB(path)

View file

@ -2,6 +2,7 @@ package cmd
import (
"context"
"errors"
"fmt"
"os"
@ -10,10 +11,10 @@ import (
)
var (
expandWorker string
expandOutput string
expandLimit int
expandDryRun bool
expandWorker string
expandOutput string
expandLimit int
expandDryRun bool
)
var expandCmd = &cli.Command{
@ -32,7 +33,7 @@ func init() {
func runExpand(cmd *cli.Command, args []string) error {
if modelName == "" {
return fmt.Errorf("--model is required")
return errors.New("--model is required")
}
path := dbPath
@ -40,7 +41,7 @@ func runExpand(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB env is required")
return errors.New("--db or LEM_DB env is required")
}
if expandWorker == "" {

View file

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
@ -21,7 +22,7 @@ func runExpandStatus(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB required")
return errors.New("--db or LEM_DB required")
}
db, err := ml.OpenDB(path)
@ -80,8 +81,8 @@ func runExpandStatus(cmd *cli.Command, args []string) error {
return nil
}
// toInt converts an interface{} (typically from QueryRows) to int.
func toInt(v interface{}) int {
// toInt converts an any (typically from QueryRows) to int.
func toInt(v any) int {
switch n := v.(type) {
case int:
return n

View file

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
@ -46,7 +47,7 @@ func runExport(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB env is required")
return errors.New("--db or LEM_DB env is required")
}
db, err := ml.OpenDB(path)

View file

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
"path/filepath"
@ -17,7 +18,7 @@ var importCmd = &cli.Command{
}
var (
importSkipM3 bool
importSkipM3 bool
importDataDir string
importM3Host string
)
@ -34,7 +35,7 @@ func runImportAll(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB required")
return errors.New("--db or LEM_DB required")
}
dataDir := importDataDir

View file

@ -1,7 +1,7 @@
package cmd
import (
"fmt"
"errors"
"os"
"forge.lthn.ai/core/cli/pkg/cli"
@ -33,10 +33,10 @@ func init() {
func runIngest(cmd *cli.Command, args []string) error {
if modelName == "" {
return fmt.Errorf("--model is required")
return errors.New("--model is required")
}
if ingestContent == "" && ingestCapability == "" && ingestTraining == "" {
return fmt.Errorf("at least one of --content, --capability, or --training-log is required")
return errors.New("at least one of --content, --capability, or --training-log is required")
}
influx := ml.NewInfluxClient(influxURL, influxDB)

View file

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
@ -21,7 +22,7 @@ func runInventory(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB required")
return errors.New("--db or LEM_DB required")
}
db, err := ml.OpenDB(path)

View file

@ -5,6 +5,7 @@ package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
@ -13,8 +14,8 @@ import (
"strings"
"time"
"forge.lthn.ai/core/go-ml"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/go-ml"
"gopkg.in/yaml.v3"
)
@ -90,9 +91,9 @@ type lessonPrompt struct {
// lessonState tracks progress through a lesson.
type lessonState struct {
LessonID string `json:"lesson_id"`
Completed map[string]lessonResult `json:"completed"`
UpdatedAt string `json:"updated_at"`
LessonID string `json:"lesson_id"`
Completed map[string]lessonResult `json:"completed"`
UpdatedAt string `json:"updated_at"`
}
type lessonResult struct {
@ -162,7 +163,7 @@ func runLesson(cmd *cli.Command, args []string) error {
)
if len(lesson.Prompts) == 0 {
return fmt.Errorf("lesson has no prompts")
return errors.New("lesson has no prompts")
}
// Load state for resume

View file

@ -71,7 +71,7 @@ func runLive(cmd *cli.Command, args []string) error {
}
// sqlScalar extracts the first numeric value from a QuerySQL result.
func sqlScalar(rows []map[string]interface{}) int {
func sqlScalar(rows []map[string]any) int {
if len(rows) == 0 {
return 0
}

View file

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
@ -21,7 +22,7 @@ func runMetrics(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB required")
return errors.New("--db or LEM_DB required")
}
db, err := ml.OpenDB(path)

View file

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
@ -27,7 +28,7 @@ func runNormalize(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB env is required")
return errors.New("--db or LEM_DB env is required")
}
db, err := ml.OpenDBReadWrite(path)

View file

@ -3,6 +3,7 @@ package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
@ -27,7 +28,7 @@ func init() {
func runProbe(cmd *cli.Command, args []string) error {
if apiURL == "" {
return fmt.Errorf("--api-url is required")
return errors.New("--api-url is required")
}
model := modelName

View file

@ -2,6 +2,7 @@ package cmd
import (
"encoding/json"
"errors"
"fmt"
"maps"
"os"
@ -35,7 +36,7 @@ func runQuery(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB env is required")
return errors.New("--db or LEM_DB env is required")
}
db, err := ml.OpenDB(path)
@ -129,7 +130,7 @@ func runQuery(cmd *cli.Command, args []string) error {
return nil
}
func formatValue(v interface{}) string {
func formatValue(v any) string {
if v == nil {
return "NULL"
}

View file

@ -5,14 +5,15 @@ package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
"runtime"
"time"
"forge.lthn.ai/core/go-ml"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/go-ml"
)
var sandwichCmd = &cli.Command{
@ -108,7 +109,7 @@ func runSandwich(cmd *cli.Command, args []string) error {
)
if len(seeds) == 0 {
return fmt.Errorf("no seed prompts found")
return errors.New("no seed prompts found")
}
// Open output file

View file

@ -1,6 +1,7 @@
package cmd
import (
"errors"
"fmt"
"os"
@ -31,7 +32,7 @@ func runSeedInflux(cmd *cli.Command, args []string) error {
path = os.Getenv("LEM_DB")
}
if path == "" {
return fmt.Errorf("--db or LEM_DB required")
return errors.New("--db or LEM_DB required")
}
db, err := ml.OpenDB(path)

View file

@ -4,6 +4,7 @@ package cmd
import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
@ -11,8 +12,8 @@ import (
"strings"
"time"
"forge.lthn.ai/core/go-ml"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/go-ml"
"gopkg.in/yaml.v3"
)
@ -70,10 +71,10 @@ type sequenceDef struct {
// sequenceState tracks progress through a sequence.
type sequenceState struct {
SequenceID string `json:"sequence_id"`
Completed map[string]bool `json:"completed"` // lesson ID → done
Current string `json:"current"`
UpdatedAt string `json:"updated_at"`
SequenceID string `json:"sequence_id"`
Completed map[string]bool `json:"completed"` // lesson ID → done
Current string `json:"current"`
UpdatedAt string `json:"updated_at"`
}
func runSequence(cmd *cli.Command, args []string) error {
@ -103,7 +104,7 @@ func runSequence(cmd *cli.Command, args []string) error {
modelPath = seq.ModelPath
}
if modelPath == "" {
return fmt.Errorf("model-path is required (flag or sequence YAML)")
return errors.New("model-path is required (flag or sequence YAML)")
}
// Resolve output

View file

@ -10,6 +10,7 @@ package cmd
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
@ -17,9 +18,10 @@ import (
"strings"
"time"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/go-ml"
"forge.lthn.ai/core/go-mlx"
"forge.lthn.ai/core/cli/pkg/cli"
"github.com/ollama/ollama/tokenizer"
)
var trainCmd = &cli.Command{
@ -36,15 +38,15 @@ Training data format (one JSON object per line):
}
var (
trainModelPath string
trainData string
trainOutput string
trainRank int
trainAlpha float64
trainLR float64
trainEpochs int
trainMaxSeqLen int
trainTargets string
trainModelPath string
trainData string
trainOutput string
trainRank int
trainAlpha float64
trainLR float64
trainEpochs int
trainMaxSeqLen int
trainTargets string
trainMemoryLimit int
)
@ -113,7 +115,7 @@ func runTrain(cmd *cli.Command, args []string) error {
slog.Info("training data loaded", "samples", len(samples))
if len(samples) == 0 {
return fmt.Errorf("no training samples loaded")
return errors.New("no training samples loaded")
}
// --- Training loop ---
@ -129,7 +131,7 @@ func runTrain(cmd *cli.Command, args []string) error {
var totalLoss float64
var totalSteps int
for epoch := 0; epoch < trainEpochs; epoch++ {
for epoch := range trainEpochs {
var epochLoss float64
epochStart := time.Now()

View file

@ -2,7 +2,8 @@ package ml
import (
"fmt"
"sort"
"maps"
"slices"
)
// RunCompare reads two score files and prints a comparison table for each
@ -28,13 +29,7 @@ func RunCompare(oldPath, newPath string) error {
}
// Sort model names for deterministic output.
sortedModels := make([]string, 0, len(models))
for m := range models {
sortedModels = append(sortedModels, m)
}
sort.Strings(sortedModels)
for _, model := range sortedModels {
for _, model := range slices.Sorted(maps.Keys(models)) {
oldAvgs := oldOutput.ModelAverages[model]
newAvgs := newOutput.ModelAverages[model]
@ -54,13 +49,7 @@ func RunCompare(oldPath, newPath string) error {
metrics[k] = true
}
sortedMetrics := make([]string, 0, len(metrics))
for k := range metrics {
sortedMetrics = append(sortedMetrics, k)
}
sort.Strings(sortedMetrics)
for _, metric := range sortedMetrics {
for _, metric := range slices.Sorted(maps.Keys(metrics)) {
oldVal := oldAvgs[metric]
newVal := newAvgs[metric]
delta := newVal - oldVal

View file

@ -3,6 +3,7 @@ package ml
import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"log"
"maps"
@ -52,7 +53,7 @@ func ReadSafetensors(path string) (map[string]SafetensorsTensorInfo, []byte, err
}
if len(data) < 8 {
return nil, nil, fmt.Errorf("file too small")
return nil, nil, errors.New("file too small")
}
headerSize := int(binary.LittleEndian.Uint64(data[:8]))

View file

@ -1,6 +1,7 @@
package ml
import (
"errors"
"fmt"
"io"
"strings"
@ -21,7 +22,7 @@ func PrintCoverage(db *DB, w io.Writer) error {
return fmt.Errorf("count seeds: %w (run: core ml import-all first)", err)
}
if len(rows) == 0 {
return fmt.Errorf("no seeds table found (run: core ml import-all first)")
return errors.New("no seeds table found (run: core ml import-all first)")
}
total := toInt(rows[0]["total"])

View file

@ -1,6 +1,7 @@
package ml
import (
"cmp"
"encoding/binary"
"encoding/json"
"fmt"
@ -8,7 +9,7 @@ import (
"math"
"os"
"regexp"
"sort"
"slices"
"strconv"
"strings"
)
@ -174,8 +175,8 @@ func ConvertMLXtoGGUFLoRA(safetensorsPath, configPath, outputPath, architecture
}
}
sort.Slice(ggufTensors, func(i, j int) bool {
return ggufTensors[i].name < ggufTensors[j].name
slices.SortFunc(ggufTensors, func(a, b ggufTensor) int {
return cmp.Compare(a.name, b.name)
})
metadata := []ggufMetadata{

View file

@ -1,7 +1,6 @@
package ml
import (
"math"
"regexp"
"strings"
)
@ -122,7 +121,7 @@ func scoreCreativeForm(response string) int {
// Metaphor density.
metaphorCount := len(metaphorPattern.FindAllString(response, -1))
score += int(math.Min(float64(metaphorCount), 3))
score += min(metaphorCount, 3)
return score
}
@ -147,7 +146,7 @@ func scoreEngagementDepth(response string) int {
// Tech depth.
techCount := len(techDepthPattern.FindAllString(response, -1))
score += int(math.Min(float64(techCount), 3))
score += min(techCount, 3)
// Word count bonuses.
words := len(strings.Fields(response))

View file

@ -3,6 +3,7 @@ package ml
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"os"
@ -59,10 +60,10 @@ var (
// At least one of ContentFile, CapabilityFile, or TrainingLog must be set.
func Ingest(influx *InfluxClient, cfg IngestConfig, w io.Writer) error {
if cfg.ContentFile == "" && cfg.CapabilityFile == "" && cfg.TrainingLog == "" {
return fmt.Errorf("at least one of --content, --capability, or --training-log is required")
return errors.New("at least one of --content, --capability, or --training-log is required")
}
if cfg.Model == "" {
return fmt.Errorf("--model is required")
return errors.New("--model is required")
}
if cfg.RunID == "" {
cfg.RunID = cfg.Model
@ -362,7 +363,7 @@ func extractIteration(label string) int {
return n
}
// toFloat64 converts a JSON-decoded interface{} value to float64.
// toFloat64 converts a JSON-decoded any value to float64.
// Handles float64 (standard json.Unmarshal), json.Number, and string values.
func toFloat64(v any) (float64, bool) {
switch val := v.(type) {

View file

@ -1,6 +1,7 @@
package ml
import (
"errors"
"fmt"
"io"
"strings"
@ -29,7 +30,7 @@ func NormalizeSeeds(db *DB, cfg NormalizeConfig, w io.Writer) error {
fmt.Fprintf(w, "Seeds table: %d rows\n", seedCount)
if seedCount == 0 {
return fmt.Errorf("seeds table is empty, nothing to normalize")
return errors.New("seeds table is empty, nothing to normalize")
}
// 2. Drop and recreate expansion_prompts.

View file

@ -2,6 +2,7 @@ package ml
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
@ -34,12 +35,12 @@ type uploadEntry struct {
// or ~/.huggingface/token, in that order.
func Publish(cfg PublishConfig, w io.Writer) error {
if cfg.InputDir == "" {
return fmt.Errorf("input directory is required")
return errors.New("input directory is required")
}
token := resolveHFToken(cfg.Token)
if token == "" && !cfg.DryRun {
return fmt.Errorf("HuggingFace token required (--token, HF_TOKEN env, or ~/.huggingface/token)")
return errors.New("HuggingFace token required (--token, HF_TOKEN env, or ~/.huggingface/token)")
}
files, err := collectUploadFiles(cfg.InputDir)

View file

@ -2,6 +2,7 @@ package ml
import (
"context"
"errors"
"fmt"
"iter"
"slices"
@ -165,7 +166,7 @@ func (s *Service) Generate(ctx context.Context, backendName, prompt string, opts
// ScoreResponses scores a batch of responses using the configured engine.
func (s *Service) ScoreResponses(ctx context.Context, responses []Response) (map[string][]PromptScore, error) {
if s.engine == nil {
return nil, fmt.Errorf("scoring engine not configured (set JudgeURL and JudgeModel)")
return nil, errors.New("scoring engine not configured (set JudgeURL and JudgeModel)")
}
return s.engine.ScoreAll(ctx, responses), nil
}

View file

@ -1,9 +1,10 @@
package ml
import (
"cmp"
"fmt"
"io"
"sort"
"slices"
)
// trainingRow holds deduplicated training status + loss for a single model.
@ -146,8 +147,8 @@ func dedupeTraining(statusRows, lossRows []map[string]any) []trainingRow {
rows = append(rows, tr)
}
sort.Slice(rows, func(i, j int) bool {
return rows[i].model < rows[j].model
slices.SortFunc(rows, func(a, b trainingRow) int {
return cmp.Compare(a.model, b.model)
})
return rows
@ -172,8 +173,8 @@ func dedupeGeneration(rows []map[string]any) []genRow {
})
}
sort.Slice(result, func(i, j int) bool {
return result[i].worker < result[j].worker
slices.SortFunc(result, func(a, b genRow) int {
return cmp.Compare(a.worker, b.worker)
})
return result

View file

@ -3,6 +3,7 @@ package ml
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
@ -271,7 +272,7 @@ func workerInfer(cfg *WorkerConfig, task APITask) (string, error) {
}
if len(chatResp.Choices) == 0 {
return "", fmt.Errorf("no choices in response")
return "", errors.New("no choices in response")
}
content := chatResp.Choices[0].Message.Content