claudetool: add go mod tidy check to codereview

Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/claudetool/codereview/differential.go b/claudetool/codereview/differential.go
index 6aa8dfe..f358594 100644
--- a/claudetool/codereview/differential.go
+++ b/claudetool/codereview/differential.go
@@ -494,6 +494,30 @@
 	return commit == r.initialCommit
 }
 
+// requireHEADDescendantOfInitialCommit returns an error if HEAD is not a descendant of r.initialCommit.
+// This serves two purposes:
+//   - ensures we're not still on the initial commit
+//   - ensures we're not on a separate branch or upstream somewhere, which would be confusing
+func (r *CodeReviewer) requireHEADDescendantOfInitialCommit(ctx context.Context) error {
+	head, err := r.CurrentCommit(ctx)
+	if err != nil {
+		return err
+	}
+
+	// Note: Git's merge-base --is-ancestor checks strict ancestry (i.e., <), so a commit is NOT an ancestor of itself.
+	cmd := exec.CommandContext(ctx, "git", "merge-base", "--is-ancestor", r.initialCommit, head)
+	cmd.Dir = r.repoRoot
+	err = cmd.Run()
+	if err != nil {
+		if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
+			// Exit code 1 means not an ancestor
+			return fmt.Errorf("HEAD is not a descendant of the initial commit")
+		}
+		return fmt.Errorf("failed to check whether initial commit is ancestor: %w", err)
+	}
+	return nil
+}
+
 // packagesForFiles returns maps of packages related to the given files:
 // 1. directPkgs: packages that directly contain the changed files
 // 2. allPkgs: all packages that might be affected, including downstream packages that depend on the direct packages