sketch: add patch callback hook to warm codereview cache
When the agent patches a file, concurrently pre-compile test binaries
in the background to speed up future codereview runs.
This helps make codereview runs faster without
pre-flighting everything in the whole repository.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s2a01805b644342f9k
diff --git a/claudetool/codereview/codereview.go b/claudetool/codereview/codereview.go
index d99edff..411195a 100644
--- a/claudetool/codereview/codereview.go
+++ b/claudetool/codereview/codereview.go
@@ -10,6 +10,7 @@
"path/filepath"
"slices"
"strings"
+ "sync"
"sketch.dev/claudetool"
)
@@ -22,8 +23,11 @@
reviewed []string // history of all commits which have been reviewed
initialWorktree string // git worktree at initial commit, absolute path
// "Related files" caching
- processedChangedFileSets map[string]bool // hash of sorted changedFiles -> processed
- reportedRelatedFiles map[string]bool // file path -> reported
+ processedChangedFileSets map[string]bool // hash of sorted changedFiles -> processed
+ reportedRelatedFiles map[string]bool // file path -> reported
+ // Pre-warming of Go build/test cache
+ warmMutex sync.Mutex // protects warmedPackages map
+ warmedPackages map[string]bool // packages that have been cache warmed
}
func NewCodeReviewer(ctx context.Context, repoRoot, sketchBaseRef string) (*CodeReviewer, error) {
@@ -32,6 +36,7 @@
sketchBaseRef: sketchBaseRef,
processedChangedFileSets: make(map[string]bool),
reportedRelatedFiles: make(map[string]bool),
+ warmedPackages: make(map[string]bool),
}
if r.repoRoot == "" {
return nil, fmt.Errorf("NewCodeReviewer: repoRoot must be non-empty")
@@ -250,6 +255,9 @@
}
func (r *CodeReviewer) absPath(relPath string) string {
+ if filepath.IsAbs(relPath) {
+ return relPath
+ }
return filepath.Clean(filepath.Join(r.repoRoot, relPath))
}
diff --git a/claudetool/codereview/differential.go b/claudetool/codereview/differential.go
index 77dde85..764f000 100644
--- a/claudetool/codereview/differential.go
+++ b/claudetool/codereview/differential.go
@@ -1188,3 +1188,78 @@
}
return false
}
+
+// WarmTestCache runs 'go test -c' on relevant packages in the background
+// to warm up the Go build cache. This is intended to be called after patch
+// operations to prepare for future differential testing.
+// It uses the base commit (before state) to warm cache for packages that
+// will likely be tested during code review.
+func (r *CodeReviewer) WarmTestCache(modifiedFile string) {
+ if !r.isGoRepository() {
+ return
+ }
+ if !strings.HasSuffix(modifiedFile, ".go") {
+ return
+ }
+
+ // Worktree must be created serially
+ ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
+ if err := r.initializeInitialCommitWorktree(ctx); err != nil {
+ cancel()
+ return
+ }
+
+ go func() {
+ defer cancel()
+
+ if err := r.warmTestCache(ctx, modifiedFile); err != nil {
+ slog.DebugContext(ctx, "cache warming failed", "err", err)
+ }
+ }()
+}
+
+func (r *CodeReviewer) warmTestCache(ctx context.Context, modifiedFile string) error {
+ allPkgs, err := r.packagesForFiles(ctx, []string{r.absPath(modifiedFile)})
+ if err != nil {
+ return fmt.Errorf("failed to get packages for files: %w", err)
+ }
+ if len(allPkgs) == 0 {
+ return nil
+ }
+
+ var pkgPaths []string
+ r.warmMutex.Lock()
+ for pkgPath := range allPkgs {
+ if strings.HasSuffix(pkgPath, ".test") {
+ continue
+ }
+ if r.warmedPackages[pkgPath] {
+ continue
+ }
+ // One attempt is enough.
+ r.warmedPackages[pkgPath] = true
+ pkgPaths = append(pkgPaths, pkgPath)
+ }
+ r.warmMutex.Unlock()
+
+ if len(pkgPaths) == 0 {
+ return nil
+ }
+
+ // Avoid stressing the machine: max 2 concurrent processes.
+ args := []string{"test", "-c", "-p", "2", "-o", "/dev/null"}
+ args = append(args, pkgPaths...)
+
+ cmd := exec.CommandContext(ctx, "go", args...)
+ cmd.Dir = r.initialWorktree
+ cmd.Stdout = io.Discard
+ cmd.Stderr = io.Discard
+
+ slog.DebugContext(ctx, "warming test cache", "packages", len(pkgPaths), "worktree", r.initialWorktree)
+
+ start := time.Now()
+ // Run the command and ignore errors - this is best effort
+ err = cmd.Run()
+ slog.DebugContext(ctx, "cache warming complete", "duration", time.Since(start), "error", err)
+ return nil
+}