claudetool: improve commit message guidance in precommit tool
The precommit tool now:
1. Directly executes git commands to extract example commit messages
2. Displays actual commit message examples from the repository
3. Includes stronger, mandatory language about following the commit style
This improves compliance with the repository's commit message style by
providing clear examples rather than just instructions on how to view them.
Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/claudetool/pre-commit.go b/claudetool/pre-commit.go
index 7c93f0d..306c7b9 100644
--- a/claudetool/pre-commit.go
+++ b/claudetool/pre-commit.go
@@ -3,84 +3,98 @@
import (
"context"
"fmt"
+ "log/slog"
"os/exec"
"strings"
"sketch.dev/llm/conversation"
)
-// CommitMessageStyleHint explains how to find commit messages representative of this repository's style.
+// CommitMessageStyleHint provides example commit messages representative of this repository's style.
func CommitMessageStyleHint(ctx context.Context, repoRoot string) (string, error) {
- commitSHAs, err := representativeCommitSHAs(ctx, repoRoot)
+ commitSHAs, analysis, err := representativeCommitSHAs(ctx, repoRoot)
if err != nil {
return "", err
}
buf := new(strings.Builder)
- if len(commitSHAs) > 0 {
- fmt.Fprint(buf, "To see representative commit messages for this repository, run:\n\n")
- fmt.Fprintf(buf, "git show -s --format='<commit_message>%%n%%B</commit_message>' %s\n\n", strings.Join(commitSHAs, " "))
- fmt.Fprint(buf, "Please run this EXACT command and follow their style when writing commit messages.\n")
+ if len(commitSHAs) == 0 {
+ return "", nil
}
+ if analysis != "" {
+ fmt.Fprintf(buf, "<commit_message_style_analysis>%s</commit_message_style_analysis>\n\n", analysis)
+ }
+
+ cmd := exec.Command("git", "show", "-s", "--format='<commit_message_style_example>%n%B</commit_message_style_example>'", strings.Join(commitSHAs, " "))
+ cmd.Dir = repoRoot
+ out, err := cmd.CombinedOutput()
+ if err == nil {
+ fmt.Fprintf(buf, "<commit_message_style_examples>%s</commit_message_style_examples>\n\n", out)
+ } else {
+ slog.DebugContext(ctx, "failed to get commit messages", "shas", commitSHAs, "out", string(out), "err", err)
+ }
+
+ fmt.Fprint(buf, "IMPORTANT: Follow this commit message style for ALL git commits you create.\n")
return buf.String(), nil
}
// representativeCommitSHAs analyze recent commits and selects some representative ones.
-func representativeCommitSHAs(ctx context.Context, repoRoot string) ([]string, error) {
+// It returns a list of commit SHAs and the analysis text.
+func representativeCommitSHAs(ctx context.Context, repoRoot string) ([]string, string, error) {
cmd := exec.Command("git", "log", "-n", "25", `--format=<commit_message hash="%H">%n%B</commit_message>`)
cmd.Dir = repoRoot
out, err := cmd.CombinedOutput()
if err != nil {
- return nil, fmt.Errorf("git log failed: %w\n%s", err, out)
+ return nil, "", fmt.Errorf("git log failed: %w\n%s", err, out)
}
commits := strings.TrimSpace(string(out))
if commits == "" {
- return nil, fmt.Errorf("no commits found in repository")
+ return nil, "", fmt.Errorf("no commits found in repository")
}
info := conversation.ToolCallInfoFromContext(ctx)
sub := info.Convo.SubConvo()
sub.PromptCaching = false
- sub.SystemPrompt = `You are an expert Git commit analyzer.
-
-Your task is to analyze the provided commit messages and select the most representative examples that demonstrate this repository's commit style.
-
-Identify consistent patterns in:
+ sub.SystemPrompt = `Analyze the provided git commit messages to identify consistent patterns, including but not limited to:
- Formatting conventions
- Language and tone
- Structure and organization
+- Length and detail
- Special notations or tags
-Select up to 3 commit hashes that best exemplify the repository's commit style.
-
-Provide ONLY the commit hashes, one per line. No additional text, formatting, or commentary.
+First, provide a concise analysis of the predominant patterns.
+Then select up to 3 commit hashes that best exemplify the repository's commit style.
+Finally, output these selected commit hashes, one per line, without commentary.
`
resp, err := sub.SendUserTextMessage(commits)
if err != nil {
- return nil, fmt.Errorf("error from Claude: %w", err)
+ return nil, "", fmt.Errorf("error from Claude: %w", err)
}
if len(resp.Content) != 1 {
- return nil, fmt.Errorf("unexpected response: %v", resp)
+ return nil, "", fmt.Errorf("unexpected response: %v", resp)
}
response := resp.Content[0].Text
+ // Split into analysis and commit hashes
+ var analysisLines []string
var result []string
for line := range strings.Lines(response) {
line = strings.TrimSpace(line)
- if line == "" {
- continue
- }
if isHexString(line) && (len(line) >= 7 && len(line) <= 40) {
result = append(result, line)
+ } else {
+ analysisLines = append(analysisLines, line)
}
}
+ analysis := strings.Join(analysisLines, "\n")
+
result = result[:min(len(result), 3)]
- return result, nil
+ return result, analysis, nil
}
// isHexString reports whether a string only contains hexadecimal characters