@soulbatical/tetra-dev-toolkit 1.9.0 → 1.9.2
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/bin/tetra-setup.js
CHANGED
|
@@ -122,35 +122,42 @@ async function setupHooks(options) {
|
|
|
122
122
|
execSync('npx husky init', { stdio: 'inherit' })
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
// Create pre-commit hook
|
|
125
|
+
// Create or extend pre-commit hook with tetra-audit quick
|
|
126
126
|
const preCommitPath = join(huskyDir, 'pre-commit')
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
echo "🔍 Running Tetra quality checks..."
|
|
132
|
-
|
|
133
|
-
# Run quick security checks (fast, blocks commit on critical issues)
|
|
127
|
+
const tetraAuditBlock = `
|
|
128
|
+
# Tetra quick security checks (hardcoded secrets, direct createClient, service key exposure)
|
|
129
|
+
echo "🔍 Running Tetra security checks..."
|
|
134
130
|
npx tetra-audit quick
|
|
135
131
|
if [ $? -ne 0 ]; then
|
|
136
132
|
echo ""
|
|
137
133
|
echo "❌ Security issues found! Fix before committing."
|
|
138
|
-
echo " Run 'tetra-audit' for
|
|
134
|
+
echo " Run 'npx tetra-audit security --verbose' for details."
|
|
139
135
|
exit 1
|
|
140
136
|
fi
|
|
141
|
-
|
|
142
|
-
# Run lint-staged if configured
|
|
143
|
-
if [ -f "package.json" ] && grep -q "lint-staged" package.json; then
|
|
144
|
-
npx lint-staged
|
|
145
|
-
fi
|
|
146
|
-
|
|
147
|
-
echo "✅ Pre-commit checks passed"
|
|
148
137
|
`
|
|
138
|
+
|
|
139
|
+
if (!existsSync(preCommitPath)) {
|
|
140
|
+
// No pre-commit hook — create one
|
|
141
|
+
const preCommitContent = `#!/bin/sh\n${tetraAuditBlock}\necho "✅ Pre-commit checks passed"\n`
|
|
142
|
+
writeFileSync(preCommitPath, preCommitContent)
|
|
143
|
+
execSync(`chmod +x ${preCommitPath}`)
|
|
144
|
+
console.log(' ✅ Created .husky/pre-commit with tetra-audit quick')
|
|
145
|
+
} else if (options.force) {
|
|
146
|
+
// Force overwrite
|
|
147
|
+
const preCommitContent = `#!/bin/sh\n${tetraAuditBlock}\necho "✅ Pre-commit checks passed"\n`
|
|
149
148
|
writeFileSync(preCommitPath, preCommitContent)
|
|
150
149
|
execSync(`chmod +x ${preCommitPath}`)
|
|
151
|
-
console.log(' ✅
|
|
150
|
+
console.log(' ✅ Overwrote .husky/pre-commit with tetra-audit quick')
|
|
152
151
|
} else {
|
|
153
|
-
|
|
152
|
+
// Pre-commit exists — add tetra-audit quick if missing
|
|
153
|
+
const existing = readFileSync(preCommitPath, 'utf-8')
|
|
154
|
+
if (!existing.includes('tetra-audit')) {
|
|
155
|
+
const updated = existing.trimEnd() + '\n' + tetraAuditBlock
|
|
156
|
+
writeFileSync(preCommitPath, updated)
|
|
157
|
+
console.log(' ✅ Added tetra-audit quick to existing .husky/pre-commit')
|
|
158
|
+
} else {
|
|
159
|
+
console.log(' ⏭️ .husky/pre-commit already has tetra-audit')
|
|
160
|
+
}
|
|
154
161
|
}
|
|
155
162
|
|
|
156
163
|
// Create or extend pre-push hook with hygiene check + RLS security gate
|
|
@@ -37,3 +37,4 @@ export { check as checkDependencyAutomation } from './dependency-automation.js'
|
|
|
37
37
|
export { check as checkLicenseAudit } from './license-audit.js'
|
|
38
38
|
export { check as checkSast } from './sast.js'
|
|
39
39
|
export { check as checkBundleSize } from './bundle-size.js'
|
|
40
|
+
export { check as checkSecurityLayers } from './security-layers.js'
|
|
@@ -33,6 +33,7 @@ import { check as checkDependencyAutomation } from './dependency-automation.js'
|
|
|
33
33
|
import { check as checkLicenseAudit } from './license-audit.js'
|
|
34
34
|
import { check as checkSast } from './sast.js'
|
|
35
35
|
import { check as checkBundleSize } from './bundle-size.js'
|
|
36
|
+
import { check as checkSecurityLayers } from './security-layers.js'
|
|
36
37
|
import { calculateHealthStatus } from './types.js'
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -74,7 +75,8 @@ export async function scanProjectHealth(projectPath, projectName, options = {})
|
|
|
74
75
|
checkDependencyAutomation(projectPath),
|
|
75
76
|
checkLicenseAudit(projectPath),
|
|
76
77
|
checkSast(projectPath),
|
|
77
|
-
checkBundleSize(projectPath)
|
|
78
|
+
checkBundleSize(projectPath),
|
|
79
|
+
checkSecurityLayers(projectPath)
|
|
78
80
|
])
|
|
79
81
|
|
|
80
82
|
const totalScore = checks.reduce((sum, c) => sum + c.score, 0)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check: 3-Layer Security Model
|
|
3
|
+
*
|
|
4
|
+
* Verifies the project has all 3 security layers active:
|
|
5
|
+
* Layer 1 (pre-commit): tetra-audit quick
|
|
6
|
+
* Layer 2 (pre-push): tetra-check-rls
|
|
7
|
+
* Layer 3 (build/deploy): tetra-check-rls --errors-only in railway.json
|
|
8
|
+
*
|
|
9
|
+
* Score: 0-3 (1 point per layer)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync } from 'fs'
|
|
13
|
+
import { join } from 'path'
|
|
14
|
+
import { createCheck } from './types.js'
|
|
15
|
+
|
|
16
|
+
export async function check(projectPath) {
|
|
17
|
+
const result = createCheck('security-layers', 3, {
|
|
18
|
+
layer1_precommit: false,
|
|
19
|
+
layer2_prepush: false,
|
|
20
|
+
layer3_build: false,
|
|
21
|
+
missing: []
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Layer 1: pre-commit has tetra-audit
|
|
25
|
+
const preCommitPaths = [
|
|
26
|
+
join(projectPath, '.husky', 'pre-commit'),
|
|
27
|
+
// monorepo: check parent dir too
|
|
28
|
+
join(projectPath, '..', '.husky', 'pre-commit')
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
for (const p of preCommitPaths) {
|
|
32
|
+
if (existsSync(p)) {
|
|
33
|
+
try {
|
|
34
|
+
const content = readFileSync(p, 'utf-8')
|
|
35
|
+
if (content.includes('tetra-audit')) {
|
|
36
|
+
result.details.layer1_precommit = true
|
|
37
|
+
result.score += 1
|
|
38
|
+
break
|
|
39
|
+
}
|
|
40
|
+
} catch { /* ignore read errors */ }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!result.details.layer1_precommit) {
|
|
45
|
+
result.details.missing.push('Layer 1: .husky/pre-commit missing tetra-audit quick')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Layer 2: pre-push has tetra-check-rls
|
|
49
|
+
const prePushPaths = [
|
|
50
|
+
join(projectPath, '.husky', 'pre-push'),
|
|
51
|
+
join(projectPath, '..', '.husky', 'pre-push')
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
for (const p of prePushPaths) {
|
|
55
|
+
if (existsSync(p)) {
|
|
56
|
+
try {
|
|
57
|
+
const content = readFileSync(p, 'utf-8')
|
|
58
|
+
if (content.includes('tetra-check-rls')) {
|
|
59
|
+
result.details.layer2_prepush = true
|
|
60
|
+
result.score += 1
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
} catch { /* ignore read errors */ }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!result.details.layer2_prepush) {
|
|
68
|
+
result.details.missing.push('Layer 2: .husky/pre-push missing tetra-check-rls')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Layer 3: railway.json has tetra-check-rls --errors-only
|
|
72
|
+
const railwayPaths = [
|
|
73
|
+
join(projectPath, 'railway.json'),
|
|
74
|
+
join(projectPath, '..', 'railway.json'),
|
|
75
|
+
join(projectPath, 'backend', 'railway.json')
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
for (const p of railwayPaths) {
|
|
79
|
+
if (existsSync(p)) {
|
|
80
|
+
try {
|
|
81
|
+
const content = readFileSync(p, 'utf-8')
|
|
82
|
+
if (content.includes('tetra-check-rls')) {
|
|
83
|
+
result.details.layer3_build = true
|
|
84
|
+
result.score += 1
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
} catch { /* ignore read errors */ }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!result.details.layer3_build) {
|
|
92
|
+
result.details.missing.push('Layer 3: railway.json missing tetra-check-rls --errors-only in buildCommand')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Set status
|
|
96
|
+
if (result.score === 3) {
|
|
97
|
+
result.status = 'ok'
|
|
98
|
+
} else if (result.score >= 2) {
|
|
99
|
+
result.status = 'warning'
|
|
100
|
+
} else {
|
|
101
|
+
result.status = 'error'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
result.details.fix = 'Run: npx tetra-setup hooks'
|
|
105
|
+
|
|
106
|
+
return result
|
|
107
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @typedef {'plugins'|'mcps'|'git'|'tests'|'secrets'|'quality-toolkit'|'naming-conventions'|'rls-audit'|'rpc-param-mismatch'|'typescript-strict'|'prettier'|'coverage-thresholds'|'eslint-security'|'dependency-cruiser'|'conventional-commits'|'knip'|'dependency-automation'|'license-audit'|'sast'|'bundle-size'|'gitignore'|'repo-visibility'|'vincifox-widget'|'stella-integration'|'claude-md'|'doppler-compliance'|'infrastructure-yml'|'file-organization'} HealthCheckType
|
|
8
|
+
* @typedef {'plugins'|'mcps'|'git'|'tests'|'secrets'|'quality-toolkit'|'naming-conventions'|'rls-audit'|'rpc-param-mismatch'|'typescript-strict'|'prettier'|'coverage-thresholds'|'eslint-security'|'dependency-cruiser'|'conventional-commits'|'knip'|'dependency-automation'|'license-audit'|'sast'|'bundle-size'|'gitignore'|'repo-visibility'|'vincifox-widget'|'stella-integration'|'claude-md'|'doppler-compliance'|'infrastructure-yml'|'file-organization'|'security-layers'} HealthCheckType
|
|
9
9
|
*
|
|
10
10
|
* @typedef {'ok'|'warning'|'error'} HealthStatus
|
|
11
11
|
*
|
|
@@ -66,7 +66,7 @@ export function calculateHealthStatus(checks) {
|
|
|
66
66
|
|
|
67
67
|
// Critical checks override percentage
|
|
68
68
|
if (checks.some(c =>
|
|
69
|
-
(c.type === 'secrets' || c.type === 'rls-audit' || c.type === 'rpc-param-mismatch' || c.type === 'repo-visibility') && c.status === 'error'
|
|
69
|
+
(c.type === 'secrets' || c.type === 'rls-audit' || c.type === 'rpc-param-mismatch' || c.type === 'repo-visibility' || c.type === 'security-layers') && c.status === 'error'
|
|
70
70
|
)) {
|
|
71
71
|
return 'unhealthy'
|
|
72
72
|
}
|