@soulbatical/tetra-dev-toolkit 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check: File Size / God File Detection
|
|
3
|
+
*
|
|
4
|
+
* Prevents "god files" — overly large source files that:
|
|
5
|
+
* - Are hard to review, test, and maintain
|
|
6
|
+
* - Indicate missing separation of concerns
|
|
7
|
+
* - Grow silently until they become unmovable
|
|
8
|
+
*
|
|
9
|
+
* Uses config.codeQuality.maxFileLines (default: 500).
|
|
10
|
+
* Scans backend + frontend source directories.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { glob } from 'glob'
|
|
14
|
+
import { readFileSync } from 'fs'
|
|
15
|
+
|
|
16
|
+
export const meta = {
|
|
17
|
+
id: 'file-size',
|
|
18
|
+
name: 'File Size / God File Detection',
|
|
19
|
+
category: 'codeQuality',
|
|
20
|
+
severity: 'high',
|
|
21
|
+
description: 'Detects source files exceeding maxFileLines — god files that need splitting'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function run(config, projectRoot) {
|
|
25
|
+
const maxLines = config.codeQuality?.maxFileLines || 500
|
|
26
|
+
|
|
27
|
+
const results = {
|
|
28
|
+
passed: true,
|
|
29
|
+
findings: [],
|
|
30
|
+
summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0 },
|
|
31
|
+
info: {
|
|
32
|
+
maxFileLines: maxLines,
|
|
33
|
+
filesScanned: 0,
|
|
34
|
+
violations: 0,
|
|
35
|
+
largest: null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Scan all TypeScript/JavaScript source files
|
|
40
|
+
const patterns = [
|
|
41
|
+
'backend/src/**/*.ts',
|
|
42
|
+
'frontend/src/**/*.ts',
|
|
43
|
+
'frontend/src/**/*.tsx',
|
|
44
|
+
'backend-mcp/src/**/*.ts',
|
|
45
|
+
'src/**/*.ts',
|
|
46
|
+
'src/**/*.tsx',
|
|
47
|
+
'bot/**/*.ts',
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
let files = []
|
|
51
|
+
for (const pattern of patterns) {
|
|
52
|
+
const found = await glob(pattern, {
|
|
53
|
+
cwd: projectRoot,
|
|
54
|
+
ignore: [
|
|
55
|
+
...(config.ignore || []),
|
|
56
|
+
'node_modules/**',
|
|
57
|
+
'**/node_modules/**',
|
|
58
|
+
'dist/**',
|
|
59
|
+
'**/dist/**',
|
|
60
|
+
'build/**',
|
|
61
|
+
'**/build/**',
|
|
62
|
+
'**/*.test.*',
|
|
63
|
+
'**/*.spec.*',
|
|
64
|
+
'**/*.d.ts',
|
|
65
|
+
'**/*.js.map',
|
|
66
|
+
'**/generated/**',
|
|
67
|
+
]
|
|
68
|
+
})
|
|
69
|
+
files.push(...found)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Deduplicate
|
|
73
|
+
files = [...new Set(files)]
|
|
74
|
+
|
|
75
|
+
if (files.length === 0) {
|
|
76
|
+
results.skipped = true
|
|
77
|
+
results.skipReason = 'No source files found'
|
|
78
|
+
return results
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let largestFile = null
|
|
82
|
+
let largestLines = 0
|
|
83
|
+
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
const filePath = `${projectRoot}/${file}`
|
|
86
|
+
let content
|
|
87
|
+
try {
|
|
88
|
+
content = readFileSync(filePath, 'utf-8')
|
|
89
|
+
} catch {
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
results.info.filesScanned++
|
|
94
|
+
|
|
95
|
+
const lineCount = content.split('\n').length
|
|
96
|
+
|
|
97
|
+
// Track largest file
|
|
98
|
+
if (lineCount > largestLines) {
|
|
99
|
+
largestLines = lineCount
|
|
100
|
+
largestFile = file
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (lineCount <= maxLines) continue
|
|
104
|
+
|
|
105
|
+
// Determine severity based on how far over the limit
|
|
106
|
+
const ratio = lineCount / maxLines
|
|
107
|
+
let severity
|
|
108
|
+
if (ratio >= 5) {
|
|
109
|
+
severity = 'critical' // 5x over limit (e.g. 2500+ lines with 500 limit)
|
|
110
|
+
} else if (ratio >= 3) {
|
|
111
|
+
severity = 'high' // 3x over limit (e.g. 1500+ lines)
|
|
112
|
+
} else if (ratio >= 2) {
|
|
113
|
+
severity = 'medium' // 2x over limit (e.g. 1000+ lines)
|
|
114
|
+
} else {
|
|
115
|
+
severity = 'low' // Just over limit
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
results.findings.push({
|
|
119
|
+
file,
|
|
120
|
+
line: 1,
|
|
121
|
+
type: 'GOD_FILE',
|
|
122
|
+
severity,
|
|
123
|
+
message: `${file} has ${lineCount} lines (max: ${maxLines}) — split into smaller modules`,
|
|
124
|
+
fix: `Break this file into focused modules of <${maxLines} lines each`
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
results.summary[severity]++
|
|
128
|
+
results.summary.total++
|
|
129
|
+
results.info.violations++
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
results.info.largest = largestFile ? { file: largestFile, lines: largestLines } : null
|
|
133
|
+
|
|
134
|
+
// Fail on critical or high findings
|
|
135
|
+
results.passed = results.findings.filter(f =>
|
|
136
|
+
f.severity === 'critical' || f.severity === 'high'
|
|
137
|
+
).length === 0
|
|
138
|
+
|
|
139
|
+
return results
|
|
140
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Quality Check: Naming Conventions (DB + Code)
|
|
3
|
+
*
|
|
4
|
+
* Wraps the health check naming-conventions as a tetra-audit check.
|
|
5
|
+
* Enforces snake_case in DB, camelCase/PascalCase in code, lowercase dirs.
|
|
6
|
+
*
|
|
7
|
+
* DB violations: table/column naming, PK/FK conventions, bool prefixes, JSON suffixes
|
|
8
|
+
* Code violations: variable/type naming, file naming, directory casing
|
|
9
|
+
*
|
|
10
|
+
* Severity: high — inconsistent naming creates confusion and bugs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { check as healthCheck } from '../health/naming-conventions.js'
|
|
14
|
+
|
|
15
|
+
export const meta = {
|
|
16
|
+
id: 'naming-conventions',
|
|
17
|
+
name: 'Naming Conventions',
|
|
18
|
+
category: 'codeQuality',
|
|
19
|
+
severity: 'high',
|
|
20
|
+
description: 'Enforces snake_case in DB schema, camelCase/PascalCase in TypeScript, lowercase directories'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function run(config, projectRoot) {
|
|
24
|
+
const result = {
|
|
25
|
+
passed: true,
|
|
26
|
+
findings: [],
|
|
27
|
+
summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0 },
|
|
28
|
+
details: {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const health = await healthCheck(projectRoot)
|
|
32
|
+
result.details = health.details
|
|
33
|
+
|
|
34
|
+
const dbViolations = health.details.database?.violations || []
|
|
35
|
+
const codeViolations = health.details.code?.violations || []
|
|
36
|
+
const dbCompliance = health.details.database?.compliancePercent ?? 100
|
|
37
|
+
const codeCompliance = health.details.code?.compliancePercent ?? 100
|
|
38
|
+
|
|
39
|
+
// DB naming violations
|
|
40
|
+
if (dbViolations.length > 0) {
|
|
41
|
+
// Separate critical (PK/FK structural) from high (naming style)
|
|
42
|
+
const structural = dbViolations.filter(v =>
|
|
43
|
+
v.includes('should be named "id"') ||
|
|
44
|
+
v.includes('should use _id suffix') ||
|
|
45
|
+
v.includes('should be "deleted_at"') ||
|
|
46
|
+
v.includes('missing created_at') ||
|
|
47
|
+
v.includes('missing updated_at') ||
|
|
48
|
+
v.includes('Inconsistent') ||
|
|
49
|
+
v.includes('Non-standard')
|
|
50
|
+
)
|
|
51
|
+
const style = dbViolations.filter(v => !structural.includes(v))
|
|
52
|
+
|
|
53
|
+
if (structural.length > 0) {
|
|
54
|
+
result.findings.push({
|
|
55
|
+
type: 'DB structural naming violations',
|
|
56
|
+
severity: 'critical',
|
|
57
|
+
message: `${structural.length} structural naming issue(s) in DB schema (PK/FK/timestamps/consistency)`,
|
|
58
|
+
files: structural.slice(0, 20)
|
|
59
|
+
})
|
|
60
|
+
result.summary.critical++
|
|
61
|
+
result.summary.total++
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (style.length > 0) {
|
|
65
|
+
result.findings.push({
|
|
66
|
+
type: 'DB naming style violations',
|
|
67
|
+
severity: 'high',
|
|
68
|
+
message: `${style.length} naming style issue(s) in DB schema (snake_case, bool prefix, JSON suffix)`,
|
|
69
|
+
files: style.slice(0, 20)
|
|
70
|
+
})
|
|
71
|
+
result.summary.high++
|
|
72
|
+
result.summary.total++
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Code naming violations
|
|
77
|
+
if (codeViolations.length > 0) {
|
|
78
|
+
const dirViolations = codeViolations.filter(v => v.includes('should be lowercase'))
|
|
79
|
+
const fileViolations = codeViolations.filter(v => v.includes('naming issue'))
|
|
80
|
+
const varViolations = codeViolations.filter(v =>
|
|
81
|
+
v.includes('should be camelCase') ||
|
|
82
|
+
v.includes('should be PascalCase') ||
|
|
83
|
+
v.includes('should not use I-prefix')
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if (dirViolations.length > 0) {
|
|
87
|
+
result.findings.push({
|
|
88
|
+
type: 'Directory naming violations',
|
|
89
|
+
severity: 'high',
|
|
90
|
+
message: `${dirViolations.length} director(ies) not lowercase`,
|
|
91
|
+
files: dirViolations.slice(0, 10)
|
|
92
|
+
})
|
|
93
|
+
result.summary.high++
|
|
94
|
+
result.summary.total++
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (fileViolations.length > 0) {
|
|
98
|
+
result.findings.push({
|
|
99
|
+
type: 'File naming violations',
|
|
100
|
+
severity: 'medium',
|
|
101
|
+
message: `${fileViolations.length} file(s) with naming issues`,
|
|
102
|
+
files: fileViolations.slice(0, 10)
|
|
103
|
+
})
|
|
104
|
+
result.summary.medium++
|
|
105
|
+
result.summary.total++
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (varViolations.length > 0) {
|
|
109
|
+
result.findings.push({
|
|
110
|
+
type: 'Variable/type naming violations',
|
|
111
|
+
severity: 'medium',
|
|
112
|
+
message: `${varViolations.length} variable(s) or type(s) with naming issues`,
|
|
113
|
+
files: varViolations.slice(0, 10)
|
|
114
|
+
})
|
|
115
|
+
result.summary.medium++
|
|
116
|
+
result.summary.total++
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Overall compliance summary
|
|
121
|
+
result.details.dbCompliance = dbCompliance
|
|
122
|
+
result.details.codeCompliance = codeCompliance
|
|
123
|
+
|
|
124
|
+
// Fail if DB compliance < 80% or code compliance < 70%, or any critical findings
|
|
125
|
+
result.passed = result.summary.critical === 0 &&
|
|
126
|
+
dbCompliance >= 80 &&
|
|
127
|
+
codeCompliance >= 70
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
}
|
package/lib/runner.js
CHANGED
|
@@ -15,6 +15,8 @@ import * as huskyHooks from './checks/stability/husky-hooks.js'
|
|
|
15
15
|
import * as ciPipeline from './checks/stability/ci-pipeline.js'
|
|
16
16
|
import * as npmAudit from './checks/stability/npm-audit.js'
|
|
17
17
|
import * as apiResponseFormat from './checks/codeQuality/api-response-format.js'
|
|
18
|
+
import * as fileSize from './checks/codeQuality/file-size.js'
|
|
19
|
+
import * as namingConventions from './checks/codeQuality/naming-conventions.js'
|
|
18
20
|
import * as gitignoreValidation from './checks/security/gitignore-validation.js'
|
|
19
21
|
import * as rlsPolicyAudit from './checks/supabase/rls-policy-audit.js'
|
|
20
22
|
import * as rpcParamMismatch from './checks/supabase/rpc-param-mismatch.js'
|
|
@@ -36,7 +38,9 @@ const ALL_CHECKS = {
|
|
|
36
38
|
npmAudit
|
|
37
39
|
],
|
|
38
40
|
codeQuality: [
|
|
39
|
-
apiResponseFormat
|
|
41
|
+
apiResponseFormat,
|
|
42
|
+
fileSize,
|
|
43
|
+
namingConventions
|
|
40
44
|
],
|
|
41
45
|
supabase: [
|
|
42
46
|
rlsPolicyAudit,
|