Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion cmd/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import (
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/hmans/beans/internal/beancore"
"github.com/hmans/beans/internal/config"
"github.com/hmans/beans/internal/ui"
"github.com/spf13/cobra"
)

var (
Expand Down Expand Up @@ -98,6 +98,18 @@ Note: Cycles cannot be auto-fixed and require manual intervention.`,
}
}

// 5. Check custom prime template exists (if configured)
if cfg.Templates.Prime != "" {
primePath := cfg.ResolvePrimeTemplatePath()
if _, err := os.Stat(primePath); os.IsNotExist(err) {
configErrors = append(configErrors, fmt.Sprintf("custom prime template not found: %s", primePath))
} else if err != nil {
configErrors = append(configErrors, fmt.Sprintf("cannot access custom prime template: %s: %v", primePath, err))
} else if !checkJSON {
fmt.Printf(" %s Custom prime template exists (%s)\n", ui.Success.Render("✓"), cfg.Templates.Prime)
}
}

// Print config errors in human-readable mode
if !checkJSON {
for _, e := range configErrors {
Expand Down
98 changes: 91 additions & 7 deletions cmd/prime.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package cmd

import (
"bytes"
_ "embed"
"fmt"
"io"
"os"
"text/template"

"github.com/spf13/cobra"
"github.com/hmans/beans/internal/config"
"github.com/spf13/cobra"
)

//go:embed prompt.tmpl
Expand All @@ -18,6 +21,62 @@ type promptData struct {
Types []config.TypeConfig
Statuses []config.StatusConfig
Priorities []config.PriorityConfig
OriginalPrime string
}

// primeResult holds the output of renderPrime for inspection.
type primeResult struct {
Output string // The rendered prime output
Warning string // Non-empty if a fallback occurred
}

// renderPrime renders the prime output using the given config.
// If a custom template is configured but cannot be loaded or parsed,
// it falls back to the built-in template and sets a warning.
func renderPrime(primeCfg *config.Config, data promptData) (*primeResult, error) {
builtinTmpl, err := template.New("prompt").Parse(agentPromptTemplate)
if err != nil {
return nil, err
}

primePath := primeCfg.ResolvePrimeTemplatePath()
if primePath == "" {
var buf bytes.Buffer
if err := builtinTmpl.Execute(&buf, data); err != nil {
return nil, err
}
return &primeResult{Output: buf.String()}, nil
}

// Custom template configured - render built-in to string first
var builtinBuf bytes.Buffer
if err := builtinTmpl.Execute(&builtinBuf, data); err != nil {
return nil, fmt.Errorf("rendering built-in prime template: %w", err)
}
data.OriginalPrime = builtinBuf.String()

// Read and parse the custom template
customTmplContent, err := os.ReadFile(primePath)
if err != nil {
return &primeResult{
Output: builtinBuf.String(),
Warning: fmt.Sprintf("custom prime template not found: %s (falling back to built-in)", primePath),
}, nil
}

customTmpl, err := template.New("custom-prompt").Parse(string(customTmplContent))
if err != nil {
return &primeResult{
Output: builtinBuf.String(),
Warning: fmt.Sprintf("parsing custom prime template: %v (falling back to built-in)", err),
}, nil
}

var customBuf bytes.Buffer
if err := customTmpl.Execute(&customBuf, data); err != nil {
return nil, fmt.Errorf("executing custom prime template: %w", err)
}
return &primeResult{Output: customBuf.String()}, nil
}

var primeCmd = &cobra.Command{
Expand All @@ -28,6 +87,7 @@ var primeCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
// If no explicit path given, check if a beans project exists by searching
// upward for a .beans.yml config file
var primeCfg *config.Config
if beansPath == "" && configPath == "" {
cwd, err := os.Getwd()
if err != nil {
Expand All @@ -38,11 +98,25 @@ var primeCmd = &cobra.Command{
// No config file found - silently exit
return nil
}
}

tmpl, err := template.New("prompt").Parse(agentPromptTemplate)
if err != nil {
return err
primeCfg, err = config.Load(configFile)
if err != nil {
return fmt.Errorf("loading config: %w", err)
}
} else if configPath != "" {
var err error
primeCfg, err = config.Load(configPath)
if err != nil {
return fmt.Errorf("loading config from %s: %w", configPath, err)
}
} else {
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("getting current directory: %w", err)
}
primeCfg, err = config.LoadFromDirectory(cwd)
if err != nil {
return fmt.Errorf("loading config: %w", err)
}
}

data := promptData{
Expand All @@ -52,7 +126,17 @@ var primeCmd = &cobra.Command{
Priorities: config.DefaultPriorities,
}

return tmpl.Execute(os.Stdout, data)
result, err := renderPrime(primeCfg, data)
if err != nil {
return err
}

if result.Warning != "" {
fmt.Fprintf(os.Stderr, "warning: %s\n", result.Warning)
}

_, err = io.WriteString(os.Stdout, result.Output)
return err
},
}

Expand Down
Loading