diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-26 07:52:08 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-26 07:52:08 +0300 |
| commit | 2efcd2c4dda97831058851e8911281d5db5ce1c6 (patch) | |
| tree | c59059cb4c781878f1291ca06fe1a215579a0fa8 /docs/coverage.html | |
| parent | d0330d02ff040326216ab940a767490cb2de09ce (diff) | |
Log config reload changes
Diffstat (limited to 'docs/coverage.html')
| -rw-r--r-- | docs/coverage.html | 417 |
1 files changed, 211 insertions, 206 deletions
diff --git a/docs/coverage.html b/docs/coverage.html index 356f77a..686f831 100644 --- a/docs/coverage.html +++ b/docs/coverage.html @@ -99,7 +99,7 @@ <option value="file21">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option> - <option value="file22">codeberg.org/snonux/hexai/internal/lsp/chat_commands.go (80.0%)</option> + <option value="file22">codeberg.org/snonux/hexai/internal/lsp/chat_commands.go (72.2%)</option> <option value="file23">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option> @@ -123,7 +123,7 @@ <option value="file33">codeberg.org/snonux/hexai/internal/lsp/transport.go (73.0%)</option> - <option value="file34">codeberg.org/snonux/hexai/internal/runtimeconfig/store.go (85.5%)</option> + <option value="file34">codeberg.org/snonux/hexai/internal/runtimeconfig/store.go (87.1%)</option> <option value="file35">codeberg.org/snonux/hexai/internal/stats/lock_posix.go (83.3%)</option> @@ -353,7 +353,7 @@ type CustomAction struct { } // Constructor: defaults for App (kept first among functions) -func newDefaultConfig() App <span class="cov6" title="47">{ +func newDefaultConfig() App <span class="cov6" title="49">{ // Coding-friendly default temperature across providers // Users can override per provider in config.toml (including 0.0). t := 0.2 @@ -409,7 +409,7 @@ func newDefaultConfig() App <span class="cov6" title="47">{ // Load reads configuration from a file and merges with defaults. // It respects the XDG Base Directory Specification. -func Load(logger *log.Logger) App <span class="cov6" title="43">{ return LoadWithOptions(logger, LoadOptions{}) }</span> +func Load(logger *log.Logger) App <span class="cov6" title="44">{ return LoadWithOptions(logger, LoadOptions{}) }</span> // LoadOptions tune how configuration is loaded at runtime. type LoadOptions struct { @@ -418,31 +418,31 @@ type LoadOptions struct { } // LoadWithOptions reads configuration and applies the requested loading options. -func LoadWithOptions(logger *log.Logger, opts LoadOptions) App <span class="cov6" title="46">{ +func LoadWithOptions(logger *log.Logger, opts LoadOptions) App <span class="cov6" title="48">{ cfg := newDefaultConfig() if logger == nil </span><span class="cov4" title="13">{ return cfg // Return defaults if no logger is provided (e.g. in tests) }</span> - <span class="cov5" title="33">configPath, err := getConfigPath() + <span class="cov5" title="35">configPath, err := getConfigPath() if err != nil </span><span class="cov0" title="0">{ logger.Printf("%v", err) // Even if config path cannot be resolved, keep defaults and optionally apply env overrides below. - }</span> else<span class="cov5" title="33"> { - if fileCfg, err := loadFromFile(configPath, logger); err == nil && fileCfg != nil </span><span class="cov5" title="28">{ + }</span> else<span class="cov5" title="35"> { + if fileCfg, err := loadFromFile(configPath, logger); err == nil && fileCfg != nil </span><span class="cov5" title="30">{ cfg.mergeWith(fileCfg) }</span> // When the config file is missing or invalid, we keep defaults and still // apply any environment overrides below (unless disabled). } - <span class="cov5" title="33">if !opts.IgnoreEnv </span><span class="cov5" title="30">{ + <span class="cov5" title="35">if !opts.IgnoreEnv </span><span class="cov5" title="31">{ // Environment overrides (take precedence over file) - if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov3" title="7">{ + if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov3" title="8">{ cfg.mergeWith(envCfg) }</span> } - <span class="cov5" title="33">return cfg</span> + <span class="cov5" title="35">return cfg</span> } // Private helpers @@ -511,7 +511,7 @@ type sectionOpenAI struct { Presets map[string]string `toml:"presets"` } -func (s sectionOpenAI) isZero() bool <span class="cov5" title="28">{ +func (s sectionOpenAI) isZero() bool <span class="cov5" title="30">{ return strings.TrimSpace(s.Model) == "" && strings.TrimSpace(s.BaseURL) == "" && s.Temperature == nil && len(s.Presets) == 0 }</span> @@ -609,11 +609,11 @@ type sectionTmux struct { CustomMenuHotkey string `toml:"custom_menu_hotkey"` } -func (fc *fileConfig) toApp() App <span class="cov5" title="28">{ +func (fc *fileConfig) toApp() App <span class="cov5" title="30">{ out := App{} // Merge section: general - if (fc.General != sectionGeneral{}) || fc.General.CodingTemperature != nil </span><span class="cov3" title="9">{ + if (fc.General != sectionGeneral{}) || fc.General.CodingTemperature != nil </span><span class="cov4" title="11">{ tmp := App{ MaxTokens: fc.General.MaxTokens, ContextMode: fc.General.ContextMode, @@ -625,13 +625,13 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="28">{ }</span> // logging - <span class="cov5" title="28">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="30">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{ tmp := App{LogPreviewLimit: fc.Logging.LogPreviewLimit} out.mergeBasics(&tmp) }</span> // completion - <span class="cov5" title="28">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="3">{ + <span class="cov5" title="30">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="3">{ tmp := App{ CompletionDebounceMs: fc.Completion.CompletionDebounceMs, CompletionThrottleMs: fc.Completion.CompletionThrottleMs, @@ -641,31 +641,31 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="28">{ }</span> // triggers - <span class="cov5" title="28">if len(fc.Triggers.TriggerCharacters) > 0 </span><span class="cov2" title="3">{ + <span class="cov5" title="30">if len(fc.Triggers.TriggerCharacters) > 0 </span><span class="cov2" title="3">{ tmp := App{TriggerCharacters: fc.Triggers.TriggerCharacters} out.mergeBasics(&tmp) }</span> // inline - <span class="cov5" title="28">if (fc.Inline != sectionInline{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="30">if (fc.Inline != sectionInline{}) </span><span class="cov4" title="11">{ tmp := App{InlineOpen: fc.Inline.InlineOpen, InlineClose: fc.Inline.InlineClose} out.mergeBasics(&tmp) }</span> // chat - <span class="cov5" title="28">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) > 0 </span><span class="cov1" title="1">{ + <span class="cov5" title="30">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) > 0 </span><span class="cov1" title="1">{ tmp := App{ChatSuffix: fc.Chat.ChatSuffix, ChatPrefixes: fc.Chat.ChatPrefixes} out.mergeBasics(&tmp) }</span> // provider - <span class="cov5" title="28">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov2" title="4">{ + <span class="cov5" title="30">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov2" title="4">{ tmp := App{Provider: fc.Provider.Name} out.mergeBasics(&tmp) }</span> // openai - <span class="cov5" title="28">if !fc.OpenAI.isZero() || fc.OpenAI.Temperature != nil </span><span class="cov4" title="14">{ + <span class="cov5" title="30">if !fc.OpenAI.isZero() || fc.OpenAI.Temperature != nil </span><span class="cov4" title="14">{ tmp := App{ OpenAIBaseURL: fc.OpenAI.BaseURL, OpenAIModel: fc.OpenAI.resolvedModel(), @@ -675,7 +675,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="28">{ }</span> // copilot - <span class="cov5" title="28">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="3">{ + <span class="cov5" title="30">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="3">{ tmp := App{ CopilotBaseURL: fc.Copilot.BaseURL, CopilotModel: fc.Copilot.Model, @@ -685,7 +685,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="28">{ }</span> // ollama - <span class="cov5" title="28">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="3">{ + <span class="cov5" title="30">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="3">{ tmp := App{ OllamaBaseURL: fc.Ollama.BaseURL, OllamaModel: fc.Ollama.Model, @@ -696,7 +696,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="28">{ // prompts // completion - <span class="cov5" title="28">if (fc.Prompts.Completion != sectionPromptsCompletion{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="30">if (fc.Prompts.Completion != sectionPromptsCompletion{}) </span><span class="cov1" title="1">{ if strings.TrimSpace(fc.Prompts.Completion.SystemGeneral) != "" </span><span class="cov1" title="1">{ out.PromptCompletionSystemGeneral = fc.Prompts.Completion.SystemGeneral }</span> @@ -717,11 +717,11 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="28">{ }</span> } // chat - <span class="cov5" title="28">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="30">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{ out.PromptChatSystem = fc.Prompts.Chat.System }</span> // code action - <span class="cov5" title="28">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" || + <span class="cov5" title="30">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" || @@ -778,7 +778,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="28">{ } } // cli - <span class="cov5" title="28">if (fc.Prompts.CLI != sectionPromptsCLI{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="30">if (fc.Prompts.CLI != sectionPromptsCLI{}) </span><span class="cov1" title="1">{ if strings.TrimSpace(fc.Prompts.CLI.DefaultSystem) != "" </span><span class="cov1" title="1">{ out.PromptCLIDefaultSystem = fc.Prompts.CLI.DefaultSystem }</span> @@ -787,24 +787,24 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="28">{ }</span> } // provider-native - <span class="cov5" title="28">if strings.TrimSpace(fc.Prompts.ProviderNative.Completion) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="30">if strings.TrimSpace(fc.Prompts.ProviderNative.Completion) != "" </span><span class="cov1" title="1">{ out.PromptNativeCompletion = fc.Prompts.ProviderNative.Completion }</span> // tmux - <span class="cov5" title="28">if (fc.Tmux != sectionTmux{}) </span><span class="cov2" title="3">{ + <span class="cov5" title="30">if (fc.Tmux != sectionTmux{}) </span><span class="cov2" title="3">{ out.TmuxCustomMenuHotkey = strings.TrimSpace(fc.Tmux.CustomMenuHotkey) }</span> // stats - <span class="cov5" title="28">if fc.Stats.WindowMinutes > 0 </span><span class="cov0" title="0">{ + <span class="cov5" title="30">if fc.Stats.WindowMinutes > 0 </span><span class="cov0" title="0">{ out.StatsWindowMinutes = fc.Stats.WindowMinutes }</span> - <span class="cov5" title="28">return out</span> + <span class="cov5" title="30">return out</span> } -func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="cov5" title="34">{ +func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="cov5" title="36">{ b, err := os.ReadFile(path) if err != nil </span><span class="cov2" title="4">{ if !os.IsNotExist(err) && logger != nil </span><span class="cov0" title="0">{ @@ -813,7 +813,7 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co <span class="cov2" title="4">return nil, err</span> } - <span class="cov5" title="30">var tables fileConfig + <span class="cov5" title="32">var tables fileConfig errTables := toml.NewDecoder(strings.NewReader(string(b))).Decode(&tables) // Raw map for validation/presence checks var raw map[string]any @@ -826,7 +826,7 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co } // Reject legacy flat keys at top-level (sectioned-only config is allowed) - <span class="cov5" title="28">legacy := map[string]struct{}{ + <span class="cov5" title="30">legacy := map[string]struct{}{ "max_tokens": {}, "context_mode": {}, "context_window_lines": {}, "max_context_tokens": {}, "log_preview_limit": {}, "completion_debounce_ms": {}, "completion_throttle_ms": {}, "manual_invoke_min_prefix": {}, "trigger_characters": {}, "inline_open": {}, "inline_close": {}, @@ -835,8 +835,8 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co "ollama_model": {}, "ollama_base_url": {}, "ollama_temperature": {}, "copilot_model": {}, "copilot_base_url": {}, "copilot_temperature": {}, } - for k := range raw </span><span class="cov6" title="64">{ - if _, isTable := map[string]struct{}{"general": {}, "logging": {}, "completion": {}, "triggers": {}, "inline": {}, "chat": {}, "provider": {}, "openai": {}, "copilot": {}, "ollama": {}, "prompts": {}}[k]; isTable </span><span class="cov6" title="61">{ + for k := range raw </span><span class="cov6" title="76">{ + if _, isTable := map[string]struct{}{"general": {}, "logging": {}, "completion": {}, "triggers": {}, "inline": {}, "chat": {}, "provider": {}, "openai": {}, "copilot": {}, "ollama": {}, "prompts": {}}[k]; isTable </span><span class="cov6" title="73">{ continue</span> } <span class="cov2" title="3">if _, isLegacy := legacy[k]; isLegacy </span><span class="cov0" title="0">{ @@ -844,13 +844,13 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co }</span> } - <span class="cov5" title="28">if logger != nil </span><span class="cov5" title="28">{ + <span class="cov5" title="30">if logger != nil </span><span class="cov5" title="30">{ logger.Printf("loaded configuration from %s (TOML)", path) }</span> // Merge order: flat first, then tables (so tables win over zero flat values) // Build App from tables only - <span class="cov5" title="28">tab := tables.toApp() + <span class="cov5" title="30">tab := tables.toApp() // Ensure explicit values from raw map are respected (defensive for ints) if t, ok := raw["completion"].(map[string]any); ok </span><span class="cov2" title="3">{ if v, present := t["manual_invoke_min_prefix"]; present </span><span class="cov2" title="3">{ @@ -864,7 +864,7 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co } } } - <span class="cov5" title="28">if t, ok := raw["logging"].(map[string]any); ok </span><span class="cov2" title="3">{ + <span class="cov5" title="30">if t, ok := raw["logging"].(map[string]any); ok </span><span class="cov2" title="3">{ if v, present := t["log_preview_limit"]; present </span><span class="cov2" title="3">{ switch vv := v.(type) </span>{ case int64:<span class="cov2" title="3"> @@ -876,142 +876,142 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co } } } - <span class="cov5" title="28">return &tab, nil</span> + <span class="cov5" title="30">return &tab, nil</span> } -func (a *App) mergeWith(other *App) <span class="cov5" title="35">{ +func (a *App) mergeWith(other *App) <span class="cov5" title="38">{ a.mergeBasics(other) a.mergeProviderFields(other) a.mergePrompts(other) }</span> // mergeBasics merges general (non-provider) fields. -func (a *App) mergeBasics(other *App) <span class="cov6" title="57">{ - if other.MaxTokens > 0 </span><span class="cov5" title="21">{ +func (a *App) mergeBasics(other *App) <span class="cov6" title="72">{ + if other.MaxTokens > 0 </span><span class="cov5" title="26">{ a.MaxTokens = other.MaxTokens }</span> - <span class="cov6" title="57">if s := strings.TrimSpace(other.ContextMode); s != "" </span><span class="cov3" title="7">{ + <span class="cov6" title="72">if s := strings.TrimSpace(other.ContextMode); s != "" </span><span class="cov3" title="7">{ a.ContextMode = s }</span> - <span class="cov6" title="57">if other.ContextWindowLines > 0 </span><span class="cov3" title="7">{ + <span class="cov6" title="72">if other.ContextWindowLines > 0 </span><span class="cov3" title="7">{ a.ContextWindowLines = other.ContextWindowLines }</span> - <span class="cov6" title="57">if other.MaxContextTokens > 0 </span><span class="cov3" title="7">{ + <span class="cov6" title="72">if other.MaxContextTokens > 0 </span><span class="cov3" title="7">{ a.MaxContextTokens = other.MaxContextTokens }</span> - <span class="cov6" title="57">if other.LogPreviewLimit >= 0 </span><span class="cov6" title="57">{ + <span class="cov6" title="72">if other.LogPreviewLimit >= 0 </span><span class="cov6" title="72">{ a.LogPreviewLimit = other.LogPreviewLimit }</span> - <span class="cov6" title="57">if other.CodingTemperature != nil </span><span class="cov3" title="7">{ // allow explicit 0.0 + <span class="cov6" title="72">if other.CodingTemperature != nil </span><span class="cov3" title="7">{ // allow explicit 0.0 a.CodingTemperature = other.CodingTemperature }</span> - <span class="cov6" title="57">if other.ManualInvokeMinPrefix >= 0 </span><span class="cov6" title="57">{ + <span class="cov6" title="72">if other.ManualInvokeMinPrefix >= 0 </span><span class="cov6" title="72">{ a.ManualInvokeMinPrefix = other.ManualInvokeMinPrefix }</span> - <span class="cov6" title="57">if other.CompletionDebounceMs > 0 </span><span class="cov3" title="7">{ + <span class="cov6" title="72">if other.CompletionDebounceMs > 0 </span><span class="cov3" title="7">{ a.CompletionDebounceMs = other.CompletionDebounceMs }</span> - <span class="cov6" title="57">if other.CompletionThrottleMs > 0 </span><span class="cov3" title="7">{ + <span class="cov6" title="72">if other.CompletionThrottleMs > 0 </span><span class="cov3" title="7">{ a.CompletionThrottleMs = other.CompletionThrottleMs }</span> - <span class="cov6" title="57">if len(other.TriggerCharacters) > 0 </span><span class="cov3" title="7">{ + <span class="cov6" title="72">if len(other.TriggerCharacters) > 0 </span><span class="cov3" title="7">{ a.TriggerCharacters = slices.Clone(other.TriggerCharacters) }</span> - <span class="cov6" title="57">if s := strings.TrimSpace(other.InlineOpen); s != "" </span><span class="cov1" title="2">{ + <span class="cov6" title="72">if s := strings.TrimSpace(other.InlineOpen); s != "" </span><span class="cov5" title="22">{ a.InlineOpen = s }</span> - <span class="cov6" title="57">if s := strings.TrimSpace(other.InlineClose); s != "" </span><span class="cov1" title="2">{ + <span class="cov6" title="72">if s := strings.TrimSpace(other.InlineClose); s != "" </span><span class="cov5" title="22">{ a.InlineClose = s }</span> - <span class="cov6" title="57">if s := strings.TrimSpace(other.ChatSuffix); s != "" </span><span class="cov1" title="2">{ + <span class="cov6" title="72">if s := strings.TrimSpace(other.ChatSuffix); s != "" </span><span class="cov1" title="2">{ a.ChatSuffix = s }</span> - <span class="cov6" title="57">if len(other.ChatPrefixes) > 0 </span><span class="cov1" title="2">{ + <span class="cov6" title="72">if len(other.ChatPrefixes) > 0 </span><span class="cov1" title="2">{ a.ChatPrefixes = slices.Clone(other.ChatPrefixes) }</span> - <span class="cov6" title="57">if s := strings.TrimSpace(other.Provider); s != "" </span><span class="cov4" title="13">{ + <span class="cov6" title="72">if s := strings.TrimSpace(other.Provider); s != "" </span><span class="cov4" title="13">{ a.Provider = s }</span> } // mergePrompts copies non-empty prompt templates from other. -func (a *App) mergePrompts(other *App) <span class="cov5" title="35">{ +func (a *App) mergePrompts(other *App) <span class="cov5" title="38">{ // Completion if strings.TrimSpace(other.PromptCompletionSystemGeneral) != "" </span><span class="cov1" title="1">{ a.PromptCompletionSystemGeneral = other.PromptCompletionSystemGeneral }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCompletionSystemParams) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCompletionSystemParams) != "" </span><span class="cov1" title="1">{ a.PromptCompletionSystemParams = other.PromptCompletionSystemParams }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCompletionSystemInline) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCompletionSystemInline) != "" </span><span class="cov1" title="1">{ a.PromptCompletionSystemInline = other.PromptCompletionSystemInline }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCompletionUserGeneral) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCompletionUserGeneral) != "" </span><span class="cov1" title="1">{ a.PromptCompletionUserGeneral = other.PromptCompletionUserGeneral }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCompletionUserParams) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCompletionUserParams) != "" </span><span class="cov1" title="1">{ a.PromptCompletionUserParams = other.PromptCompletionUserParams }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCompletionExtraHeader) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCompletionExtraHeader) != "" </span><span class="cov1" title="1">{ a.PromptCompletionExtraHeader = other.PromptCompletionExtraHeader }</span> // Provider-native - <span class="cov5" title="35">if strings.TrimSpace(other.PromptNativeCompletion) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptNativeCompletion) != "" </span><span class="cov1" title="1">{ a.PromptNativeCompletion = other.PromptNativeCompletion }</span> // Chat - <span class="cov5" title="35">if strings.TrimSpace(other.PromptChatSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptChatSystem) != "" </span><span class="cov1" title="1">{ a.PromptChatSystem = other.PromptChatSystem }</span> // Code actions - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionRewriteSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionRewriteSystem) != "" </span><span class="cov1" title="1">{ a.PromptCodeActionRewriteSystem = other.PromptCodeActionRewriteSystem }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionDiagnosticsSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionDiagnosticsSystem) != "" </span><span class="cov1" title="1">{ a.PromptCodeActionDiagnosticsSystem = other.PromptCodeActionDiagnosticsSystem }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionDocumentSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionDocumentSystem) != "" </span><span class="cov1" title="1">{ a.PromptCodeActionDocumentSystem = other.PromptCodeActionDocumentSystem }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionRewriteUser) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionRewriteUser) != "" </span><span class="cov1" title="1">{ a.PromptCodeActionRewriteUser = other.PromptCodeActionRewriteUser }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionDiagnosticsUser) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionDiagnosticsUser) != "" </span><span class="cov1" title="1">{ a.PromptCodeActionDiagnosticsUser = other.PromptCodeActionDiagnosticsUser }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionDocumentUser) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionDocumentUser) != "" </span><span class="cov1" title="1">{ a.PromptCodeActionDocumentUser = other.PromptCodeActionDocumentUser }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionGoTestSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionGoTestSystem) != "" </span><span class="cov1" title="1">{ a.PromptCodeActionGoTestSystem = other.PromptCodeActionGoTestSystem }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionGoTestUser) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionGoTestUser) != "" </span><span class="cov1" title="1">{ a.PromptCodeActionGoTestUser = other.PromptCodeActionGoTestUser }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionSimplifySystem) != "" </span><span class="cov0" title="0">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionSimplifySystem) != "" </span><span class="cov0" title="0">{ a.PromptCodeActionSimplifySystem = other.PromptCodeActionSimplifySystem }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCodeActionSimplifyUser) != "" </span><span class="cov0" title="0">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCodeActionSimplifyUser) != "" </span><span class="cov0" title="0">{ a.PromptCodeActionSimplifyUser = other.PromptCodeActionSimplifyUser }</span> // CLI - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCLIDefaultSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCLIDefaultSystem) != "" </span><span class="cov1" title="1">{ a.PromptCLIDefaultSystem = other.PromptCLIDefaultSystem }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.PromptCLIExplainSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="38">if strings.TrimSpace(other.PromptCLIExplainSystem) != "" </span><span class="cov1" title="1">{ a.PromptCLIExplainSystem = other.PromptCLIExplainSystem }</span> // Custom actions - <span class="cov5" title="35">if len(other.CustomActions) > 0 </span><span class="cov4" title="16">{ + <span class="cov5" title="38">if len(other.CustomActions) > 0 </span><span class="cov4" title="16">{ a.CustomActions = append([]CustomAction{}, other.CustomActions...) }</span> - <span class="cov5" title="35">if strings.TrimSpace(other.TmuxCustomMenuHotkey) != "" </span><span class="cov2" title="3">{ + <span class="cov5" title="38">if strings.TrimSpace(other.TmuxCustomMenuHotkey) != "" </span><span class="cov2" title="3">{ a.TmuxCustomMenuHotkey = other.TmuxCustomMenuHotkey }</span> } // Validate checks custom actions and tmux settings for duplicates and consistency. -func (a App) Validate() error <span class="cov5" title="23">{ +func (a App) Validate() error <span class="cov5" title="24">{ // Normalize and check duplicates for IDs and hotkeys seenID := make(map[string]struct{}) seenHK := make(map[string]struct{}) @@ -1054,7 +1054,7 @@ func (a App) Validate() error <span class="cov5" title="23">{ } } // Tmux custom menu hotkey validation - <span class="cov4" title="18">if hk := strings.TrimSpace(a.TmuxCustomMenuHotkey); hk != "" </span><span class="cov1" title="2">{ + <span class="cov4" title="19">if hk := strings.TrimSpace(a.TmuxCustomMenuHotkey); hk != "" </span><span class="cov1" title="2">{ if len([]rune(hk)) != 1 </span><span class="cov0" title="0">{ return fmt.Errorf("config: invalid tmux.custom_menu_hotkey: %s", hk) }</span> @@ -1064,43 +1064,43 @@ func (a App) Validate() error <span class="cov5" title="23">{ return fmt.Errorf("config: invalid tmux.custom_menu_hotkey: %s (clashes with built-in)", hk)</span> } } - <span class="cov4" title="17">return nil</span> + <span class="cov4" title="18">return nil</span> } // mergeProviderFields merges per-provider configuration. -func (a *App) mergeProviderFields(other *App) <span class="cov6" title="55">{ +func (a *App) mergeProviderFields(other *App) <span class="cov6" title="58">{ if s := strings.TrimSpace(other.OpenAIBaseURL); s != "" </span><span class="cov5" title="27">{ a.OpenAIBaseURL = s }</span> - <span class="cov6" title="55">if s := strings.TrimSpace(other.OpenAIModel); s != "" </span><span class="cov5" title="33">{ + <span class="cov6" title="58">if s := strings.TrimSpace(other.OpenAIModel); s != "" </span><span class="cov5" title="33">{ a.OpenAIModel = s }</span> - <span class="cov6" title="55">if other.OpenAITemperature != nil </span><span class="cov5" title="27">{ // allow explicit 0.0 + <span class="cov6" title="58">if other.OpenAITemperature != nil </span><span class="cov5" title="27">{ // allow explicit 0.0 a.OpenAITemperature = other.OpenAITemperature }</span> - <span class="cov6" title="55">if s := strings.TrimSpace(other.OllamaBaseURL); s != "" </span><span class="cov3" title="7">{ + <span class="cov6" title="58">if s := strings.TrimSpace(other.OllamaBaseURL); s != "" </span><span class="cov3" title="7">{ a.OllamaBaseURL = s }</span> - <span class="cov6" title="55">if s := strings.TrimSpace(other.OllamaModel); s != "" </span><span class="cov3" title="7">{ + <span class="cov6" title="58">if s := strings.TrimSpace(other.OllamaModel); s != "" </span><span class="cov3" title="7">{ a.OllamaModel = s }</span> - <span class="cov6" title="55">if other.OllamaTemperature != nil </span><span class="cov3" title="7">{ // allow explicit 0.0 + <span class="cov6" title="58">if other.OllamaTemperature != nil </span><span class="cov3" title="7">{ // allow explicit 0.0 a.OllamaTemperature = other.OllamaTemperature }</span> - <span class="cov6" title="55">if s := strings.TrimSpace(other.CopilotBaseURL); s != "" </span><span class="cov3" title="7">{ + <span class="cov6" title="58">if s := strings.TrimSpace(other.CopilotBaseURL); s != "" </span><span class="cov3" title="7">{ a.CopilotBaseURL = s }</span> - <span class="cov6" title="55">if s := strings.TrimSpace(other.CopilotModel); s != "" </span><span class="cov3" title="7">{ + <span class="cov6" title="58">if s := strings.TrimSpace(other.CopilotModel); s != "" </span><span class="cov3" title="7">{ a.CopilotModel = s }</span> - <span class="cov6" title="55">if other.CopilotTemperature != nil </span><span class="cov3" title="7">{ // allow explicit 0.0 + <span class="cov6" title="58">if other.CopilotTemperature != nil </span><span class="cov3" title="7">{ // allow explicit 0.0 a.CopilotTemperature = other.CopilotTemperature }</span> } -func getConfigPath() (string, error) <span class="cov5" title="34">{ +func getConfigPath() (string, error) <span class="cov5" title="36">{ var configPath string - if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" </span><span class="cov5" title="24">{ + if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" </span><span class="cov5" title="26">{ configPath = filepath.Join(xdgConfigHome, "hexai", "config.toml") }</span> else<span class="cov4" title="10"> { home, err := os.UserHomeDir() @@ -1109,36 +1109,36 @@ func getConfigPath() (string, error) <span class="cov5" title="34">{ }</span> <span class="cov4" title="10">configPath = filepath.Join(home, ".config", "hexai", "config.toml")</span> } - <span class="cov5" title="34">return configPath, nil</span> + <span class="cov5" title="36">return configPath, nil</span> } // --- Environment overrides --- // loadFromEnv constructs an App containing only fields set via HEXAI_* env vars. // These values should take precedence over file config when merged. -func loadFromEnv(logger *log.Logger) *App <span class="cov5" title="30">{ +func loadFromEnv(logger *log.Logger) *App <span class="cov5" title="31">{ var out App var any bool // helpers - getenv := func(k string) string </span><span class="cov10" title="780">{ return strings.TrimSpace(os.Getenv(k)) }</span> - <span class="cov5" title="30">parseInt := func(k string) (int, bool) </span><span class="cov8" title="210">{ + getenv := func(k string) string </span><span class="cov10" title="806">{ return strings.TrimSpace(os.Getenv(k)) }</span> + <span class="cov5" title="31">parseInt := func(k string) (int, bool) </span><span class="cov8" title="217">{ v := getenv(k) - if v == "" </span><span class="cov8" title="201">{ + if v == "" </span><span class="cov8" title="207">{ return 0, false }</span> - <span class="cov3" title="9">n, err := strconv.Atoi(v) + <span class="cov4" title="10">n, err := strconv.Atoi(v) if err != nil </span><span class="cov0" title="0">{ if logger != nil </span><span class="cov0" title="0">{ logger.Printf("invalid %s: %v", k, err) }</span> <span class="cov0" title="0">return 0, false</span> } - <span class="cov3" title="9">return n, true</span> + <span class="cov4" title="10">return n, true</span> } - <span class="cov5" title="30">parseFloatPtr := func(k string) (*float64, bool) </span><span class="cov7" title="120">{ + <span class="cov5" title="31">parseFloatPtr := func(k string) (*float64, bool) </span><span class="cov7" title="124">{ v := getenv(k) - if v == "" </span><span class="cov7" title="116">{ + if v == "" </span><span class="cov7" title="120">{ return nil, false }</span> <span class="cov2" title="4">f, err := strconv.ParseFloat(v, 64) @@ -1151,43 +1151,43 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov5" title="30">{ <span class="cov2" title="4">return &f, true</span> } - <span class="cov5" title="30">if n, ok := parseInt("HEXAI_MAX_TOKENS"); ok </span><span class="cov2" title="3">{ + <span class="cov5" title="31">if n, ok := parseInt("HEXAI_MAX_TOKENS"); ok </span><span class="cov2" title="4">{ out.MaxTokens = n any = true }</span> - <span class="cov5" title="30">if s := getenv("HEXAI_CONTEXT_MODE"); s != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="31">if s := getenv("HEXAI_CONTEXT_MODE"); s != "" </span><span class="cov1" title="1">{ out.ContextMode = s any = true }</span> - <span class="cov5" title="30">if n, ok := parseInt("HEXAI_CONTEXT_WINDOW_LINES"); ok </span><span class="cov1" title="1">{ + <span class="cov5" title="31">if n, ok := parseInt("HEXAI_CONTEXT_WINDOW_LINES"); ok </span><span class="cov1" title="1">{ out.ContextWindowLines = n any = true }</span> - <span class="cov5" title="30">if n, ok := parseInt("HEXAI_MAX_CONTEXT_TOKENS"); ok </span><span class="cov1" title="1">{ + <span class="cov5" title="31">if n, ok := parseInt("HEXAI_MAX_CONTEXT_TOKENS"); ok </span><span class="cov1" title="1">{ out.MaxContextTokens = n any = true }</span> - <span class="cov5" title="30">if n, ok := parseInt("HEXAI_LOG_PREVIEW_LIMIT"); ok </span><span class="cov1" title="1">{ + <span class="cov5" title="31">if n, ok := parseInt("HEXAI_LOG_PREVIEW_LIMIT"); ok </span><span class="cov1" title="1">{ out.LogPreviewLimit = n any = true }</span> - <span class="cov5" title="30">if n, ok := parseInt("HEXAI_MANUAL_INVOKE_MIN_PREFIX"); ok </span><span class="cov1" title="1">{ + <span class="cov5" title="31">if n, ok := parseInt("HEXAI_MANUAL_INVOKE_MIN_PREFIX"); ok </span& |
