check-safety/presets.go
Alex Dunmow f968cb31d9 fix: skip gitignored .worktrees/ dirs in repo scans
Directory walkers pruned .git/vendor/node_modules but not .worktrees/, so a repo scan descended into nested git worktrees (e.g. an orchestrator worktree under cms/.worktrees/). Their Go/TS files surfaced as false positives in the standalone-plugin import check and noise in the any-usage warnings.

Add ".worktrees" to the skip set across the implicated and common walkers: proto RBAC proto-scan, standalone-plugin imports, frontend extras, go-lint, sqlc-uuid, presets.json, and plugin segmentation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:53:18 +08:00

116 lines
2.8 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"git.dev.alexdunmow.com/block/check-safety/internal/theme"
)
type presetViolation struct {
file string
message string
}
type presetJSON struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Theme theme.Theme `json:"theme"`
}
func checkPresets(root string) []presetViolation {
var violations []presetViolation
presetsFiles := findPresetsFiles(root)
for _, path := range presetsFiles {
relPath, _ := filepath.Rel(root, path)
if relPath == "" {
relPath = path
}
data, err := os.ReadFile(path)
if err != nil {
violations = append(violations, presetViolation{
file: relPath,
message: fmt.Sprintf("cannot read file: %v", err),
})
continue
}
// Strict unmarshal: catches type mismatches (e.g., int where string expected)
var presets []presetJSON
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&presets); err != nil {
// Distinguish between unknown fields (warning-worthy) and hard parse errors
if isUnknownFieldError(err) {
// Re-try without strict mode to get the actual parse result
var lenient []presetJSON
if lenientErr := json.Unmarshal(data, &lenient); lenientErr != nil {
violations = append(violations, presetViolation{
file: relPath,
message: fmt.Sprintf("invalid JSON: %v", lenientErr),
})
} else {
violations = append(violations, presetViolation{
file: relPath,
message: fmt.Sprintf("unknown fields (stale preset data): %v", err),
})
}
} else {
violations = append(violations, presetViolation{
file: relPath,
message: fmt.Sprintf("invalid JSON: %v", err),
})
}
continue
}
for i, p := range presets {
if p.ID == "" {
violations = append(violations, presetViolation{
file: relPath,
message: fmt.Sprintf("preset[%d]: missing \"id\" field", i),
})
}
if p.Name == "" {
violations = append(violations, presetViolation{
file: relPath,
message: fmt.Sprintf("preset[%d]: missing \"name\" field", i),
})
}
}
}
return violations
}
func findPresetsFiles(root string) []string {
var files []string
_ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
if info != nil && info.IsDir() {
switch info.Name() {
case ".git", ".worktrees", "vendor", "node_modules", "web", "dist":
return filepath.SkipDir
}
}
return nil
}
if info.Name() == "presets.json" {
files = append(files, path)
}
return nil
})
return files
}
func isUnknownFieldError(err error) bool {
return strings.Contains(err.Error(), "unknown field")
}