@soulbatical/tetra-dev-toolkit 1.16.2 → 1.17.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.
- package/README.md +27 -2
- package/lib/checks/security/config-rls-alignment.js +49 -4
- package/lib/checks/security/direct-supabase-client.js +2 -2
- package/lib/checks/security/mixed-db-usage.js +5 -3
- package/lib/checks/security/route-config-alignment.js +35 -6
- package/lib/checks/security/systemdb-whitelist.js +2 -1
- package/lib/config.js +7 -1
- package/lib/runner.js +64 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ LAYER 8: DB HELPERS adminDB/userDB/publicDB/systemDB enforce correct
|
|
|
58
58
|
| `tetra-audit` | Run quality/security/hygiene checks |
|
|
59
59
|
| `tetra-audit quick` | Quick critical checks (pre-commit) |
|
|
60
60
|
| `tetra-audit security` | Full security suite (12 checks) |
|
|
61
|
-
| `tetra-audit stability` | Stability suite (
|
|
61
|
+
| `tetra-audit stability` | Stability suite (16 checks) |
|
|
62
62
|
| `tetra-audit codeQuality` | Code quality suite (4 checks) |
|
|
63
63
|
| `tetra-audit supabase` | Supabase suite (3 checks) |
|
|
64
64
|
| `tetra-audit hygiene` | Repo hygiene suite (2 checks) |
|
|
@@ -119,13 +119,26 @@ Exit codes: `0` = passed, `1` = failed (CRITICAL/HIGH), `2` = error. No middle g
|
|
|
119
119
|
| `rpc-param-mismatch` | critical | TypeScript `.rpc()` calls with wrong parameter names vs SQL |
|
|
120
120
|
| `rpc-generator-origin` | high | RPC functions not generated by Tetra SQL Generator |
|
|
121
121
|
|
|
122
|
-
### Stability (
|
|
122
|
+
### Stability (16 checks)
|
|
123
123
|
|
|
124
124
|
| Check | Severity | What it catches |
|
|
125
125
|
|-------|----------|-----------------|
|
|
126
126
|
| `husky-hooks` | medium | Missing pre-commit/pre-push hooks |
|
|
127
127
|
| `ci-pipeline` | medium | Missing or incomplete CI config |
|
|
128
128
|
| `npm-audit` | high | Known vulnerabilities in dependencies |
|
|
129
|
+
| `tests` | high | Missing test framework, test files, or test scripts |
|
|
130
|
+
| `eslint-security` | high | Missing ESLint config, security plugin, or SonarJS plugin |
|
|
131
|
+
| `typescript-strict` | medium | TypeScript strict mode not enabled |
|
|
132
|
+
| `coverage-thresholds` | medium | No coverage thresholds configured |
|
|
133
|
+
| `knip` | medium | Dead code: unused files, dependencies, exports |
|
|
134
|
+
| `dependency-cruiser` | medium | Circular dependencies, architecture violations |
|
|
135
|
+
| `dependency-automation` | medium | No Dependabot or Renovate configured |
|
|
136
|
+
| `prettier` | low | No code formatter configured |
|
|
137
|
+
| `conventional-commits` | low | No commit message convention enforced |
|
|
138
|
+
| `bundle-size` | low | No bundle size monitoring |
|
|
139
|
+
| `sast` | medium | No static application security testing |
|
|
140
|
+
| `license-audit` | low | No license compliance checking |
|
|
141
|
+
| `security-layers` | high | Missing security layers (auth, rate limiting, CORS, etc.) |
|
|
129
142
|
|
|
130
143
|
### Code Quality (4 checks)
|
|
131
144
|
|
|
@@ -220,6 +233,18 @@ If any step fails, fix it before writing code. No exceptions.
|
|
|
220
233
|
|
|
221
234
|
## Changelog
|
|
222
235
|
|
|
236
|
+
### 1.16.0
|
|
237
|
+
|
|
238
|
+
**New: Full Stability Suite (16 checks)**
|
|
239
|
+
- Stability suite expanded from 3 → 16 checks via health check adapter
|
|
240
|
+
- New checks: tests, eslint-security, typescript-strict, coverage-thresholds, knip, dependency-cruiser, dependency-automation, prettier, conventional-commits, bundle-size, sast, license-audit, security-layers
|
|
241
|
+
- `tetra-audit stability` now catches missing test infrastructure, dead code, formatting, and security layer gaps
|
|
242
|
+
- Health checks (score-based) automatically adapted to runner format (pass/fail)
|
|
243
|
+
- RPC generator: SECURITY DEFINER → SECURITY INVOKER for all data RPCs
|
|
244
|
+
- Migration lint: expanded whitelist for legitimate DEFINER functions
|
|
245
|
+
- Mixed DB checker: fixed regex that matched `superadminDB` as `adminDB`
|
|
246
|
+
- Route config checker: support `@tetra-audit-ignore` directive
|
|
247
|
+
|
|
223
248
|
### 1.15.0
|
|
224
249
|
|
|
225
250
|
**New: Migration Lint + DB Push Guard**
|
|
@@ -184,6 +184,43 @@ function parseMigrations(projectRoot) {
|
|
|
184
184
|
})
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// Find PL/pgSQL loop-based CREATE POLICY patterns (DO blocks with EXECUTE format)
|
|
188
|
+
// Handles two patterns:
|
|
189
|
+
// Pattern A: FOR t IN SELECT unnest(ARRAY['table1','table2']) LOOP ... EXECUTE format('CREATE POLICY ...')
|
|
190
|
+
// Pattern B: tables TEXT[] := ARRAY['table1','table2']; FOREACH tbl IN ARRAY tables LOOP ... EXECUTE format('CREATE POLICY ...')
|
|
191
|
+
if (/DO\s+\$/.test(content) && /EXECUTE\s+format\s*\(\s*'CREATE\s+POLICY/i.test(content)) {
|
|
192
|
+
// Try unnest pattern first, then FOREACH/variable pattern
|
|
193
|
+
const arrayMatch = content.match(/unnest\s*\(\s*ARRAY\s*\[\s*'([^[\]]+)'\s*\]/i)
|
|
194
|
+
|| content.match(/(?:TEXT\[\]|text\[\])\s*:=\s*ARRAY\s*\[\s*'([^[\]]+)'\s*\]/i)
|
|
195
|
+
if (arrayMatch) {
|
|
196
|
+
const loopTables = arrayMatch[1].split(/'\s*,\s*'/).map(t => t.trim())
|
|
197
|
+
const execLines = [...content.matchAll(/EXECUTE\s+format\s*\(\s*'(CREATE\s+POLICY\s+.*?)'\s*,/gi)]
|
|
198
|
+
for (const exec of execLines) {
|
|
199
|
+
const stmt = exec[1]
|
|
200
|
+
const forOp = stmt.match(/FOR\s+(SELECT|INSERT|UPDATE|DELETE|ALL)/i)
|
|
201
|
+
const operation = forOp ? forOp[1].toUpperCase() : 'ALL'
|
|
202
|
+
const hasUsing = /\bUSING\b/i.test(stmt)
|
|
203
|
+
const hasWithCheck = /WITH\s+CHECK/i.test(stmt)
|
|
204
|
+
const condMatch = stmt.match(/(?:USING|WITH\s+CHECK)\s*\(\s*(.*?)\s*\)/i)
|
|
205
|
+
const condition = condMatch ? condMatch[1] : ''
|
|
206
|
+
|
|
207
|
+
for (const table of loopTables) {
|
|
208
|
+
if (!tables.has(table)) tables.set(table, { rlsEnabled: false, policies: [], rpcFunctions: new Map() })
|
|
209
|
+
const policyName = `${table}_${operation.toLowerCase()}_org`
|
|
210
|
+
if (!tables.get(table).policies.find(p => p.name === policyName)) {
|
|
211
|
+
tables.get(table).policies.push({
|
|
212
|
+
name: policyName,
|
|
213
|
+
operation,
|
|
214
|
+
using: hasUsing ? condition : '',
|
|
215
|
+
withCheck: hasWithCheck ? condition : '',
|
|
216
|
+
file: relFile
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
187
224
|
// Find RPC functions and their security mode
|
|
188
225
|
const funcRegex = /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+(?:public\.)?(\w+)\s*\(([\s\S]*?)\)\s*RETURNS\s+([\s\S]*?)(?:LANGUAGE|AS)/gi
|
|
189
226
|
for (const m of content.matchAll(funcRegex)) {
|
|
@@ -204,6 +241,7 @@ function parseMigrations(projectRoot) {
|
|
|
204
241
|
}
|
|
205
242
|
}
|
|
206
243
|
}
|
|
244
|
+
|
|
207
245
|
}
|
|
208
246
|
|
|
209
247
|
return tables
|
|
@@ -221,8 +259,10 @@ function findFiles(projectRoot, pattern) {
|
|
|
221
259
|
* Check if a USING clause enforces org isolation
|
|
222
260
|
*/
|
|
223
261
|
function isOrgIsolation(using) {
|
|
224
|
-
|
|
225
|
-
|
|
262
|
+
const hasOrgFunction = /auth_org_id\(\)|auth_admin_organizations\(\)/i.test(using)
|
|
263
|
+
// Legacy pattern: auth.jwt() -> 'app_metadata' ->> 'organization_id'
|
|
264
|
+
const hasLegacyJwtOrg = /auth\.jwt\(\)\s*->\s*'app_metadata'\s*->>\s*'organization_id'/i.test(using)
|
|
265
|
+
return (hasOrgFunction || hasLegacyJwtOrg) && /organization_id/i.test(using)
|
|
226
266
|
}
|
|
227
267
|
|
|
228
268
|
/**
|
|
@@ -287,6 +327,7 @@ export async function run(config, projectRoot) {
|
|
|
287
327
|
const updatePolicies = policies.filter(p => p.operation === 'UPDATE' || p.operation === 'ALL')
|
|
288
328
|
const deletePolicies = policies.filter(p => p.operation === 'DELETE' || p.operation === 'ALL')
|
|
289
329
|
|
|
330
|
+
|
|
290
331
|
// CHECK 2: Must have policies for all operations
|
|
291
332
|
const missingOps = []
|
|
292
333
|
if (selectPolicies.length === 0) missingOps.push('SELECT')
|
|
@@ -383,8 +424,12 @@ export async function run(config, projectRoot) {
|
|
|
383
424
|
}
|
|
384
425
|
}
|
|
385
426
|
|
|
386
|
-
// CHECK 4: RPC functions must be SECURITY INVOKER
|
|
427
|
+
// CHECK 4: RPC functions must be SECURITY INVOKER (unless whitelisted)
|
|
428
|
+
const definerWhitelist = config?.supabase?.securityDefinerWhitelist || []
|
|
387
429
|
for (const rpcName of cfg.rpcFunctions) {
|
|
430
|
+
// Skip if explicitly whitelisted in config
|
|
431
|
+
if (definerWhitelist.includes(rpcName)) continue
|
|
432
|
+
|
|
388
433
|
const rpcInfo = tableRls.rpcFunctions.get(rpcName)
|
|
389
434
|
if (rpcInfo && rpcInfo.securityMode === 'DEFINER') {
|
|
390
435
|
results.passed = false
|
|
@@ -394,7 +439,7 @@ export async function run(config, projectRoot) {
|
|
|
394
439
|
type: 'rpc-security-definer',
|
|
395
440
|
severity: 'critical',
|
|
396
441
|
message: `RPC function "${rpcName}" for table "${tableName}" uses SECURITY DEFINER — this BYPASSES RLS completely. Any authenticated user can see ALL data.`,
|
|
397
|
-
fix: `Change to SECURITY INVOKER or
|
|
442
|
+
fix: `Change to SECURITY INVOKER or add "${rpcName}" to supabase.securityDefinerWhitelist in .tetra-quality.json.`
|
|
398
443
|
})
|
|
399
444
|
results.summary.critical++
|
|
400
445
|
results.summary.total++
|
|
@@ -74,8 +74,8 @@ export async function run(config, projectRoot) {
|
|
|
74
74
|
summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0 }
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// Build whitelist from hardcoded + config
|
|
78
|
-
const configWhitelist = config?.
|
|
77
|
+
// Build whitelist from hardcoded + config (canonical: security.directSupabaseClientWhitelist)
|
|
78
|
+
const configWhitelist = config?.security?.directSupabaseClientWhitelist || config?.directSupabaseClientWhitelist || []
|
|
79
79
|
const extraPatterns = configWhitelist.map(p => new RegExp(p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
|
|
80
80
|
const allAllowed = [...ALLOWED_FILES, ...extraPatterns]
|
|
81
81
|
|
|
@@ -77,7 +77,8 @@ export async function run(config, projectRoot) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// Config-based ignore for controllers that legitimately mix DB helpers
|
|
80
|
-
|
|
80
|
+
// Canonical: security.mixedDbWhitelist
|
|
81
|
+
const mixedDbIgnore = config?.security?.mixedDbWhitelist || config?.mixedDbWhitelist || []
|
|
81
82
|
|
|
82
83
|
const files = await glob('**/*[Cc]ontroller*.ts', {
|
|
83
84
|
cwd: projectRoot,
|
|
@@ -157,11 +158,12 @@ export async function run(config, projectRoot) {
|
|
|
157
158
|
|
|
158
159
|
// HIGH: Multiple DB helper types in one controller
|
|
159
160
|
if (usedLevels.length > 1) {
|
|
160
|
-
// Allow: Superadmin controllers
|
|
161
|
+
// Allow: Superadmin controllers use superadminDB which internally calls systemDB
|
|
162
|
+
// So tetra sees SUPERADMIN + SYSTEM (or SUPERADMIN + ADMIN) — both are legitimate
|
|
161
163
|
const isSuperadminMixingAdmin = expectedLevel === 'SUPERADMIN' &&
|
|
162
164
|
usedLevels.length === 2 &&
|
|
163
165
|
usedLevels.includes('SUPERADMIN') &&
|
|
164
|
-
usedLevels.includes('ADMIN')
|
|
166
|
+
(usedLevels.includes('ADMIN') || usedLevels.includes('SYSTEM'))
|
|
165
167
|
|
|
166
168
|
if (!isSuperadminMixingAdmin) {
|
|
167
169
|
results.passed = false
|
|
@@ -102,6 +102,32 @@ function hasOrgAdminMiddleware(content) {
|
|
|
102
102
|
return /requireOrganizationAdmin/.test(content)
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Check if admin routes are protected by a RouteManager group-level middleware.
|
|
107
|
+
* Many projects apply auth middleware at the route group level (e.g., all /api/admin/* routes)
|
|
108
|
+
* rather than in individual route files.
|
|
109
|
+
*/
|
|
110
|
+
function hasRouteManagerGroupAuth(projectRoot) {
|
|
111
|
+
const candidates = [
|
|
112
|
+
join(projectRoot, 'backend/src/core/RouteManager.ts'),
|
|
113
|
+
join(projectRoot, 'src/core/RouteManager.ts'),
|
|
114
|
+
join(projectRoot, 'backend/src/routes/index.ts'),
|
|
115
|
+
join(projectRoot, 'src/routes/index.ts')
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
for (const file of candidates) {
|
|
119
|
+
if (!existsSync(file)) continue
|
|
120
|
+
try {
|
|
121
|
+
const content = readFileSync(file, 'utf-8')
|
|
122
|
+
// Check for group middleware pattern: prefix '/api/admin' with authenticateToken
|
|
123
|
+
if (/\/api\/admin/.test(content) && /authenticateToken/.test(content)) {
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
} catch { /* skip */ }
|
|
127
|
+
}
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
105
131
|
/**
|
|
106
132
|
* Expected route filename for a given accessLevel
|
|
107
133
|
*/
|
|
@@ -123,8 +149,11 @@ export async function run(config, projectRoot) {
|
|
|
123
149
|
details: { routesChecked: 0, violations: 0 }
|
|
124
150
|
}
|
|
125
151
|
|
|
126
|
-
//
|
|
127
|
-
const routeIgnore = config?.
|
|
152
|
+
// Canonical: security.routeConfigIgnore
|
|
153
|
+
const routeIgnore = config?.security?.routeConfigIgnore || config?.routeConfigAlignmentIgnore || []
|
|
154
|
+
|
|
155
|
+
// Detect if RouteManager applies group-level auth middleware to /api/admin/*
|
|
156
|
+
const routeManagerHasGroupAuth = hasRouteManagerGroupAuth(projectRoot)
|
|
128
157
|
|
|
129
158
|
const featureConfigs = parseFeatureConfigs(projectRoot)
|
|
130
159
|
|
|
@@ -176,8 +205,8 @@ export async function run(config, projectRoot) {
|
|
|
176
205
|
if (cfg.accessLevel === 'admin') {
|
|
177
206
|
// Route name should be adminRoutes.ts
|
|
178
207
|
if (routeName === 'adminRoutes.ts') {
|
|
179
|
-
// CRITICAL: admin route MUST have authenticateToken
|
|
180
|
-
if (!hasAuthMiddleware(content)) {
|
|
208
|
+
// CRITICAL: admin route MUST have authenticateToken (in file OR via RouteManager group)
|
|
209
|
+
if (!hasAuthMiddleware(content) && !routeManagerHasGroupAuth) {
|
|
181
210
|
results.findings.push({
|
|
182
211
|
file: relRouteFile,
|
|
183
212
|
line: 1,
|
|
@@ -192,8 +221,8 @@ export async function run(config, projectRoot) {
|
|
|
192
221
|
results.details.violations++
|
|
193
222
|
}
|
|
194
223
|
|
|
195
|
-
// CRITICAL: admin route MUST have requireOrganizationAdmin
|
|
196
|
-
if (!hasOrgAdminMiddleware(content)) {
|
|
224
|
+
// CRITICAL: admin route MUST have requireOrganizationAdmin (in file OR via RouteManager group)
|
|
225
|
+
if (!hasOrgAdminMiddleware(content) && !routeManagerHasGroupAuth) {
|
|
197
226
|
results.findings.push({
|
|
198
227
|
file: relRouteFile,
|
|
199
228
|
line: 1,
|
|
@@ -86,7 +86,8 @@ const FORBIDDEN_FILE_PATTERNS = [
|
|
|
86
86
|
const DEFAULT_MAX_WHITELIST_SIZE = 35
|
|
87
87
|
|
|
88
88
|
export async function run(config, projectRoot) {
|
|
89
|
-
|
|
89
|
+
// Canonical: security.systemDbMaxEntries
|
|
90
|
+
const MAX_WHITELIST_SIZE = config?.security?.systemDbMaxEntries || config?.systemDB?.maxWhitelistEntries || DEFAULT_MAX_WHITELIST_SIZE
|
|
90
91
|
const results = {
|
|
91
92
|
passed: true,
|
|
92
93
|
findings: [],
|
package/lib/config.js
CHANGED
|
@@ -42,7 +42,13 @@ export const DEFAULT_CONFIG = {
|
|
|
42
42
|
// Code patterns
|
|
43
43
|
checkSqlInjection: true,
|
|
44
44
|
checkEvalUsage: true,
|
|
45
|
-
checkCommandInjection: true
|
|
45
|
+
checkCommandInjection: true,
|
|
46
|
+
|
|
47
|
+
// Whitelists — project-specific overrides in .tetra-quality.json
|
|
48
|
+
directSupabaseClientWhitelist: [], // Files allowed to import createClient directly
|
|
49
|
+
mixedDbWhitelist: [], // Controllers allowed to mix DB helper types
|
|
50
|
+
routeConfigIgnore: [], // Tables to skip in route↔config alignment check
|
|
51
|
+
systemDbMaxEntries: 35 // Max systemDB whitelist entries before warning
|
|
46
52
|
},
|
|
47
53
|
|
|
48
54
|
// Stability checks
|
package/lib/runner.js
CHANGED
|
@@ -32,6 +32,56 @@ import * as rpcGeneratorOrigin from './checks/supabase/rpc-generator-origin.js'
|
|
|
32
32
|
import * as fileOrganization from './checks/hygiene/file-organization.js'
|
|
33
33
|
import * as stellaCompliance from './checks/hygiene/stella-compliance.js'
|
|
34
34
|
|
|
35
|
+
// Health checks (score-based) — wrapped as runner checks via adapter
|
|
36
|
+
import { check as checkTests } from './checks/health/tests.js'
|
|
37
|
+
import { check as checkEslintSecurity } from './checks/health/eslint-security.js'
|
|
38
|
+
import { check as checkTypescriptStrict } from './checks/health/typescript-strict.js'
|
|
39
|
+
import { check as checkCoverageThresholds } from './checks/health/coverage-thresholds.js'
|
|
40
|
+
import { check as checkKnip } from './checks/health/knip.js'
|
|
41
|
+
import { check as checkDependencyCruiser } from './checks/health/dependency-cruiser.js'
|
|
42
|
+
import { check as checkDependencyAutomation } from './checks/health/dependency-automation.js'
|
|
43
|
+
import { check as checkPrettier } from './checks/health/prettier.js'
|
|
44
|
+
import { check as checkConventionalCommits } from './checks/health/conventional-commits.js'
|
|
45
|
+
import { check as checkBundleSize } from './checks/health/bundle-size.js'
|
|
46
|
+
import { check as checkSast } from './checks/health/sast.js'
|
|
47
|
+
import { check as checkLicenseAudit } from './checks/health/license-audit.js'
|
|
48
|
+
import { check as checkSecurityLayers } from './checks/health/security-layers.js'
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adapt a health check (score-based) to the runner format (meta + run).
|
|
52
|
+
* A health check passes if it scores > 0 (has at least some infrastructure).
|
|
53
|
+
*/
|
|
54
|
+
function adaptHealthCheck(id, name, severity, healthCheckFn) {
|
|
55
|
+
return {
|
|
56
|
+
meta: { id, name, severity },
|
|
57
|
+
async run(config, projectRoot) {
|
|
58
|
+
const result = await healthCheckFn(projectRoot)
|
|
59
|
+
const passed = result.score > 0
|
|
60
|
+
const findings = []
|
|
61
|
+
|
|
62
|
+
if (!passed && result.details?.message) {
|
|
63
|
+
findings.push({
|
|
64
|
+
file: 'project',
|
|
65
|
+
line: 0,
|
|
66
|
+
severity,
|
|
67
|
+
message: result.details.message
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
passed,
|
|
73
|
+
findings,
|
|
74
|
+
summary: {
|
|
75
|
+
total: 1,
|
|
76
|
+
[severity]: passed ? 0 : 1,
|
|
77
|
+
score: result.score,
|
|
78
|
+
maxScore: result.maxScore
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
35
85
|
// Register all checks
|
|
36
86
|
const ALL_CHECKS = {
|
|
37
87
|
security: [
|
|
@@ -51,7 +101,20 @@ const ALL_CHECKS = {
|
|
|
51
101
|
stability: [
|
|
52
102
|
huskyHooks,
|
|
53
103
|
ciPipeline,
|
|
54
|
-
npmAudit
|
|
104
|
+
npmAudit,
|
|
105
|
+
adaptHealthCheck('tests', 'Test Infrastructure', 'high', checkTests),
|
|
106
|
+
adaptHealthCheck('eslint-security', 'ESLint Security Plugins', 'high', checkEslintSecurity),
|
|
107
|
+
adaptHealthCheck('typescript-strict', 'TypeScript Strictness', 'medium', checkTypescriptStrict),
|
|
108
|
+
adaptHealthCheck('coverage-thresholds', 'Test Coverage Thresholds', 'medium', checkCoverageThresholds),
|
|
109
|
+
adaptHealthCheck('knip', 'Dead Code Detection (Knip)', 'medium', checkKnip),
|
|
110
|
+
adaptHealthCheck('dependency-cruiser', 'Dependency Architecture', 'medium', checkDependencyCruiser),
|
|
111
|
+
adaptHealthCheck('dependency-automation', 'Dependency Updates (Dependabot/Renovate)', 'medium', checkDependencyAutomation),
|
|
112
|
+
adaptHealthCheck('prettier', 'Code Formatting (Prettier)', 'low', checkPrettier),
|
|
113
|
+
adaptHealthCheck('conventional-commits', 'Conventional Commits', 'low', checkConventionalCommits),
|
|
114
|
+
adaptHealthCheck('bundle-size', 'Bundle Size Monitoring', 'low', checkBundleSize),
|
|
115
|
+
adaptHealthCheck('sast', 'Static Application Security Testing', 'medium', checkSast),
|
|
116
|
+
adaptHealthCheck('license-audit', 'License Compliance', 'low', checkLicenseAudit),
|
|
117
|
+
adaptHealthCheck('security-layers', 'Security Layer Coverage', 'high', checkSecurityLayers)
|
|
55
118
|
],
|
|
56
119
|
codeQuality: [
|
|
57
120
|
apiResponseFormat,
|