diff --git a/check_codegen.go b/check_codegen.go index 99314eb..4084493 100644 --- a/check_codegen.go +++ b/check_codegen.go @@ -42,7 +42,37 @@ func init() { fmt.Println(" SKIP: no sqlc or buf codegen targets found") } + protoFreshness, protoFreshnessErr := checkProtoGeneratedFreshness(ctx.repoRoot) + if protoFreshnessErr != nil { + fmt.Printf(" ERROR: failed to run make proto freshness check: %v\n", protoFreshnessErr) + if protoFreshness.output != "" { + for line := range strings.SplitSeq(protoFreshness.output, "\n") { + fmt.Printf(" %s\n", line) + } + } + rep.Fail() + } else if protoFreshness.changed() { + fmt.Println(" FAIL: make proto changed generated outputs; run make proto and commit the generated files.") + fmt.Println(" before:") + printIndentedStatus(protoFreshness.beforeStatus) + fmt.Println(" after:") + printIndentedStatus(protoFreshness.afterStatus) + rep.Fail() + } else if protoFreshness.checked { + fmt.Println(" OK: make proto freshness check clean") + } + fmt.Println() }, }) } + +func printIndentedStatus(status string) { + if strings.TrimSpace(status) == "" { + fmt.Println(" (clean)") + return + } + for line := range strings.SplitSeq(status, "\n") { + fmt.Printf(" %s\n", line) + } +} diff --git a/comingsoon.go b/comingsoon.go index 5569a3e..39311b6 100644 --- a/comingsoon.go +++ b/comingsoon.go @@ -18,14 +18,14 @@ type comingSoonViolation struct { } var comingSoonAllowedFiles = map[string]bool{ - "components/pages/system-pages-list.tsx": true, // Real system page type - "components/settings/ai-action-matrix.tsx": true, // AI task for site status copy - "components/settings/site-settings-form.tsx": true, // Settings navigation text - "components/settings/site-status-section.tsx": true, // Legitimate Site Status feature UI - "lib/nav-items.ts": true, // Admin navigation metadata - "routes/admin/pages.tsx": true, // System page type enum wiring - "lib/api/gen/blockninja/v1/pages_pb.d.ts": true, // Generated proto enum SYSTEM_PAGE_TYPE_COMING_SOON - "lib/api/gen/blockninja/v1/settings_pb.d.ts": true, // Generated proto enum SITE_MODE_COMING_SOON + "components/pages/system-pages-list.tsx": true, // Real system page type + "components/settings/ai-action-matrix.tsx": true, // AI task for site status copy + "components/settings/site-settings-form.tsx": true, // Settings navigation text + "components/settings/site-status-section.tsx": true, // Legitimate Site Status feature UI + "lib/nav-items.ts": true, // Admin navigation metadata + "routes/admin/pages.tsx": true, // System page type enum wiring + "lib/api/gen/blockninja/v1/pages_pb.d.ts": true, // Generated proto enum SYSTEM_PAGE_TYPE_COMING_SOON + "lib/api/gen/blockninja/v1/settings_pb.d.ts": true, // Generated proto enum SITE_MODE_COMING_SOON } var placeholderCopyPatterns = []*regexp.Regexp{ @@ -138,6 +138,12 @@ func checkPlaceholderLanguage(roots []todoScanRoot) []comingSoonViolation { if strings.HasSuffix(path, "_test.go") || strings.HasSuffix(path, ".test.tsx") { return nil } + slashPath := filepath.ToSlash(path) + if strings.Contains(slashPath, "/generated/") || + strings.HasSuffix(slashPath, ".pb.go") || + strings.HasSuffix(slashPath, ".connect.go") { + return nil + } if strings.Contains(filepath.ToSlash(path), "cmd/check-safety/") { return nil } diff --git a/proto_rbac.go b/proto_rbac.go index 3e09343..79c41a4 100644 --- a/proto_rbac.go +++ b/proto_rbac.go @@ -28,6 +28,13 @@ type protoOwnershipViolation struct { coreProto string } +type protoFreshnessResult struct { + checked bool + beforeStatus string + afterStatus string + output string +} + var ( bufModulePathPattern = regexp.MustCompile(`^(?:-\s*)?path:\s*"?([^"\s]+)"?\s*$`) protoPackagePattern = regexp.MustCompile(`^\s*package\s+([A-Za-z0-9_.]+)\s*;\s*$`) @@ -729,6 +736,52 @@ func countRBACRoles(protoMethods []string, roleByMethod map[string]string) map[s return counts } +func checkProtoGeneratedFreshness(repoRoot string) (protoFreshnessResult, error) { + result := protoFreshnessResult{} + if !samePath(repoRoot, blockNinjaRepoRoot()) { + return result, nil + } + if !fileExists(filepath.Join(repoRoot, "Makefile")) || !fileExists(filepath.Join(repoRoot, "buf.gen.yaml")) { + return result, nil + } + + result.checked = true + before, err := protoGeneratedStatus(repoRoot) + if err != nil { + return result, err + } + result.beforeStatus = before + + output, runErr := runCommand(repoRoot, "make", "proto") + result.output = output + + after, statusErr := protoGeneratedStatus(repoRoot) + if statusErr != nil { + return result, statusErr + } + result.afterStatus = after + + if runErr != nil { + return result, fmt.Errorf("make proto failed: %w", runErr) + } + return result, nil +} + +func (r protoFreshnessResult) changed() bool { + return r.checked && r.beforeStatus != r.afterStatus +} + +func protoGeneratedStatus(repoRoot string) (string, error) { + paths := []string{ + "backend/internal/api", + "backend/internal/mcpserver/generated", + "packages/api/src", + } + args := []string{"status", "--porcelain=v1", "--untracked-files=all", "--"} + args = append(args, paths...) + return runCommand(repoRoot, "git", args...) +} + func printRoleBreakdown(roleCounts map[string]int) { orderedRoles := []string{"public", "viewer", "user", "admin", "superadmin", "cms"} printed := make(map[string]bool)