summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-17 21:33:45 +0300
committerPaul Buetow <paul@buetow.org>2025-09-17 21:33:45 +0300
commit88103657fb230bb41217a06aa5602ae23e7acb8b (patch)
tree524c437e4e40ee5d6713b6ea5414ad975654cc52 /docs
parent2b6232704ecc90630196b9f829f966533e5cdccd (diff)
feat(stats,tmux): global Σ@window stats across processes with flocked cache; width mitigation (narrow/maxlen); configurable [stats] window_minutes; robust coverage parsing; docs update\n\n- Add internal/stats with windowed event cache + flock + atomic writes\n- Wire stats into LSP/CLI/Tmux Action; tmux shows Σ@window with per-model tail\n- HEXAI_TMUX_STATUS_NARROW and HEXAI_TMUX_STATUS_MAXLEN for width control\n- Add [stats] window_minutes to config and apply on startup\n- Improve Magefile coverage handling; add tests to lift coverage >85%\n- Update docs/tmux.md and config example
Diffstat (limited to 'docs')
-rw-r--r--docs/coverage.html1215
-rw-r--r--docs/coverage.out29536
-rw-r--r--docs/tmux.md20
3 files changed, 17704 insertions, 13067 deletions
diff --git a/docs/coverage.html b/docs/coverage.html
index 2d72d59..4c7532e 100644
--- a/docs/coverage.html
+++ b/docs/coverage.html
@@ -61,7 +61,7 @@
<option value="file2">codeberg.org/snonux/hexai/cmd/hexai/main.go (71.4%)</option>
- <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (90.8%)</option>
+ <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (90.6%)</option>
<option value="file4">codeberg.org/snonux/hexai/internal/editor/editor.go (58.3%)</option>
@@ -69,9 +69,9 @@
<option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option>
- <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (87.5%)</option>
+ <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (92.7%)</option>
- <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (67.2%)</option>
+ <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (69.7%)</option>
<option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option>
@@ -79,9 +79,9 @@
<option value="file11">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option>
- <option value="file12">codeberg.org/snonux/hexai/internal/hexaicli/run.go (88.6%)</option>
+ <option value="file12">codeberg.org/snonux/hexai/internal/hexaicli/run.go (89.7%)</option>
- <option value="file13">codeberg.org/snonux/hexai/internal/hexailsp/run.go (83.7%)</option>
+ <option value="file13">codeberg.org/snonux/hexai/internal/hexailsp/run.go (90.2%)</option>
<option value="file14">codeberg.org/snonux/hexai/internal/llm/copilot.go (82.4%)</option>
@@ -99,37 +99,39 @@
<option value="file21">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option>
- <option value="file22">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option>
+ <option value="file22">codeberg.org/snonux/hexai/internal/lsp/context.go (76.9%)</option>
- <option value="file23">codeberg.org/snonux/hexai/internal/lsp/document.go (90.1%)</option>
+ <option value="file23">codeberg.org/snonux/hexai/internal/lsp/document.go (91.5%)</option>
<option value="file24">codeberg.org/snonux/hexai/internal/lsp/handlers.go (92.9%)</option>
<option value="file25">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (82.3%)</option>
- <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (87.9%)</option>
+ <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (88.0%)</option>
- <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (88.9%)</option>
+ <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (90.1%)</option>
<option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option>
<option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (63.6%)</option>
- <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.4%)</option>
+ <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.5%)</option>
- <option value="file31">codeberg.org/snonux/hexai/internal/lsp/server.go (81.8%)</option>
+ <option value="file31">codeberg.org/snonux/hexai/internal/lsp/server.go (83.0%)</option>
<option value="file32">codeberg.org/snonux/hexai/internal/lsp/transport.go (71.4%)</option>
- <option value="file33">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option>
+ <option value="file33">codeberg.org/snonux/hexai/internal/stats/stats.go (75.4%)</option>
- <option value="file34">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option>
+ <option value="file34">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option>
- <option value="file35">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option>
+ <option value="file35">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option>
- <option value="file36">codeberg.org/snonux/hexai/internal/tmux/status.go (68.5%)</option>
+ <option value="file36">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option>
- <option value="file37">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
+ <option value="file37">codeberg.org/snonux/hexai/internal/tmux/status.go (73.8%)</option>
+
+ <option value="file38">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
</select>
</div>
@@ -328,6 +330,8 @@ type App struct {
// Custom code actions and tmux integration
CustomActions []CustomAction `json:"-" toml:"-"`
TmuxCustomMenuHotkey string `json:"-" toml:"-"`
+ // Stats
+ StatsWindowMinutes int `json:"-" toml:"-"`
}
// CustomAction describes a user-defined code action.
@@ -343,7 +347,7 @@ type CustomAction struct {
}
// Constructor: defaults for App (kept first among functions)
-func newDefaultConfig() App <span class="cov5" title="30">{
+func newDefaultConfig() App <span class="cov5" title="31">{
// Coding-friendly default temperature across providers
// Users can override per provider in config.toml (including 0.0).
t := 0.2
@@ -358,7 +362,7 @@ func newDefaultConfig() App <span class="cov5" title="30">{
OllamaTemperature: &amp;t,
CopilotTemperature: &amp;t,
ManualInvokeMinPrefix: 0,
- CompletionDebounceMs: 200,
+ CompletionDebounceMs: 800,
CompletionThrottleMs: 0,
// Inline/chat trigger defaults
InlineOpen: "&gt;",
@@ -391,14 +395,17 @@ func newDefaultConfig() App <span class="cov5" title="30">{
PromptCLIDefaultSystem: "You are Hexai CLI. Default to very short, concise answers. If the user asks for commands, output only the commands (one per line) with no commentary or explanation. Only when the word 'explain' appears in the prompt, produce a verbose explanation.",
PromptCLIExplainSystem: "You are Hexai CLI. The user requested an explanation. Provide a clear, verbose explanation with reasoning and details. If commands are needed, include them with brief context.",
+
+ // Stats
+ StatsWindowMinutes: 60,
}
}</span>
// 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="cov5" title="29">{
+func Load(logger *log.Logger) App <span class="cov5" title="30">{
cfg := newDefaultConfig()
- if logger == nil </span><span class="cov4" title="8">{
+ if logger == nil </span><span class="cov4" title="9">{
return cfg // Return defaults if no logger is provided (e.g. in tests)
}</span>
@@ -407,7 +414,7 @@ func Load(logger *log.Logger) App <span class="cov5" title="29">{
logger.Printf("%v", err)
// Even if config path cannot be resolved, still allow env overrides below.
}</span> else<span class="cov5" title="21"> {
- if fileCfg, err := loadFromFile(configPath, logger); err == nil &amp;&amp; fileCfg != nil </span><span class="cov4" title="11">{
+ if fileCfg, err := loadFromFile(configPath, logger); err == nil &amp;&amp; fileCfg != nil </span><span class="cov5" title="16">{
cfg.mergeWith(fileCfg)
}</span>
// When the config file is missing or invalid, we keep defaults and still
@@ -415,7 +422,7 @@ func Load(logger *log.Logger) App <span class="cov5" title="29">{
}
// Environment overrides (take precedence over file)
- <span class="cov5" title="21">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov1" title="1">{
+ <span class="cov5" title="21">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov4" title="12">{
cfg.mergeWith(envCfg)
}</span>
<span class="cov5" title="21">return cfg</span>
@@ -437,6 +444,7 @@ type fileConfig struct {
Ollama sectionOllama `toml:"ollama"`
Prompts sectionPrompts `toml:"prompts"`
Tmux sectionTmux `toml:"tmux"`
+ Stats sectionStats `toml:"stats"`
}
type sectionGeneral struct {
@@ -475,6 +483,10 @@ type sectionProvider struct {
Name string `toml:"name"`
}
+type sectionStats struct {
+ WindowMinutes int `toml:"window_minutes"`
+}
+
type sectionOpenAI struct {
Model string `toml:"model"`
BaseURL string `toml:"base_url"`
@@ -553,7 +565,7 @@ type sectionTmux struct {
CustomMenuHotkey string `toml:"custom_menu_hotkey"`
}
-func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
+func (fc *fileConfig) toApp() App <span class="cov5" title="16">{
out := App{}
// Merge section: general
@@ -569,13 +581,13 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
}</span>
// logging
- <span class="cov4" title="11">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{
+ <span class="cov5" title="16">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{
tmp := App{LogPreviewLimit: fc.Logging.LogPreviewLimit}
out.mergeBasics(&amp;tmp)
}</span>
// completion
- <span class="cov4" title="11">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="3">{
+ <span class="cov5" title="16">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="3">{
tmp := App{
CompletionDebounceMs: fc.Completion.CompletionDebounceMs,
CompletionThrottleMs: fc.Completion.CompletionThrottleMs,
@@ -585,31 +597,31 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
}</span>
// triggers
- <span class="cov4" title="11">if len(fc.Triggers.TriggerCharacters) &gt; 0 </span><span class="cov2" title="3">{
+ <span class="cov5" title="16">if len(fc.Triggers.TriggerCharacters) &gt; 0 </span><span class="cov2" title="3">{
tmp := App{TriggerCharacters: fc.Triggers.TriggerCharacters}
out.mergeBasics(&amp;tmp)
}</span>
// inline
- <span class="cov4" title="11">if (fc.Inline != sectionInline{}) </span><span class="cov1" title="1">{
+ <span class="cov5" title="16">if (fc.Inline != sectionInline{}) </span><span class="cov1" title="1">{
tmp := App{InlineOpen: fc.Inline.InlineOpen, InlineClose: fc.Inline.InlineClose}
out.mergeBasics(&amp;tmp)
}</span>
// chat
- <span class="cov4" title="11">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) &gt; 0 </span><span class="cov1" title="1">{
+ <span class="cov5" title="16">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) &gt; 0 </span><span class="cov1" title="1">{
tmp := App{ChatSuffix: fc.Chat.ChatSuffix, ChatPrefixes: fc.Chat.ChatPrefixes}
out.mergeBasics(&amp;tmp)
}</span>
// provider
- <span class="cov4" title="11">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov2" title="3">{
+ <span class="cov5" title="16">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov2" title="3">{
tmp := App{Provider: fc.Provider.Name}
out.mergeBasics(&amp;tmp)
}</span>
// openai
- <span class="cov4" title="11">if (fc.OpenAI != sectionOpenAI{}) || fc.OpenAI.Temperature != nil </span><span class="cov2" title="3">{
+ <span class="cov5" title="16">if (fc.OpenAI != sectionOpenAI{}) || fc.OpenAI.Temperature != nil </span><span class="cov2" title="3">{
tmp := App{
OpenAIBaseURL: fc.OpenAI.BaseURL,
OpenAIModel: fc.OpenAI.Model,
@@ -619,7 +631,7 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
}</span>
// copilot
- <span class="cov4" title="11">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="3">{
+ <span class="cov5" title="16">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="3">{
tmp := App{
CopilotBaseURL: fc.Copilot.BaseURL,
CopilotModel: fc.Copilot.Model,
@@ -629,7 +641,7 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
}</span>
// ollama
- <span class="cov4" title="11">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="3">{
+ <span class="cov5" title="16">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="3">{
tmp := App{
OllamaBaseURL: fc.Ollama.BaseURL,
OllamaModel: fc.Ollama.Model,
@@ -640,7 +652,7 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
// prompts
// completion
- <span class="cov4" title="11">if (fc.Prompts.Completion != sectionPromptsCompletion{}) </span><span class="cov1" title="1">{
+ <span class="cov5" title="16">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>
@@ -661,11 +673,11 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
}</span>
}
// chat
- <span class="cov4" title="11">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="16">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{
out.PromptChatSystem = fc.Prompts.Chat.System
}</span>
// code action
- <span class="cov4" title="11">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" ||
+ <span class="cov5" title="16">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) != "" ||
@@ -675,39 +687,39 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" ||
strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" ||
strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" ||
- len(fc.Prompts.CodeAction.Custom) &gt; 0 </span><span class="cov3" title="7">{
+ len(fc.Prompts.CodeAction.Custom) &gt; 0 </span><span class="cov4" title="12">{
if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionRewriteSystem = fc.Prompts.CodeAction.RewriteSystem
}</span>
- <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionDiagnosticsSystem = fc.Prompts.CodeAction.DiagnosticsSystem
}</span>
- <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionDocumentSystem = fc.Prompts.CodeAction.DocumentSystem
}</span>
- <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionRewriteUser = fc.Prompts.CodeAction.RewriteUser
}</span>
- <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsUser) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionDiagnosticsUser = fc.Prompts.CodeAction.DiagnosticsUser
}</span>
- <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentUser) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionDocumentUser = fc.Prompts.CodeAction.DocumentUser
}</span>
- <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionGoTestSystem = fc.Prompts.CodeAction.GoTestSystem
}</span>
- <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionGoTestUser = fc.Prompts.CodeAction.GoTestUser
}</span>
- <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" </span><span class="cov0" title="0">{
+ <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" </span><span class="cov0" title="0">{
out.PromptCodeActionSimplifySystem = fc.Prompts.CodeAction.SimplifySystem
}</span>
- <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" </span><span class="cov0" title="0">{
+ <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" </span><span class="cov0" title="0">{
out.PromptCodeActionSimplifyUser = fc.Prompts.CodeAction.SimplifyUser
}</span>
- <span class="cov3" title="7">if len(fc.Prompts.CodeAction.Custom) &gt; 0 </span><span class="cov3" title="6">{
- for _, ca := range fc.Prompts.CodeAction.Custom </span><span class="cov4" title="10">{
+ <span class="cov4" title="12">if len(fc.Prompts.CodeAction.Custom) &gt; 0 </span><span class="cov4" title="11">{
+ for _, ca := range fc.Prompts.CodeAction.Custom </span><span class="cov5" title="20">{
out.CustomActions = append(out.CustomActions, CustomAction{
ID: strings.TrimSpace(ca.ID),
Title: strings.TrimSpace(ca.Title),
@@ -722,7 +734,7 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
}
}
// cli
- <span class="cov4" title="11">if (fc.Prompts.CLI != sectionPromptsCLI{}) </span><span class="cov1" title="1">{
+ <span class="cov5" title="16">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>
@@ -731,28 +743,33 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{
}</span>
}
// provider-native
- <span class="cov4" title="11">if strings.TrimSpace(fc.Prompts.ProviderNative.Completion) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="16">if strings.TrimSpace(fc.Prompts.ProviderNative.Completion) != "" </span><span class="cov1" title="1">{
out.PromptNativeCompletion = fc.Prompts.ProviderNative.Completion
}</span>
// tmux
- <span class="cov4" title="11">if (fc.Tmux != sectionTmux{}) </span><span class="cov2" title="3">{
+ <span class="cov5" title="16">if (fc.Tmux != sectionTmux{}) </span><span class="cov2" title="3">{
out.TmuxCustomMenuHotkey = strings.TrimSpace(fc.Tmux.CustomMenuHotkey)
}</span>
- <span class="cov4" title="11">return out</span>
+ // stats
+ <span class="cov5" title="16">if fc.Stats.WindowMinutes &gt; 0 </span><span class="cov0" title="0">{
+ out.StatsWindowMinutes = fc.Stats.WindowMinutes
+ }</span>
+
+ <span class="cov5" title="16">return out</span>
}
func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="cov5" title="22">{
b, err := os.ReadFile(path)
- if err != nil </span><span class="cov4" title="9">{
+ if err != nil </span><span class="cov3" title="4">{
if !os.IsNotExist(err) &amp;&amp; logger != nil </span><span class="cov0" title="0">{
logger.Printf("cannot open TOML config file %s: %v", path, err)
}</span>
- <span class="cov4" title="9">return nil, err</span>
+ <span class="cov3" title="4">return nil, err</span>
}
- <span class="cov4" title="13">var tables fileConfig
+ <span class="cov5" title="18">var tables fileConfig
errTables := toml.NewDecoder(strings.NewReader(string(b))).Decode(&amp;tables)
// Raw map for validation/presence checks
var raw map[string]any
@@ -765,7 +782,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="cov4" title="11">legacy := map[string]struct{}{
+ <span class="cov5" title="16">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": {},
@@ -774,8 +791,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="36">{
- if _, isTable := map[string]struct{}{"general": {}, "logging": {}, "completion": {}, "triggers": {}, "inline": {}, "chat": {}, "provider": {}, "openai": {}, "copilot": {}, "ollama": {}, "prompts": {}}[k]; isTable </span><span class="cov6" title="33">{
+ for k := range raw </span><span class="cov6" title="41">{
+ if _, isTable := map[string]struct{}{"general": {}, "logging": {}, "completion": {}, "triggers": {}, "inline": {}, "chat": {}, "provider": {}, "openai": {}, "copilot": {}, "ollama": {}, "prompts": {}}[k]; isTable </span><span class="cov6" title="38">{
continue</span>
}
<span class="cov2" title="3">if _, isLegacy := legacy[k]; isLegacy </span><span class="cov0" title="0">{
@@ -783,13 +800,13 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co
}</span>
}
- <span class="cov4" title="11">if logger != nil </span><span class="cov4" title="11">{
+ <span class="cov5" title="16">if logger != nil </span><span class="cov5" title="16">{
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="cov4" title="11">tab := tables.toApp()
+ <span class="cov5" title="16">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">{
@@ -803,7 +820,7 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co
}
}
}
- <span class="cov4" title="11">if t, ok := raw["logging"].(map[string]any); ok </span><span class="cov2" title="3">{
+ <span class="cov5" title="16">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">
@@ -815,136 +832,136 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co
}
}
}
- <span class="cov4" title="11">return &amp;tab, nil</span>
+ <span class="cov5" title="16">return &amp;tab, nil</span>
}
-func (a *App) mergeWith(other *App) <span class="cov4" title="12">{
+func (a *App) mergeWith(other *App) <span class="cov5" title="28">{
a.mergeBasics(other)
a.mergeProviderFields(other)
a.mergePrompts(other)
}</span>
// mergeBasics merges general (non-provider) fields.
-func (a *App) mergeBasics(other *App) <span class="cov5" title="27">{
+func (a *App) mergeBasics(other *App) <span class="cov6" title="43">{
if other.MaxTokens &gt; 0 </span><span class="cov3" title="7">{
a.MaxTokens = other.MaxTokens
}</span>
- <span class="cov5" title="27">if s := strings.TrimSpace(other.ContextMode); s != "" </span><span class="cov3" title="7">{
+ <span class="cov6" title="43">if s := strings.TrimSpace(other.ContextMode); s != "" </span><span class="cov3" title="7">{
a.ContextMode = s
}</span>
- <span class="cov5" title="27">if other.ContextWindowLines &gt; 0 </span><span class="cov3" title="7">{
+ <span class="cov6" title="43">if other.ContextWindowLines &gt; 0 </span><span class="cov3" title="7">{
a.ContextWindowLines = other.ContextWindowLines
}</span>
- <span class="cov5" title="27">if other.MaxContextTokens &gt; 0 </span><span class="cov3" title="7">{
+ <span class="cov6" title="43">if other.MaxContextTokens &gt; 0 </span><span class="cov3" title="7">{
a.MaxContextTokens = other.MaxContextTokens
}</span>
- <span class="cov5" title="27">if other.LogPreviewLimit &gt;= 0 </span><span class="cov5" title="27">{
+ <span class="cov6" title="43">if other.LogPreviewLimit &gt;= 0 </span><span class="cov6" title="43">{
a.LogPreviewLimit = other.LogPreviewLimit
}</span>
- <span class="cov5" title="27">if other.CodingTemperature != nil </span><span class="cov3" title="7">{ // allow explicit 0.0
+ <span class="cov6" title="43">if other.CodingTemperature != nil </span><span class="cov3" title="7">{ // allow explicit 0.0
a.CodingTemperature = other.CodingTemperature
}</span>
- <span class="cov5" title="27">if other.ManualInvokeMinPrefix &gt;= 0 </span><span class="cov5" title="27">{
+ <span class="cov6" title="43">if other.ManualInvokeMinPrefix &gt;= 0 </span><span class="cov6" title="43">{
a.ManualInvokeMinPrefix = other.ManualInvokeMinPrefix
}</span>
- <span class="cov5" title="27">if other.CompletionDebounceMs &gt; 0 </span><span class="cov3" title="7">{
+ <span class="cov6" title="43">if other.CompletionDebounceMs &gt; 0 </span><span class="cov3" title="7">{
a.CompletionDebounceMs = other.CompletionDebounceMs
}</span>
- <span class="cov5" title="27">if other.CompletionThrottleMs &gt; 0 </span><span class="cov3" title="7">{
+ <span class="cov6" title="43">if other.CompletionThrottleMs &gt; 0 </span><span class="cov3" title="7">{
a.CompletionThrottleMs = other.CompletionThrottleMs
}</span>
- <span class="cov5" title="27">if len(other.TriggerCharacters) &gt; 0 </span><span class="cov3" title="7">{
+ <span class="cov6" title="43">if len(other.TriggerCharacters) &gt; 0 </span><span class="cov3" title="7">{
a.TriggerCharacters = slices.Clone(other.TriggerCharacters)
}</span>
- <span class="cov5" title="27">if s := strings.TrimSpace(other.InlineOpen); s != "" </span><span class="cov2" title="2">{
+ <span class="cov6" title="43">if s := strings.TrimSpace(other.InlineOpen); s != "" </span><span class="cov2" title="2">{
a.InlineOpen = s
}</span>
- <span class="cov5" title="27">if s := strings.TrimSpace(other.InlineClose); s != "" </span><span class="cov2" title="2">{
+ <span class="cov6" title="43">if s := strings.TrimSpace(other.InlineClose); s != "" </span><span class="cov2" title="2">{
a.InlineClose = s
}</span>
- <span class="cov5" title="27">if s := strings.TrimSpace(other.ChatSuffix); s != "" </span><span class="cov2" title="2">{
+ <span class="cov6" title="43">if s := strings.TrimSpace(other.ChatSuffix); s != "" </span><span class="cov2" title="2">{
a.ChatSuffix = s
}</span>
- <span class="cov5" title="27">if len(other.ChatPrefixes) &gt; 0 </span><span class="cov2" title="2">{
+ <span class="cov6" title="43">if len(other.ChatPrefixes) &gt; 0 </span><span class="cov2" title="2">{
a.ChatPrefixes = slices.Clone(other.ChatPrefixes)
}</span>
- <span class="cov5" title="27">if s := strings.TrimSpace(other.Provider); s != "" </span><span class="cov3" title="7">{
+ <span class="cov6" title="43">if s := strings.TrimSpace(other.Provider); s != "" </span><span class="cov3" title="7">{
a.Provider = s
}</span>
}
// mergePrompts copies non-empty prompt templates from other.
-func (a *App) mergePrompts(other *App) <span class="cov4" title="12">{
+func (a *App) mergePrompts(other *App) <span class="cov5" title="28">{
// Completion
if strings.TrimSpace(other.PromptCompletionSystemGeneral) != "" </span><span class="cov1" title="1">{
a.PromptCompletionSystemGeneral = other.PromptCompletionSystemGeneral
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCompletionSystemParams) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCompletionSystemParams) != "" </span><span class="cov1" title="1">{
a.PromptCompletionSystemParams = other.PromptCompletionSystemParams
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCompletionSystemInline) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCompletionSystemInline) != "" </span><span class="cov1" title="1">{
a.PromptCompletionSystemInline = other.PromptCompletionSystemInline
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCompletionUserGeneral) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCompletionUserGeneral) != "" </span><span class="cov1" title="1">{
a.PromptCompletionUserGeneral = other.PromptCompletionUserGeneral
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCompletionUserParams) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCompletionUserParams) != "" </span><span class="cov1" title="1">{
a.PromptCompletionUserParams = other.PromptCompletionUserParams
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCompletionExtraHeader) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCompletionExtraHeader) != "" </span><span class="cov1" title="1">{
a.PromptCompletionExtraHeader = other.PromptCompletionExtraHeader
}</span>
// Provider-native
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptNativeCompletion) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptNativeCompletion) != "" </span><span class="cov1" title="1">{
a.PromptNativeCompletion = other.PromptNativeCompletion
}</span>
// Chat
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptChatSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptChatSystem) != "" </span><span class="cov1" title="1">{
a.PromptChatSystem = other.PromptChatSystem
}</span>
// Code actions
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionRewriteSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionRewriteSystem) != "" </span><span class="cov1" title="1">{
a.PromptCodeActionRewriteSystem = other.PromptCodeActionRewriteSystem
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionDiagnosticsSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionDiagnosticsSystem) != "" </span><span class="cov1" title="1">{
a.PromptCodeActionDiagnosticsSystem = other.PromptCodeActionDiagnosticsSystem
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionDocumentSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionDocumentSystem) != "" </span><span class="cov1" title="1">{
a.PromptCodeActionDocumentSystem = other.PromptCodeActionDocumentSystem
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionRewriteUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionRewriteUser) != "" </span><span class="cov1" title="1">{
a.PromptCodeActionRewriteUser = other.PromptCodeActionRewriteUser
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionDiagnosticsUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionDiagnosticsUser) != "" </span><span class="cov1" title="1">{
a.PromptCodeActionDiagnosticsUser = other.PromptCodeActionDiagnosticsUser
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionDocumentUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionDocumentUser) != "" </span><span class="cov1" title="1">{
a.PromptCodeActionDocumentUser = other.PromptCodeActionDocumentUser
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionGoTestSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionGoTestSystem) != "" </span><span class="cov1" title="1">{
a.PromptCodeActionGoTestSystem = other.PromptCodeActionGoTestSystem
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionGoTestUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionGoTestUser) != "" </span><span class="cov1" title="1">{
a.PromptCodeActionGoTestUser = other.PromptCodeActionGoTestUser
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionSimplifySystem) != "" </span><span class="cov0" title="0">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionSimplifySystem) != "" </span><span class="cov0" title="0">{
a.PromptCodeActionSimplifySystem = other.PromptCodeActionSimplifySystem
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCodeActionSimplifyUser) != "" </span><span class="cov0" title="0">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCodeActionSimplifyUser) != "" </span><span class="cov0" title="0">{
a.PromptCodeActionSimplifyUser = other.PromptCodeActionSimplifyUser
}</span>
// CLI
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCLIDefaultSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCLIDefaultSystem) != "" </span><span class="cov1" title="1">{
a.PromptCLIDefaultSystem = other.PromptCLIDefaultSystem
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.PromptCLIExplainSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.PromptCLIExplainSystem) != "" </span><span class="cov1" title="1">{
a.PromptCLIExplainSystem = other.PromptCLIExplainSystem
}</span>
// Custom actions
- <span class="cov4" title="12">if len(other.CustomActions) &gt; 0 </span><span class="cov3" title="6">{
+ <span class="cov5" title="28">if len(other.CustomActions) &gt; 0 </span><span class="cov4" title="11">{
a.CustomActions = append([]CustomAction{}, other.CustomActions...)
}</span>
- <span class="cov4" title="12">if strings.TrimSpace(other.TmuxCustomMenuHotkey) != "" </span><span class="cov2" title="3">{
+ <span class="cov5" title="28">if strings.TrimSpace(other.TmuxCustomMenuHotkey) != "" </span><span class="cov2" title="3">{
a.TmuxCustomMenuHotkey = other.TmuxCustomMenuHotkey
}</span>
}
@@ -954,42 +971,42 @@ func (a App) Validate() error <span class="cov5" title="19">{
// Normalize and check duplicates for IDs and hotkeys