From 9a4d4c072744148395bee234d4cdd95c354cf92d Mon Sep 17 00:00:00 2001 From: s3rj1k Date: Fri, 17 Apr 2026 19:08:03 +0000 Subject: [PATCH] feat: run --summary via agent CLI (claude -p by default) --- internal/engine/engine.go | 2 +- internal/summary/cli.go | 79 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 internal/summary/cli.go diff --git a/internal/engine/engine.go b/internal/engine/engine.go index a70e4df..df5f0a0 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -253,7 +253,7 @@ func Run(opts Options) (*Result, error) { if !opts.Quiet { fmt.Println("[stacklit] generating AI summary...") } - text, summaryErr := summary.Generate(idx) + text, summaryErr := summary.Run(idx) if summaryErr != nil { fmt.Printf("[stacklit] warning: summary failed: %v\n", summaryErr) } else { diff --git a/internal/summary/cli.go b/internal/summary/cli.go new file mode 100644 index 0000000..b34bc1b --- /dev/null +++ b/internal/summary/cli.go @@ -0,0 +1,79 @@ +package summary + +import ( + "bytes" + "cmp" + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/glincker/stacklit/internal/schema" +) + +const ( + envCmd = "STACKLIT_SUMMARY_CMD" + envTimeout = "STACKLIT_SUMMARY_TIMEOUT" + + defaultTimeoutSec = 120 + stderrTail = 500 +) + +// Run invokes the configured agent CLI +func Run(idx *schema.Index) (string, error) { + command := []string{"claude", "-p"} + if parts := strings.Fields(os.Getenv(envCmd)); len(parts) > 0 { + command = parts + } + + n, _ := strconv.Atoi(strings.TrimSpace(os.Getenv(envTimeout))) + timeout := time.Duration(cmp.Or(max(n, 0), defaultTimeoutSec)) * time.Second + + snapshot := indexSnapshot{ + Project: idx.Project, + Tech: idx.Tech, + Modules: idx.Modules, + Dependencies: idx.Dependencies, + Entrypoints: idx.Structure.Entrypoints, + } + + userJSON, err := json.Marshal(snapshot) + if err != nil { + return "", fmt.Errorf("marshalling index snapshot: %w", err) + } + + // Most agent CLIs lack a stdin system-prompt channel, so prepend inline. + prompt := systemPrompt + "\n\n" + string(userJSON) + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + cmd := exec.CommandContext(ctx, command[0], command[1:]...) + cmd.Stdin = strings.NewReader(prompt) + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err = cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("summary CLI %q timed out after %s", command[0], timeout) + } + + if err != nil { + return "", fmt.Errorf("summary CLI %q failed: %w: %.*s", + command[0], err, stderrTail, strings.TrimSpace(stderr.String())) + } + + text := strings.TrimSpace(stdout.String()) + if text == "" { + return "", fmt.Errorf("summary CLI %q produced empty output (stderr: %.*s)", + command[0], stderrTail, strings.TrimSpace(stderr.String())) + } + + return text, nil +}