summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-06 13:18:21 +0300
committerPaul Buetow <paul@buetow.org>2025-09-06 13:18:21 +0300
commit5e966f50111adf6e2cb2683fe588f6fe033fa931 (patch)
tree19ac2033483c2ac6147e8f44ac37f14e6a5c0cf7
parent80e61812986573464cd24c4b3ffa605c4003146a (diff)
fix unit test coverage
-rw-r--r--Magefile.go59
-rw-r--r--README.md58
-rw-r--r--docs/coverage.html2585
-rw-r--r--docs/coverage.out14106
-rw-r--r--go.mod27
-rw-r--r--go.sum47
-rw-r--r--internal/appconfig/config.go802
-rw-r--r--internal/appconfig/config_test.go164
-rw-r--r--internal/hexaicli/run.go116
-rw-r--r--internal/hexailsp/run.go68
-rw-r--r--internal/logging/logging_test.go58
-rw-r--r--internal/lsp/build_prompts_table_test.go36
-rw-r--r--internal/lsp/chat_prompt_test.go58
-rw-r--r--internal/lsp/codeaction_prompts_test.go171
-rw-r--r--internal/lsp/completion_messages_test.go19
-rw-r--r--internal/lsp/document_test.go52
-rw-r--r--internal/lsp/handlers.go112
-rw-r--r--internal/lsp/handlers_codeaction.go88
-rw-r--r--internal/lsp/handlers_completion.go70
-rw-r--r--internal/lsp/handlers_document.go6
-rw-r--r--internal/lsp/handlers_utils.go39
-rw-r--r--internal/lsp/helpers_more_test.go20
-rw-r--r--internal/lsp/provider_native_success_test.go44
-rw-r--r--internal/lsp/server.go126
-rw-r--r--internal/lsp/testhelper_capture_llm_test.go9
25 files changed, 14233 insertions, 4707 deletions
diff --git a/Magefile.go b/Magefile.go
index 6acc882..dedb72c 100644
--- a/Magefile.go
+++ b/Magefile.go
@@ -17,16 +17,16 @@ import (
)
var (
- Default = Build // Default target: build both binaries.
- coverageThreshold float64 = 85
- coveragePrinted = make(chan struct{}, 1)
+ Default = Build // Default target: build all binaries.
+ coverageThreshold float64 = 85
+ coveragePrinted = make(chan struct{}, 1)
)
// Build builds the Hexai LSP and CLI binaries.
func Build() error {
- mg.Deps(BuildHexaiLSP, BuildHexaiCLI)
- printCoverage()
- return nil
+ mg.Deps(BuildHexaiLSP, BuildHexaiCLI, BuildHexaiAction)
+ printCoverage()
+ return nil
}
// BuildHexaiLSP builds the LSP server binary.
@@ -37,18 +37,27 @@ func BuildHexaiLSP() error {
// BuildHexaiCLI builds the CLI binary.
func BuildHexaiCLI() error {
- printCoverage()
- return sh.RunV("go", "build", "-o", "hexai", "cmd/hexai/main.go")
+ printCoverage()
+ return sh.RunV("go", "build", "-o", "hexai", "cmd/hexai/main.go")
+}
+
+// BuildHexaiAction builds the hexai-action TUI binary.
+func BuildHexaiAction() error {
+ printCoverage()
+ return sh.RunV("go", "build", "-o", "hexai-action", "cmd/internal/hexai-action/main.go")
}
// Dev runs tests, vet, lint, then builds with race for both binaries.
func Dev() error {
- printCoverage()
- mg.Deps(Test, Vet, Lint)
- if err := sh.RunV("go", "build", "-race", "-o", "hexai-lsp", "cmd/hexai-lsp/main.go"); err != nil {
- return err
- }
- return sh.RunV("go", "build", "-race", "-o", "hexai", "cmd/hexai/main.go")
+ printCoverage()
+ mg.Deps(Test, Vet, Lint)
+ if err := sh.RunV("go", "build", "-race", "-o", "hexai-lsp", "cmd/hexai-lsp/main.go"); err != nil {
+ return err
+ }
+ if err := sh.RunV("go", "build", "-race", "-o", "hexai", "cmd/hexai/main.go"); err != nil {
+ return err
+ }
+ return sh.RunV("go", "build", "-race", "-o", "hexai-action", "cmd/internal/hexai-action/main.go")
}
// Run launches the LSP server via go run (useful during development).
@@ -68,8 +77,8 @@ func RunCLI() error {
// Install copies built binaries to GOPATH/bin (defaults to ~/go/bin when GOPATH is unset).
func Install() error {
- printCoverage()
- mg.Deps(Build)
+ printCoverage()
+ mg.Deps(Build)
gopath := os.Getenv("GOPATH")
if gopath == "" {
home, err := os.UserHomeDir()
@@ -82,10 +91,20 @@ func Install() error {
if err := os.MkdirAll(bin, 0o755); err != nil {
return err
}
- if err := sh.RunV("cp", "-v", "./hexai-lsp", bin+"/"); err != nil {
- return err
- }
- return sh.RunV("cp", "-v", "./hexai", bin+"/")
+ if err := sh.RunV("cp", "-v", "./hexai-lsp", bin+"/"); err != nil {
+ return err
+ }
+ if err := sh.RunV("cp", "-v", "./hexai", bin+"/"); err != nil {
+ return err
+ }
+ return sh.RunV("cp", "-v", "./hexai-action", bin+"/")
+}
+
+// RunAction runs the hexai-action TUI via go run (reads stdin).
+func RunAction() error {
+ printCoverage()
+ mg.Deps(Dev)
+ return sh.RunV("go", "run", "cmd/internal/hexai-action/main.go")
}
// printCoverage prints a warning if an existing coverage profile shows total < coverateThreshold.
diff --git a/README.md b/README.md
index f9b9864..1486f4a 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ It has got improved capabilities for Go code understanding (for example, create
* LSP Code actions
* LSP in-editor chat with the LLM
* Stand-alone command line tool for LLM interaction
+* TUI code-action runner (`hexai-action`) with Bubble Tea
* Support for OpenAI, GitHub Copilot, and Ollama
## Documentation
@@ -24,7 +25,7 @@ It has got improved capabilities for Go code understanding (for example, create
Hexai uses Mage for developer tasks. Install Mage, then run targets like build, dev, test, and install.
- Install Mage: `go install github.com/magefile/mage@latest`
-- Build binaries: `mage build` (produces `hexai` and `hexai-lsp`)
+- Build binaries: `mage build` (produces `hexai`, `hexai-lsp`, and `hexai-action`)
- Dev build (+ tests, vet, lint): `mage dev`
- Run tests: `mage test`
- Run tests with coverage: `go test ./... -cover`
@@ -40,3 +41,58 @@ Either use the Mage method as mentioned above, or install directly with:
- CLI: `go install codeberg.org/snonux/hexai/cmd/hexai@latest`
- LSP: `go install codeberg.org/snonux/hexai/cmd/hexai-lsp@latest`
+
+For `hexai-action`, use Mage or a local build:
+
+- Build locally: `go build -o hexai-action cmd/internal/hexai-action/main.go`
+- Or via Mage: `mage buildHexaiAction` (or `mage build`)
+- Install: `mage install` (copies `hexai-action` to `GOPATH/bin` together with other binaries)
+
+## Hexai Action (TUI)
+
+`hexai-action` is a small TUI to run Hexai code actions from stdin. It loads the same `config.toml` as `hexai` and `hexai-lsp` (XDG path: `~/.config/hexai/config.toml`), and respects the same environment overrides.
+
+- Pipe code (and optionally diagnostics) into the tool.
+- Select an action with arrow keys, vi keys (`j/k`, `g/G`), Enter, or hotkeys `[s] [r] [d] [c] [t]`.
+- The tool prints the transformed text to stdout.
+
+Input formats
+
+- Rewrite: include an inline instruction near the top of the selection using one of:
+ - `;do something;`
+ - `/* do something */`
+ - `<!-- do something -->`
+ - `// do something` (or `#`, `--`)
+
+- Diagnostics (optional block):
+ - Begin with a header line `Diagnostics:` (case-insensitive), one diagnostic per line, blank line, then the code selection.
+
+Examples
+
+- Rewrite selection:
+
+```
+;replace fmt.Println with log.Println;
+package main
+
+import "fmt"
+
+func main() { fmt.Println("hi") }
+```
+
+- Diagnostics + selection:
+
+```
+Diagnostics:
+missing return at end of function
+use of undefined: foo
+
+func f() int {
+ foo()
+}
+```
+
+Run:
+
+- `cat input.go | ./hexai-action`
+- or `./hexai-action < input.go`
diff --git a/docs/coverage.html b/docs/coverage.html
index d22ef74..6b80630 100644
--- a/docs/coverage.html
+++ b/docs/coverage.html
@@ -55,53 +55,69 @@
<div id="nav">
<select id="files">
- <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (0.0%)</option>
+ <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (75.0%)</option>
- <option value="file1">codeberg.org/snonux/hexai/cmd/hexai/main.go (0.0%)</option>
+ <option value="file1">codeberg.org/snonux/hexai/cmd/hexai/main.go (71.4%)</option>
- <option value="file2">codeberg.org/snonux/hexai/internal/appconfig/config.go (91.6%)</option>
+ <option value="file2">codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go (0.0%)</option>
- <option value="file3">codeberg.org/snonux/hexai/internal/hexaicli/run.go (72.6%)</option>
+ <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (91.6%)</option>
- <option value="file4">codeberg.org/snonux/hexai/internal/hexailsp/run.go (92.5%)</option>
+ <option value="file4">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option>
- <option value="file5">codeberg.org/snonux/hexai/internal/llm/copilot.go (81.8%)</option>
+ <option value="file5">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (81.1%)</option>
- <option value="file6">codeberg.org/snonux/hexai/internal/llm/ollama.go (88.0%)</option>
+ <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (33.3%)</option>
- <option value="file7">codeberg.org/snonux/hexai/internal/llm/openai.go (85.5%)</option>
+ <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (47.3%)</option>
- <option value="file8">codeberg.org/snonux/hexai/internal/llm/provider.go (100.0%)</option>
+ <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (91.7%)</option>
- <option value="file9">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option>
+ <option value="file9">codeberg.org/snonux/hexai/internal/hexaicli/run.go (78.8%)</option>
- <option value="file10">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option>
+ <option value="file10">codeberg.org/snonux/hexai/internal/hexailsp/run.go (92.5%)</option>
- <option value="file11">codeberg.org/snonux/hexai/internal/logging/logging.go (100.0%)</option>
+ <option value="file11">codeberg.org/snonux/hexai/internal/llm/copilot.go (82.4%)</option>
- <option value="file12">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option>
+ <option value="file12">codeberg.org/snonux/hexai/internal/llm/ollama.go (89.8%)</option>
- <option value="file13">codeberg.org/snonux/hexai/internal/lsp/document.go (90.1%)</option>
+ <option value="file13">codeberg.org/snonux/hexai/internal/llm/openai.go (85.5%)</option>
- <option value="file14">codeberg.org/snonux/hexai/internal/lsp/handlers.go (92.9%)</option>
+ <option value="file14">codeberg.org/snonux/hexai/internal/llm/provider.go (100.0%)</option>
- <option value="file15">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (81.2%)</option>
+ <option value="file15">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option>
- <option value="file16">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (87.6%)</option>
+ <option value="file16">codeberg.org/snonux/hexai/internal/llmutils/client.go (100.0%)</option>
- <option value="file17">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (88.9%)</option>
+ <option value="file17">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option>
- <option value="file18">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option>
+ <option value="file18">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option>
- <option value="file19">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (55.6%)</option>
+ <option value="file19">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option>
- <option value="file20">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (88.5%)</option>
+ <option value="file20">codeberg.org/snonux/hexai/internal/lsp/document.go (90.1%)</option>
- <option value="file21">codeberg.org/snonux/hexai/internal/lsp/server.go (82.1%)</option>
+ <option value="file21">codeberg.org/snonux/hexai/internal/lsp/handlers.go (92.9%)</option>
- <option value="file22">codeberg.org/snonux/hexai/internal/lsp/transport.go (71.4%)</option>
+ <option value="file22">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (81.9%)</option>
- <option value="file23">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (60.0%)</option>
+ <option value="file23">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (87.6%)</option>
+
+ <option value="file24">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (88.9%)</option>
+
+ <option value="file25">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option>
+
+ <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (55.6%)</option>
+
+ <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.0%)</option>
+
+ <option value="file28">codeberg.org/snonux/hexai/internal/lsp/server.go (82.1%)</option>
+
+ <option value="file29">codeberg.org/snonux/hexai/internal/lsp/transport.go (71.4%)</option>
+
+ <option value="file30">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (60.0%)</option>
+
+ <option value="file31">codeberg.org/snonux/hexai/internal/textutil/textutil.go (89.0%)</option>
</select>
</div>
@@ -136,11 +152,11 @@ import (
"codeberg.org/snonux/hexai/internal/hexailsp"
)
-func main() <span class="cov0" title="0">{
+func main() <span class="cov8" title="1">{
logPath := flag.String("log", "/tmp/hexai-lsp.log", "path to log file (optional)")
showVersion := flag.Bool("version", false, "print version and exit")
flag.Parse()
- if *showVersion </span><span class="cov0" title="0">{
+ if *showVersion </span><span class="cov8" title="1">{
log.Println(internal.Version)
return
}</span>
@@ -164,10 +180,10 @@ import (
"codeberg.org/snonux/hexai/internal/hexaicli"
)
-func main() <span class="cov0" title="0">{
+func main() <span class="cov8" title="1">{
showVersion := flag.Bool("version", false, "print version and exit")
flag.Parse()
- if *showVersion </span><span class="cov0" title="0">{
+ if *showVersion </span><span class="cov8" title="1">{
fmt.Fprintln(os.Stdout, internal.Version)
return
}</span>
@@ -178,7 +194,25 @@ func main() <span class="cov0" title="0">{
}
</pre>
- <pre class="file" id="file2" style="display: none">// Summary: Application configuration model and loader; reads ~/.config/hexai/config.toml and merges defaults.
+ <pre class="file" id="file2" style="display: none">package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "codeberg.org/snonux/hexai/internal/hexaiaction"
+)
+
+func main() <span class="cov0" title="0">{
+ if err := hexaiaction.Run(context.Background(), os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }</span>
+}
+</pre>
+
+ <pre class="file" id="file3" style="display: none">// Summary: Application configuration model and loader; reads ~/.config/hexai/config.toml and merges defaults.
package appconfig
import (
@@ -234,43 +268,43 @@ type App struct {
// Default temperature for Ollama requests (nil means use provider default)
OllamaTemperature *float64 `json:"ollama_temperature" toml:"ollama_temperature"`
CopilotBaseURL string `json:"copilot_base_url" toml:"copilot_base_url"`
- CopilotModel string `json:"copilot_model" toml:"copilot_model"`
- // Default temperature for Copilot requests (nil means use provider default)
- CopilotTemperature *float64 `json:"copilot_temperature" toml:"copilot_temperature"`
-
- // Prompt templates (configured only via file; no env overrides)
- // Completion/chat/code action/CLI prompt strings. See config.toml.example for placeholders.
- // Completion
- PromptCompletionSystemGeneral string `json:"-" toml:"-"`
- PromptCompletionSystemParams string `json:"-" toml:"-"`
- PromptCompletionSystemInline string `json:"-" toml:"-"`
- PromptCompletionUserGeneral string `json:"-" toml:"-"`
- PromptCompletionUserParams string `json:"-" toml:"-"`
- PromptCompletionExtraHeader string `json:"-" toml:"-"`
- // Provider-native code-completer
- PromptNativeCompletion string `json:"-" toml:"-"`
- // In-editor chat
- PromptChatSystem string `json:"-" toml:"-"`
- // Code actions
- PromptCodeActionRewriteSystem string `json:"-" toml:"-"`
- PromptCodeActionDiagnosticsSystem string `json:"-" toml:"-"`
- PromptCodeActionDocumentSystem string `json:"-" toml:"-"`
- PromptCodeActionRewriteUser string `json:"-" toml:"-"`
- PromptCodeActionDiagnosticsUser string `json:"-" toml:"-"`
- PromptCodeActionDocumentUser string `json:"-" toml:"-"`
- PromptCodeActionGoTestSystem string `json:"-" toml:"-"`
- PromptCodeActionGoTestUser string `json:"-" toml:"-"`
- // CLI
- PromptCLIDefaultSystem string `json:"-" toml:"-"`
- PromptCLIExplainSystem string `json:"-" toml:"-"`
+ CopilotModel string `json:"copilot_model" toml:"copilot_model"`
+ // Default temperature for Copilot requests (nil means use provider default)
+ CopilotTemperature *float64 `json:"copilot_temperature" toml:"copilot_temperature"`
+
+ // Prompt templates (configured only via file; no env overrides)
+ // Completion/chat/code action/CLI prompt strings. See config.toml.example for placeholders.
+ // Completion
+ PromptCompletionSystemGeneral string `json:"-" toml:"-"`
+ PromptCompletionSystemParams string `json:"-" toml:"-"`
+ PromptCompletionSystemInline string `json:"-" toml:"-"`
+ PromptCompletionUserGeneral string `json:"-" toml:"-"`
+ PromptCompletionUserParams string `json:"-" toml:"-"`
+ PromptCompletionExtraHeader string `json:"-" toml:"-"`
+ // Provider-native code-completer
+ PromptNativeCompletion string `json:"-" toml:"-"`
+ // In-editor chat
+ PromptChatSystem string `json:"-" toml:"-"`
+ // Code actions
+ PromptCodeActionRewriteSystem string `json:"-" toml:"-"`
+ PromptCodeActionDiagnosticsSystem string `json:"-" toml:"-"`
+ PromptCodeActionDocumentSystem string `json:"-" toml:"-"`
+ PromptCodeActionRewriteUser string `json:"-" toml:"-"`
+ PromptCodeActionDiagnosticsUser string `json:"-" toml:"-"`
+ PromptCodeActionDocumentUser string `json:"-" toml:"-"`
+ PromptCodeActionGoTestSystem string `json:"-" toml:"-"`
+ PromptCodeActionGoTestUser string `json:"-" toml:"-"`
+ // CLI
+ PromptCLIDefaultSystem string `json:"-" toml:"-"`
+ PromptCLIExplainSystem string `json:"-" toml:"-"`
}
// Constructor: defaults for App (kept first among functions)
-func newDefaultConfig() App <span class="cov5" title="11">{
+func newDefaultConfig() App <span class="cov5" title="13">{
// Coding-friendly default temperature across providers
// Users can override per provider in config.toml (including 0.0).
t := 0.2
- return App{
+ return App{
MaxTokens: 4000,
ContextMode: "always-full",
ContextWindowLines: 120,
@@ -287,47 +321,47 @@ func newDefaultConfig() App <span class="cov5" title="11">{
InlineOpen: "&gt;",
InlineClose: "&gt;",
ChatSuffix: "&gt;",
- ChatPrefixes: []string{"?", "!", ":", ";"},
-
- // Default prompt templates (match current hard-coded strings)
- PromptCompletionSystemParams: "You are a code completion engine for function signatures. Return only the parameter list contents (without parentheses), no braces, no prose. Prefer idiomatic names and types.",
- PromptCompletionUserParams: "Cursor is inside the function parameter list. Suggest only the parameter list (no parentheses).\nFunction line: {{function}}\nCurrent line (cursor at {{char}}): {{current}}",
- PromptCompletionSystemGeneral: "You are a terse code completion engine. Return only the code to insert, no surrounding prose or backticks. Only continue from the cursor; never repeat characters already present to the left of the cursor on the current line (e.g., if 'name :=' is already typed, only return the right-hand side expression).",
- PromptCompletionUserGeneral: "Provide the next likely code to insert at the cursor.\nFile: {{file}}\nFunction/context: {{function}}\nAbove line: {{above}}\nCurrent line (cursor at character {{char}}): {{current}}\nBelow line: {{below}}\nOnly return the completion snippet.",
- PromptCompletionSystemInline: "You are a precise code completion/refactoring engine. Output only the code to insert with no prose, no comments, and no backticks. Return raw code only.",
- PromptCompletionExtraHeader: "Additional context:\n{{context}}",
-
- PromptNativeCompletion: "// Path: {{path}}\n{{before}}",
-
- PromptChatSystem: "You are a helpful coding assistant. Answer concisely and clearly.",
-
- PromptCodeActionRewriteSystem: "You are a precise code refactoring engine. Rewrite the given code strictly according to the instruction. Return only the updated code with no prose or backticks. Preserve formatting where reasonable.",
- PromptCodeActionDiagnosticsSystem: "You are a precise code fixer. Resolve the given diagnostics by editing only the selected code. Return only the corrected code with no prose or backticks. Keep behavior and style, and avoid unrelated changes.",
- PromptCodeActionDocumentSystem: "You are a precise code documentation engine. Add idiomatic documentation comments to the given code. Preserve exact behavior and formatting as much as possible. Return only the updated code with comments, no prose or backticks.",
- PromptCodeActionRewriteUser: "Instruction: {{instruction}}\n\nSelected code to transform:\n{{selection}}",
- PromptCodeActionDiagnosticsUser: "Diagnostics to resolve (selection only):\n{{diagnostics}}\n\nSelected code:\n{{selection}}",
- PromptCodeActionDocumentUser: "Add documentation comments to this code:\n{{selection}}",
- PromptCodeActionGoTestSystem: "You are a precise Go unit test generator. Given a Go function, write one or more Test* functions using the testing package. Do NOT include package or imports, only the test function(s). Prefer table-driven tests. Keep it minimal and idiomatic.",
- PromptCodeActionGoTestUser: "Function under test:\n{{function}}",
-
- 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.",
- }
+ ChatPrefixes: []string{"?", "!", ":", ";"},
+
+ // Default prompt templates (match current hard-coded strings)
+ PromptCompletionSystemParams: "You are a code completion engine for function signatures. Return only the parameter list contents (without parentheses), no braces, no prose. Prefer idiomatic names and types.",
+ PromptCompletionUserParams: "Cursor is inside the function parameter list. Suggest only the parameter list (no parentheses).\nFunction line: {{function}}\nCurrent line (cursor at {{char}}): {{current}}",
+ PromptCompletionSystemGeneral: "You are a terse code completion engine. Return only the code to insert, no surrounding prose or backticks. Only continue from the cursor; never repeat characters already present to the left of the cursor on the current line (e.g., if 'name :=' is already typed, only return the right-hand side expression).",
+ PromptCompletionUserGeneral: "Provide the next likely code to insert at the cursor.\nFile: {{file}}\nFunction/context: {{function}}\nAbove line: {{above}}\nCurrent line (cursor at character {{char}}): {{current}}\nBelow line: {{below}}\nOnly return the completion snippet.",
+ PromptCompletionSystemInline: "You are a precise code completion/refactoring engine. Output only the code to insert with no prose, no comments, and no backticks. Return raw code only.",
+ PromptCompletionExtraHeader: "Additional context:\n{{context}}",
+
+ PromptNativeCompletion: "// Path: {{path}}\n{{before}}",
+
+ PromptChatSystem: "You are a helpful coding assistant. Answer concisely and clearly.",
+
+ PromptCodeActionRewriteSystem: "You are a precise code refactoring engine. Rewrite the given code strictly according to the instruction. Return only the updated code with no prose or backticks. Preserve formatting where reasonable.",
+ PromptCodeActionDiagnosticsSystem: "You are a precise code fixer. Resolve the given diagnostics by editing only the selected code. Return only the corrected code with no prose or backticks. Keep behavior and style, and avoid unrelated changes.",
+ PromptCodeActionDocumentSystem: "You are a precise code documentation engine. Add idiomatic documentation comments to the given code. Preserve exact behavior and formatting as much as possible. Return only the updated code with comments, no prose or backticks.",
+ PromptCodeActionRewriteUser: "Instruction: {{instruction}}\n\nSelected code to transform:\n{{selection}}",
+ PromptCodeActionDiagnosticsUser: "Diagnostics to resolve (selection only):\n{{diagnostics}}\n\nSelected code:\n{{selection}}",
+ PromptCodeActionDocumentUser: "Add documentation comments to this code:\n{{selection}}",
+ PromptCodeActionGoTestSystem: "You are a precise Go unit test generator. Given a Go function, write one or more Test* functions using the testing package. Do NOT include package or imports, only the test function(s). Prefer table-driven tests. Keep it minimal and idiomatic.",
+ PromptCodeActionGoTestUser: "Function under test:\n{{function}}",
+
+ 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.",
+ }
}</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="10">{
+func Load(logger *log.Logger) App <span class="cov5" title="12">{
cfg := newDefaultConfig()
- if logger == nil </span><span class="cov2" title="3">{
+ if logger == nil </span><span class="cov3" title="4">{
return cfg // Return defaults if no logger is provided (e.g. in tests)
}</span>
- <span class="cov4" title="7">configPath, err := getConfigPath()
+ <span class="cov4" title="8">configPath, err := getConfigPath()
if err != nil </span><span class="cov0" title="0">{
logger.Printf("%v", err)
// Even if config path cannot be resolved, still allow env overrides below.
- }</span> else<span class="cov4" title="7"> {
+ }</span> else<span class="cov4" title="8"> {
if fileCfg, err := loadFromFile(configPath, logger); err == nil &amp;&amp; fileCfg != nil </span><span class="cov3" title="4">{
cfg.mergeWith(fileCfg)
}</span>
@@ -336,359 +370,359 @@ func Load(logger *log.Logger) App <span class="cov5" title="10">{
}
// Environment overrides (take precedence over file)
- <span class="cov4" title="7">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov1" title="1">{
+ <span class="cov4" title="8">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov1" title="1">{
cfg.mergeWith(envCfg)
}</span>
- <span class="cov4" title="7">return cfg</span>
+ <span class="cov4" title="8">return cfg</span>
}
// Private helpers
// Sectioned (table-based) file format only.
type fileConfig struct {
- // Section tables only (flat keys are not allowed)
- General sectionGeneral `toml:"general"`
- Logging sectionLogging `toml:"logging"`
- Completion sectionCompletion `toml:"completion"`
- Triggers sectionTriggers `toml:"triggers"`
- Inline sectionInline `toml:"inline"`
- Chat sectionChat `toml:"chat"`
- Provider sectionProvider `toml:"provider"`
- OpenAI sectionOpenAI `toml:"openai"`
- Copilot sectionCopilot `toml:"copilot"`
- Ollama sectionOllama `toml:"ollama"`
- Prompts sectionPrompts `toml:"prompts"`
+ // Section tables only (flat keys are not allowed)
+ General sectionGeneral `toml:"general"`
+ Logging sectionLogging `toml:"logging"`
+ Completion sectionCompletion `toml:"completion"`
+ Triggers sectionTriggers `toml:"triggers"`
+ Inline sectionInline `toml:"inline"`
+ Chat sectionChat `toml:"chat"`
+ Provider sectionProvider `toml:"provider"`
+ OpenAI sectionOpenAI `toml:"openai"`
+ Copilot sectionCopilot `toml:"copilot"`
+ Ollama sectionOllama `toml:"ollama"`
+ Prompts sectionPrompts `toml:"prompts"`
}
type sectionGeneral struct {
- MaxTokens int `toml:"max_tokens"`
- ContextMode string `toml:"context_mode"`
- ContextWindowLines int `toml:"context_window_lines"`
- MaxContextTokens int `toml:"max_context_tokens"`
- CodingTemperature *float64 `toml:"coding_temperature"`
+ MaxTokens int `toml:"max_tokens"`
+ ContextMode string `toml:"context_mode"`
+ ContextWindowLines int `toml:"context_window_lines"`
+ MaxContextTokens int `toml:"max_context_tokens"`
+ CodingTemperature *float64 `toml:"coding_temperature"`
}
type sectionLogging struct {
- LogPreviewLimit int `toml:"log_preview_limit"`
+ LogPreviewLimit int `toml:"log_preview_limit"`
}
type sectionCompletion struct {
- CompletionDebounceMs int `toml:"completion_debounce_ms"`
- CompletionThrottleMs int `toml:"completion_throttle_ms"`
- ManualInvokeMinPrefix int `toml:"manual_invoke_min_prefix"`
+ CompletionDebounceMs int `toml:"completion_debounce_ms"`
+ CompletionThrottleMs int `toml:"completion_throttle_ms"`
+ ManualInvokeMinPrefix int `toml:"manual_invoke_min_prefix"`
}
type sectionTriggers struct {
- TriggerCharacters []string `toml:"trigger_characters"`
+ TriggerCharacters []string `toml:"trigger_characters"`
}
type sectionInline struct {
- InlineOpen string `toml:"inline_open"`
- InlineClose string `toml:"inline_close"`
+ InlineOpen string `toml:"inline_open"`
+ InlineClose string `toml:"inline_close"`
}
type sectionChat struct {
- ChatSuffix string `toml:"chat_suffix"`
- ChatPrefixes []string `toml:"chat_prefixes"`
+ ChatSuffix string `toml:"chat_suffix"`
+ ChatPrefixes []string `toml:"chat_prefixes"`
}
type sectionProvider struct {
- Name string `toml:"name"`
+ Name string `toml:"name"`
}
type sectionOpenAI struct {
- Model string `toml:"model"`
- BaseURL string `toml:"base_url"`
- Temperature *float64 `toml:"temperature"`
+ Model string `toml:"model"`
+ BaseURL string `toml:"base_url"`
+ Temperature *float64 `toml:"temperature"`
}
type sectionCopilot struct {
- Model string `toml:"model"`
- BaseURL string `toml:"base_url"`
- Temperature *float64 `toml:"temperature"`
+ Model string `toml:"model"`
+ BaseURL string `toml:"base_url"`
+ Temperature *float64 `toml:"temperature"`
}
type sectionOllama struct {
- Model string `toml:"model"`
- BaseURL string `toml:"base_url"`
- Temperature *float64 `toml:"temperature"`
+ Model string `toml:"model"`
+ BaseURL string `toml:"base_url"`
+ Temperature *float64 `toml:"temperature"`
}
// Prompts sections
type sectionPrompts struct {
- Completion sectionPromptsCompletion `toml:"completion"`
- Chat sectionPromptsChat `toml:"chat"`
- CodeAction sectionPromptsCodeAction `toml:"code_action"`
- CLI sectionPromptsCLI `toml:"cli"`
- ProviderNative sectionPromptsProviderNative `toml:"provider_native"`
+ Completion sectionPromptsCompletion `toml:"completion"`
+ Chat sectionPromptsChat `toml:"chat"`
+ CodeAction sectionPromptsCodeAction `toml:"code_action"`
+ CLI sectionPromptsCLI `toml:"cli"`
+ ProviderNative sectionPromptsProviderNative `toml:"provider_native"`
}
type sectionPromptsCompletion struct {
- SystemGeneral string `toml:"system_general"`
- SystemParams string `toml:"system_params"`
- SystemInline string `toml:"system_inline"`
- UserGeneral string `toml:"user_general"`
- UserParams string `toml:"user_params"`
- ExtraHeader string `toml:"additional_context"`
+ SystemGeneral string `toml:"system_general"`
+ SystemParams string `toml:"system_params"`
+ SystemInline string `toml:"system_inline"`
+ UserGeneral string `toml:"user_general"`
+ UserParams string `toml:"user_params"`
+ ExtraHeader string `toml:"additional_context"`
}
type sectionPromptsChat struct {
- System string `toml:"system"`
+ System string `toml:"system"`
}
type sectionPromptsCodeAction struct {
- RewriteSystem string `toml:"rewrite_system"`
- DiagnosticsSystem string `toml:"diagnostics_system"`
- DocumentSystem string `toml:"document_system"`
- RewriteUser string `toml:"rewrite_user"`
- DiagnosticsUser string `toml:"diagnostics_user"`
- DocumentUser string `toml:"document_user"`
- GoTestSystem string `toml:"go_test_system"`<