Skip to content

Commit 7514cb4

Browse files
committed
Tooling: Add allowlist to file-length check
- `file-length-allowlist.json` maps paths to accepted line counts - Files at or below their allowlisted count are silently suppressed - Files that grow past their allowlist are reported with both counts - New long files not in the allowlist are reported normally - Missing allowlist file = backwards-compatible (all files reported) - Added 31 current long files as the initial allowlist baseline
1 parent 315609a commit 7514cb4

4 files changed

Lines changed: 201 additions & 5 deletions

File tree

scripts/check/CLAUDE.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ go run ./scripts/check --only-freestyle
9595
| `checks/desktop-rust-*.go` | One file per Rust check |
9696
| `checks/desktop-svelte-*.go` | One file per Svelte/TS check |
9797
| `checks/website-*.go`, `checks/api-server-*.go`, `checks/scripts-go-*.go` | One file per check |
98-
| `checks/file-length.go` | Informational file-length scanner (warn-only, never fails) |
98+
| `checks/file-length.go` | Informational file-length scanner (warn-only, never fails). Supports an allowlist. |
99+
| `checks/file-length-allowlist.json` | Allowlist for file-length check: `{ "files": { "path": lineCount } }`. Files at or below their allowlisted count are suppressed. |
99100

100101
## Key patterns
101102

@@ -125,6 +126,13 @@ full binary path. Used for staticcheck, nilaway, etc.
125126
result (pass/fail/skip/blocked), and optional counts (total, issues, changes). `CheckResult` has `Total`, `Issues`,
126127
`Changes` fields (`-1` = N/A, rendered as `N/A` in CSV). Disabled by `--no-log` or `--ci`. Implementation in `stats.go`.
127128

129+
**File-length allowlist:** `checks/file-length-allowlist.json` maps relative paths to accepted line counts. Files at or
130+
below their allowlisted count are silently suppressed. Files that grow beyond their allowlisted count are reported with
131+
both the current and allowed line counts. New files not in the allowlist are reported normally. When the allowlist
132+
suppresses all long files, the check shows "No new long files (N allowlisted)". If the allowlist file is missing, all
133+
long files are reported (backwards-compatible). To allowlist a file, add `"relative/path": lineCount` to the `files`
134+
object.
135+
128136
## Check definition shape
129137

130138
```go
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"$comment": "Files listed here are exempt from the file-length warning up to the given line count. If a file grows beyond its allowlisted count, it will be reported. Remove entries as you shrink files.",
3+
"files": {
4+
"apps/desktop/src-tauri/src/ai/manager.rs": 988,
5+
"apps/desktop/src-tauri/src/file_system/volume/friendly_error.rs": 1136,
6+
"apps/desktop/src-tauri/src/file_system/volume/mtp.rs": 879,
7+
"apps/desktop/src-tauri/src/file_system/volume/smb.rs": 1802,
8+
"apps/desktop/src-tauri/src/file_system/write_operations/copy.rs": 825,
9+
"apps/desktop/src-tauri/src/file_system/write_operations/volume_copy.rs": 1122,
10+
"apps/desktop/src-tauri/src/indexing/aggregator.rs": 873,
11+
"apps/desktop/src-tauri/src/indexing/event_loop.rs": 1683,
12+
"apps/desktop/src-tauri/src/indexing/mod.rs": 1072,
13+
"apps/desktop/src-tauri/src/indexing/reconciler.rs": 2068,
14+
"apps/desktop/src-tauri/src/indexing/scanner.rs": 1026,
15+
"apps/desktop/src-tauri/src/indexing/store.rs": 2142,
16+
"apps/desktop/src-tauri/src/indexing/writer.rs": 2295,
17+
"apps/desktop/src-tauri/src/lib.rs": 1069,
18+
"apps/desktop/src-tauri/src/mcp/resources.rs": 834,
19+
"apps/desktop/src-tauri/src/mcp/tools.rs": 846,
20+
"apps/desktop/src-tauri/src/menu/mod.rs": 1000,
21+
"apps/desktop/src-tauri/src/mtp/connection/mod.rs": 923,
22+
"apps/desktop/src-tauri/src/network/manual_servers.rs": 1054,
23+
"apps/desktop/src-tauri/src/search/engine.rs": 1298,
24+
"apps/desktop/src-tauri/src/volumes/mod.rs": 986,
25+
"apps/desktop/src-tauri/src/volumes_linux/mod.rs": 853,
26+
"apps/desktop/src/lib/file-explorer/navigation/VolumeBreadcrumb.svelte": 1087,
27+
"apps/desktop/src/lib/file-explorer/pane/DualPaneExplorer.svelte": 2450,
28+
"apps/desktop/src/lib/file-explorer/pane/FilePane.svelte": 1965,
29+
"apps/desktop/src/lib/file-operations/transfer/TransferProgressDialog.svelte": 1265,
30+
"apps/desktop/src/lib/settings/sections/KeyboardShortcutsSection.svelte": 817,
31+
"apps/desktop/src/lib/settings/settings-registry.ts": 911,
32+
"apps/desktop/src/routes/viewer/+page.svelte": 968,
33+
"apps/desktop/test/e2e-playwright/mtp.spec.ts": 976,
34+
"scripts/check/checks/desktop-rust-cfg-gate_test.go": 976
35+
}
36+
}

scripts/check/checks/file-length.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package checks
22

33
import (
44
"bufio"
5+
"encoding/json"
56
"fmt"
67
"os"
78
"path/filepath"
@@ -44,10 +45,31 @@ type longFile struct {
4445
sizeBytes int64
4546
}
4647

48+
// loadFileLengthAllowlist reads the allowlist JSON from the checks directory.
49+
// Returns a map of relative path → allowed line count.
50+
func loadFileLengthAllowlist(rootDir string) map[string]int {
51+
// The allowlist lives next to the check source files
52+
allowlistPath := filepath.Join(rootDir, "scripts", "check", "checks", "file-length-allowlist.json")
53+
data, err := os.ReadFile(allowlistPath)
54+
if err != nil {
55+
return nil
56+
}
57+
var raw struct {
58+
Files map[string]int `json:"files"`
59+
}
60+
if err := json.Unmarshal(data, &raw); err != nil {
61+
return nil
62+
}
63+
return raw.Files
64+
}
65+
4766
// RunFileLength scans the repo for source files exceeding the line count threshold.
67+
// Files in the allowlist are suppressed if at or below their allowlisted line count.
4868
// Always succeeds — reports long files as a warning, never fails.
4969
func RunFileLength(ctx *CheckContext) (CheckResult, error) {
70+
allowlist := loadFileLengthAllowlist(ctx.RootDir)
5071
var longFiles []longFile
72+
allowlistedCount := 0
5173

5274
err := filepath.WalkDir(ctx.RootDir, func(path string, d os.DirEntry, err error) error {
5375
if err != nil {
@@ -72,11 +94,18 @@ func RunFileLength(ctx *CheckContext) (CheckResult, error) {
7294
}
7395

7496
if lineCount >= fileLengthWarnLines {
97+
relPath, _ := filepath.Rel(ctx.RootDir, path)
98+
99+
// Check allowlist: suppress if at or below the allowlisted count
100+
if allowedLines, ok := allowlist[relPath]; ok && lineCount <= allowedLines {
101+
allowlistedCount++
102+
return nil
103+
}
104+
75105
info, err := d.Info()
76106
if err != nil {
77107
return nil
78108
}
79-
relPath, _ := filepath.Rel(ctx.RootDir, path)
80109
longFiles = append(longFiles, longFile{
81110
relPath: relPath,
82111
lines: lineCount,
@@ -91,6 +120,9 @@ func RunFileLength(ctx *CheckContext) (CheckResult, error) {
91120
}
92121

93122
if len(longFiles) == 0 {
123+
if allowlistedCount > 0 {
124+
return Success(fmt.Sprintf("No new long files (%d allowlisted)", allowlistedCount)), nil
125+
}
94126
return Success("All files under threshold"), nil
95127
}
96128

@@ -109,13 +141,23 @@ func RunFileLength(ctx *CheckContext) (CheckResult, error) {
109141
color = ansiRed
110142
}
111143

144+
// Show if this file exceeded its allowlisted count
145+
if allowedLines, ok := allowlist[f.relPath]; ok {
146+
detail = fmt.Sprintf("(%d lines, allowlist: %d, %d kB, ~%s tokens)", f.lines, allowedLines, sizeKB, tokenStr)
147+
}
148+
112149
sb.WriteString(fmt.Sprintf(" - %s %s%s%s\n", f.relPath, color, detail, ansiReset))
113150
}
114151

115-
msg := fmt.Sprintf("%d %s over %d lines:\n%s",
152+
suffix := ""
153+
if allowlistedCount > 0 {
154+
suffix = fmt.Sprintf(" (%d allowlisted)", allowlistedCount)
155+
}
156+
msg := fmt.Sprintf("%d new %s over %d lines%s:\n%s",
116157
len(longFiles),
117158
Pluralize(len(longFiles), "file", "files"),
118159
fileLengthWarnLines,
160+
suffix,
119161
strings.TrimRight(sb.String(), "\n"),
120162
)
121163

scripts/check/checks/file-length_test.go

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package checks
22

33
import (
4+
"fmt"
45
"os"
56
"path/filepath"
67
"strings"
@@ -227,7 +228,116 @@ func TestRunFileLength_MessageFormat(t *testing.T) {
227228
if !strings.Contains(result.Message, "~1k tokens") {
228229
t.Errorf("expected '~1k tokens' in message, got: %s", result.Message)
229230
}
230-
if !strings.Contains(result.Message, "1 file over 800 lines") {
231-
t.Errorf("expected '1 file over 800 lines' in summary, got: %s", result.Message)
231+
if !strings.Contains(result.Message, "1 new file over 800 lines") {
232+
t.Errorf("expected '1 new file over 800 lines' in summary, got: %s", result.Message)
233+
}
234+
}
235+
236+
func writeAllowlist(t *testing.T, dir string, files map[string]int) {
237+
t.Helper()
238+
checksDir := filepath.Join(dir, "scripts", "check", "checks")
239+
if err := os.MkdirAll(checksDir, 0755); err != nil {
240+
t.Fatal(err)
241+
}
242+
// Build JSON manually to keep it simple
243+
var sb strings.Builder
244+
sb.WriteString(`{"files":{`)
245+
first := true
246+
for path, lines := range files {
247+
if !first {
248+
sb.WriteString(",")
249+
}
250+
sb.WriteString(fmt.Sprintf(`"%s":%d`, path, lines))
251+
first = false
252+
}
253+
sb.WriteString("}}")
254+
if err := os.WriteFile(filepath.Join(checksDir, "file-length-allowlist.json"), []byte(sb.String()), 0644); err != nil {
255+
t.Fatal(err)
256+
}
257+
}
258+
259+
func TestRunFileLength_AllowlistSuppresses(t *testing.T) {
260+
tmp := t.TempDir()
261+
262+
// Create a long file
263+
path := filepath.Join(tmp, "big.go")
264+
if err := os.WriteFile(path, []byte(strings.Repeat("line\n", 900)), 0644); err != nil {
265+
t.Fatal(err)
266+
}
267+
268+
// Allowlist it at 900 lines
269+
writeAllowlist(t, tmp, map[string]int{"big.go": 900})
270+
271+
ctx := &CheckContext{RootDir: tmp}
272+
result, err := RunFileLength(ctx)
273+
if err != nil {
274+
t.Fatal(err)
275+
}
276+
if result.Code != ResultSuccess {
277+
t.Errorf("expected success (file is allowlisted), got code %d: %s", result.Code, result.Message)
278+
}
279+
if !strings.Contains(result.Message, "1 allowlisted") {
280+
t.Errorf("expected '1 allowlisted' in message, got: %s", result.Message)
281+
}
282+
}
283+
284+
func TestRunFileLength_AllowlistExceeded(t *testing.T) {
285+
tmp := t.TempDir()
286+
287+
// Create a file that exceeds its allowlist
288+
path := filepath.Join(tmp, "grew.go")
289+
if err := os.WriteFile(path, []byte(strings.Repeat("line\n", 950)), 0644); err != nil {
290+
t.Fatal(err)
291+
}
292+
293+
// Allowlist it at 900 (but it's 950 now)
294+
writeAllowlist(t, tmp, map[string]int{"grew.go": 900})
295+
296+
ctx := &CheckContext{RootDir: tmp}
297+
result, err := RunFileLength(ctx)
298+
if err != nil {
299+
t.Fatal(err)
300+
}
301+
if result.Code != ResultWarning {
302+
t.Errorf("expected warning (file exceeded allowlist), got code %d", result.Code)
303+
}
304+
if !strings.Contains(result.Message, "grew.go") {
305+
t.Errorf("expected 'grew.go' in message, got: %s", result.Message)
306+
}
307+
if !strings.Contains(result.Message, "allowlist: 900") {
308+
t.Errorf("expected 'allowlist: 900' in message, got: %s", result.Message)
309+
}
310+
}
311+
312+
func TestRunFileLength_NewFileNotInAllowlist(t *testing.T) {
313+
tmp := t.TempDir()
314+
315+
// Create a long file NOT in the allowlist
316+
path := filepath.Join(tmp, "new.go")
317+
if err := os.WriteFile(path, []byte(strings.Repeat("line\n", 850)), 0644); err != nil {
318+
t.Fatal(err)
319+
}
320+
321+
// Empty allowlist
322+
writeAllowlist(t, tmp, map[string]int{})
323+
324+
ctx := &CheckContext{RootDir: tmp}
325+
result, err := RunFileLength(ctx)
326+
if err != nil {
327+
t.Fatal(err)
328+
}
329+
if result.Code != ResultWarning {
330+
t.Errorf("expected warning (new file not allowlisted), got code %d", result.Code)
331+
}
332+
if !strings.Contains(result.Message, "new.go") {
333+
t.Errorf("expected 'new.go' in message, got: %s", result.Message)
334+
}
335+
}
336+
337+
func TestLoadFileLengthAllowlist_Missing(t *testing.T) {
338+
tmp := t.TempDir()
339+
result := loadFileLengthAllowlist(tmp)
340+
if result != nil {
341+
t.Errorf("expected nil for missing allowlist, got %v", result)
232342
}
233343
}

0 commit comments

Comments
 (0)