// Package config loads the two YAML profiles documented in // configs/{model,review}-profile.example.yaml. Both are optional — // callers that don't pass --review-profile or --model-profile get // DefaultReviewProfile / DefaultModelProfile. // // Defaults reflect the 2026-04-30 small-model-pipeline tier bump: // local model is qwen3.5:latest, not qwen2.5-coder. The example // YAML in configs/ still says qwen2.5-coder per PROMPT.md as // originally authored — operators who copy the example get that; // operators who skip the file get the current default. package config import ( "fmt" "os" "gopkg.in/yaml.v3" ) // ModelProfile mirrors configs/model-profile.example.yaml. type ModelProfile struct { Provider string `yaml:"provider"` BaseURL string `yaml:"base_url"` Model string `yaml:"model"` FallbackModel string `yaml:"fallback_model"` TimeoutSeconds int `yaml:"timeout_seconds"` Temperature float64 `yaml:"temperature"` } // ReviewProfile mirrors configs/review-profile.example.yaml. // Toggles disable analyzers without code changes — review-profile // drives noisy-check tuning per repo. type ReviewProfile struct { ProjectName string `yaml:"project_name"` Mode string `yaml:"mode"` SeverityThresholds struct { FailOnCritical bool `yaml:"fail_on_critical"` FailOnHigh bool `yaml:"fail_on_high"` } `yaml:"severity_thresholds"` StaticChecks struct { HardcodedPaths bool `yaml:"hardcoded_paths"` RawSQLInterpolation bool `yaml:"raw_sql_interpolation"` ShellExecution bool `yaml:"shell_execution"` BroadCORS bool `yaml:"broad_cors"` SecretPatterns bool `yaml:"secret_patterns"` LargeFiles bool `yaml:"large_files"` TODOComments bool `yaml:"todo_comments"` MissingTests bool `yaml:"missing_tests"` } `yaml:"static_checks"` Limits struct { LargeFileLines int `yaml:"large_file_lines"` MaxFileBytes int `yaml:"max_file_bytes"` MaxLLMChunkChars int `yaml:"max_llm_chunk_chars"` } `yaml:"limits"` Reports struct { OutputDir string `yaml:"output_dir"` Markdown bool `yaml:"markdown"` JSONReceipts bool `yaml:"json_receipts"` } `yaml:"reports"` Memory struct { Enabled bool `yaml:"enabled"` Path string `yaml:"path"` AppendOnly bool `yaml:"append_only"` } `yaml:"memory"` } // DefaultModelProfile reflects the current Lakehouse-Go local-tier // default (qwen3.5:latest), not the qwen2.5-coder example file. // PROMPT.md was authored 2026-04-29; tier bump landed 2026-04-30. func DefaultModelProfile() ModelProfile { return ModelProfile{ Provider: "ollama", BaseURL: "http://localhost:11434", Model: "qwen3.5:latest", FallbackModel: "qwen3:latest", TimeoutSeconds: 120, Temperature: 0.1, } } // DefaultReviewProfile turns every static check ON by default. // review-profile.yaml in the target repo is how operators tune. func DefaultReviewProfile() ReviewProfile { p := ReviewProfile{ ProjectName: "review-harness", Mode: "local-first", } p.SeverityThresholds.FailOnCritical = true p.SeverityThresholds.FailOnHigh = false p.StaticChecks.HardcodedPaths = true p.StaticChecks.RawSQLInterpolation = true p.StaticChecks.ShellExecution = true p.StaticChecks.BroadCORS = true p.StaticChecks.SecretPatterns = true p.StaticChecks.LargeFiles = true p.StaticChecks.TODOComments = true p.StaticChecks.MissingTests = true p.Limits.LargeFileLines = 800 p.Limits.MaxFileBytes = 1_000_000 p.Limits.MaxLLMChunkChars = 12000 p.Reports.OutputDir = "reports/latest" p.Reports.Markdown = true p.Reports.JSONReceipts = true p.Memory.Enabled = true p.Memory.Path = ".memory" p.Memory.AppendOnly = true return p } // LoadModelProfile reads YAML from path. Empty path returns defaults // without error — operators don't need a profile to run. func LoadModelProfile(path string) (ModelProfile, error) { out := DefaultModelProfile() if path == "" { return out, nil } b, err := os.ReadFile(path) if err != nil { return out, fmt.Errorf("read model profile %s: %w", path, err) } if err := yaml.Unmarshal(b, &out); err != nil { return out, fmt.Errorf("parse model profile %s: %w", path, err) } return out, nil } // LoadReviewProfile reads YAML from path. Empty path returns defaults. // Partial files merge into defaults — unspecified fields stay at // default values (yaml.v3 preserves pre-existing values for fields // not present in the YAML). func LoadReviewProfile(path string) (ReviewProfile, error) { out := DefaultReviewProfile() if path == "" { return out, nil } b, err := os.ReadFile(path) if err != nil { return out, fmt.Errorf("read review profile %s: %w", path, err) } if err := yaml.Unmarshal(b, &out); err != nil { return out, fmt.Errorf("parse review profile %s: %w", path, err) } return out, nil }