Skip to content

Commit 2dae4c2

Browse files
authored
feat: run --summary via agent CLI (claude -p by default) (#33)
1 parent 88537a4 commit 2dae4c2

2 files changed

Lines changed: 80 additions & 1 deletion

File tree

internal/engine/engine.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ func Run(opts Options) (*Result, error) {
253253
if !opts.Quiet {
254254
fmt.Println("[stacklit] generating AI summary...")
255255
}
256-
text, summaryErr := summary.Generate(idx)
256+
text, summaryErr := summary.Run(idx)
257257
if summaryErr != nil {
258258
fmt.Printf("[stacklit] warning: summary failed: %v\n", summaryErr)
259259
} else {

internal/summary/cli.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package summary
2+
3+
import (
4+
"bytes"
5+
"cmp"
6+
"context"
7+
"encoding/json"
8+
"fmt"
9+
"os"
10+
"os/exec"
11+
"strconv"
12+
"strings"
13+
"time"
14+
15+
"github.com/glincker/stacklit/internal/schema"
16+
)
17+
18+
const (
19+
envCmd = "STACKLIT_SUMMARY_CMD"
20+
envTimeout = "STACKLIT_SUMMARY_TIMEOUT"
21+
22+
defaultTimeoutSec = 120
23+
stderrTail = 500
24+
)
25+
26+
// Run invokes the configured agent CLI
27+
func Run(idx *schema.Index) (string, error) {
28+
command := []string{"claude", "-p"}
29+
if parts := strings.Fields(os.Getenv(envCmd)); len(parts) > 0 {
30+
command = parts
31+
}
32+
33+
n, _ := strconv.Atoi(strings.TrimSpace(os.Getenv(envTimeout)))
34+
timeout := time.Duration(cmp.Or(max(n, 0), defaultTimeoutSec)) * time.Second
35+
36+
snapshot := indexSnapshot{
37+
Project: idx.Project,
38+
Tech: idx.Tech,
39+
Modules: idx.Modules,
40+
Dependencies: idx.Dependencies,
41+
Entrypoints: idx.Structure.Entrypoints,
42+
}
43+
44+
userJSON, err := json.Marshal(snapshot)
45+
if err != nil {
46+
return "", fmt.Errorf("marshalling index snapshot: %w", err)
47+
}
48+
49+
// Most agent CLIs lack a stdin system-prompt channel, so prepend inline.
50+
prompt := systemPrompt + "\n\n" + string(userJSON)
51+
52+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
53+
defer cancel()
54+
55+
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
56+
cmd.Stdin = strings.NewReader(prompt)
57+
58+
var stdout, stderr bytes.Buffer
59+
cmd.Stdout = &stdout
60+
cmd.Stderr = &stderr
61+
62+
err = cmd.Run()
63+
if ctx.Err() == context.DeadlineExceeded {
64+
return "", fmt.Errorf("summary CLI %q timed out after %s", command[0], timeout)
65+
}
66+
67+
if err != nil {
68+
return "", fmt.Errorf("summary CLI %q failed: %w: %.*s",
69+
command[0], err, stderrTail, strings.TrimSpace(stderr.String()))
70+
}
71+
72+
text := strings.TrimSpace(stdout.String())
73+
if text == "" {
74+
return "", fmt.Errorf("summary CLI %q produced empty output (stderr: %.*s)",
75+
command[0], stderrTail, strings.TrimSpace(stderr.String()))
76+
}
77+
78+
return text, nil
79+
}

0 commit comments

Comments
 (0)