blob: 728294803b58c40d45ff3e1d5a06e66f5804d628 [file] [log] [blame]
Josh Bleecher Snyderd7970e62025-05-01 01:56:28 +00001package claudetool
2
3import (
4 "context"
5 "fmt"
6 "os/exec"
7 "strings"
8
9 "sketch.dev/llm/conversation"
10)
11
12// CommitMessageStyleHint explains how to find commit messages representative of this repository's style.
13func CommitMessageStyleHint(ctx context.Context, repoRoot string) (string, error) {
14 commitSHAs, err := representativeCommitSHAs(ctx, repoRoot)
15 if err != nil {
16 return "", err
17 }
18
19 buf := new(strings.Builder)
20 if len(commitSHAs) > 0 {
21 fmt.Fprint(buf, "To see representative commit messages for this repository, run:\n\n")
22 fmt.Fprintf(buf, "git show -s --format='<commit_message>%%n%%B</commit_message>' %s\n\n", strings.Join(commitSHAs, " "))
23 fmt.Fprint(buf, "Please run this EXACT command and follow their style when writing commit messages.\n")
24 }
25
26 return buf.String(), nil
27}
28
29// representativeCommitSHAs analyze recent commits and selects some representative ones.
30func representativeCommitSHAs(ctx context.Context, repoRoot string) ([]string, error) {
31 cmd := exec.Command("git", "log", "-n", "25", `--format=<commit_message hash="%H">%n%B</commit_message>`)
32 cmd.Dir = repoRoot
33 out, err := cmd.CombinedOutput()
34 if err != nil {
35 return nil, fmt.Errorf("git log failed: %w\n%s", err, out)
36 }
37 commits := strings.TrimSpace(string(out))
38 if commits == "" {
39 return nil, fmt.Errorf("no commits found in repository")
40 }
41
42 info := conversation.ToolCallInfoFromContext(ctx)
43 sub := info.Convo.SubConvo()
44
45 sub.SystemPrompt = `You are an expert Git commit analyzer.
46
47Your task is to analyze the provided commit messages and select the most representative examples that demonstrate this repository's commit style.
48
49Identify consistent patterns in:
50- Formatting conventions
51- Language and tone
52- Structure and organization
53- Special notations or tags
54
55Select up to 3 commit hashes that best exemplify the repository's commit style.
56
57Provide ONLY the commit hashes, one per line. No additional text, formatting, or commentary.
58`
59
60 resp, err := sub.SendUserTextMessage(commits)
61 if err != nil {
62 return nil, fmt.Errorf("error from Claude: %w", err)
63 }
64
65 if len(resp.Content) != 1 {
66 return nil, fmt.Errorf("unexpected response: %v", resp)
67 }
68 response := resp.Content[0].Text
69
70 var result []string
71 for line := range strings.Lines(response) {
72 line = strings.TrimSpace(line)
73 if line == "" {
74 continue
75 }
76 if isHexString(line) && (len(line) >= 7 && len(line) <= 40) {
77 result = append(result, line)
78 }
79 }
80
81 result = result[:min(len(result), 3)]
82 return result, nil
83}
84
85// isHexString reports whether a string only contains hexadecimal characters
86func isHexString(s string) bool {
87 for _, c := range s {
88 if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
89 return false
90 }
91 }
92 return true
93}