@soulbatical/tetra-dev-toolkit 1.3.3 → 1.4.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,199 @@
1
+ /**
2
+ * Health Check: Test Coverage Thresholds
3
+ *
4
+ * Checks that test coverage thresholds are configured and enforced.
5
+ * Score: up to 3 points:
6
+ * +1 for coverage config in test framework (jest/vitest)
7
+ * +1 for threshold values defined (statements/branches/lines)
8
+ * +1 for coverage script in package.json or CI enforcement
9
+ */
10
+
11
+ import { existsSync, readFileSync } from 'fs'
12
+ import { join } from 'path'
13
+ import { createCheck } from './types.js'
14
+
15
+ export async function check(projectPath) {
16
+ const result = createCheck('coverage-thresholds', 3, {
17
+ framework: null,
18
+ hasCoverageConfig: false,
19
+ hasThresholds: false,
20
+ thresholds: null,
21
+ hasCoverageScript: false,
22
+ coverageScript: null,
23
+ hasCiCoverage: false,
24
+ message: ''
25
+ })
26
+
27
+ // Check Jest configs
28
+ const jestConfigs = [
29
+ 'jest.config.js',
30
+ 'jest.config.ts',
31
+ 'jest.config.cjs',
32
+ 'jest.config.mjs',
33
+ 'backend/jest.config.js',
34
+ 'backend/jest.config.ts'
35
+ ]
36
+
37
+ for (const config of jestConfigs) {
38
+ const configPath = join(projectPath, config)
39
+ if (!existsSync(configPath)) continue
40
+
41
+ try {
42
+ const content = readFileSync(configPath, 'utf-8')
43
+ result.details.framework = 'jest'
44
+
45
+ // Check for coverage configuration
46
+ if (content.includes('collectCoverage') || content.includes('coverageDirectory') ||
47
+ content.includes('coverageReporters') || content.includes('collectCoverageFrom')) {
48
+ result.details.hasCoverageConfig = true
49
+ result.score += 1
50
+ }
51
+
52
+ // Check for thresholds
53
+ if (content.includes('coverageThreshold')) {
54
+ result.details.hasThresholds = true
55
+ result.score += 1
56
+
57
+ // Try to extract threshold values
58
+ const thresholdMatch = content.match(/coverageThreshold\s*:\s*\{[\s\S]*?global\s*:\s*\{([^}]+)\}/)
59
+ if (thresholdMatch) {
60
+ result.details.thresholds = thresholdMatch[1].trim()
61
+ }
62
+ }
63
+ } catch { /* ignore */ }
64
+ break
65
+ }
66
+
67
+ // Check Vitest configs
68
+ if (!result.details.framework) {
69
+ const vitestConfigs = [
70
+ 'vitest.config.ts',
71
+ 'vitest.config.js',
72
+ 'vitest.config.mjs',
73
+ 'vite.config.ts',
74
+ 'vite.config.js',
75
+ 'backend/vitest.config.ts',
76
+ 'backend/vite.config.ts'
77
+ ]
78
+
79
+ for (const config of vitestConfigs) {
80
+ const configPath = join(projectPath, config)
81
+ if (!existsSync(configPath)) continue
82
+
83
+ try {
84
+ const content = readFileSync(configPath, 'utf-8')
85
+
86
+ // Only count as vitest if it has test config
87
+ if (!content.includes('test')) continue
88
+
89
+ result.details.framework = 'vitest'
90
+
91
+ if (content.includes('coverage')) {
92
+ result.details.hasCoverageConfig = true
93
+ result.score += 1
94
+ }
95
+
96
+ if (content.includes('thresholds') || content.includes('threshold')) {
97
+ result.details.hasThresholds = true
98
+ result.score += 1
99
+ }
100
+ } catch { /* ignore */ }
101
+ break
102
+ }
103
+ }
104
+
105
+ // Also check package.json for jest coverage config
106
+ if (!result.details.hasCoverageConfig) {
107
+ const pkgPath = join(projectPath, 'package.json')
108
+ if (existsSync(pkgPath)) {
109
+ try {
110
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
111
+ if (pkg.jest) {
112
+ result.details.framework = result.details.framework || 'jest'
113
+ if (pkg.jest.collectCoverage || pkg.jest.coverageReporters) {
114
+ result.details.hasCoverageConfig = true
115
+ result.score += 1
116
+ }
117
+ if (pkg.jest.coverageThreshold) {
118
+ result.details.hasThresholds = true
119
+ result.score += 1
120
+ const global = pkg.jest.coverageThreshold.global
121
+ if (global) {
122
+ result.details.thresholds = global
123
+ }
124
+ }
125
+ }
126
+ } catch { /* ignore */ }
127
+ }
128
+ }
129
+
130
+ // Check for coverage script
131
+ const packageLocations = [
132
+ 'package.json',
133
+ 'backend/package.json',
134
+ 'frontend/package.json'
135
+ ]
136
+
137
+ for (const loc of packageLocations) {
138
+ const pkgPath = join(projectPath, loc)
139
+ if (!existsSync(pkgPath)) continue
140
+
141
+ try {
142
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
143
+ const scripts = pkg.scripts || {}
144
+
145
+ for (const [name, cmd] of Object.entries(scripts)) {
146
+ if (typeof cmd !== 'string') continue
147
+ if (name.includes('coverage') || cmd.includes('--coverage') ||
148
+ cmd.includes('--coverageThreshold')) {
149
+ result.details.hasCoverageScript = true
150
+ result.details.coverageScript = `${loc} → ${name}`
151
+ break
152
+ }
153
+ }
154
+ if (result.details.hasCoverageScript) break
155
+ } catch { /* ignore */ }
156
+ }
157
+
158
+ // Check CI workflows for coverage enforcement
159
+ const ciFiles = [
160
+ '.github/workflows/quality.yml',
161
+ '.github/workflows/test.yml',
162
+ '.github/workflows/ci.yml',
163
+ '.github/workflows/admin-mode-tests.yml'
164
+ ]
165
+
166
+ for (const ci of ciFiles) {
167
+ const ciPath = join(projectPath, ci)
168
+ if (!existsSync(ciPath)) continue
169
+
170
+ try {
171
+ const content = readFileSync(ciPath, 'utf-8')
172
+ if (content.includes('coverage') || content.includes('--coverageThreshold')) {
173
+ result.details.hasCiCoverage = true
174
+ break
175
+ }
176
+ } catch { /* ignore */ }
177
+ }
178
+
179
+ if (result.details.hasCoverageScript || result.details.hasCiCoverage) {
180
+ result.score += 1
181
+ }
182
+
183
+ result.score = Math.min(result.score, result.maxScore)
184
+
185
+ // Determine status
186
+ if (result.score === 0) {
187
+ result.status = result.details.framework ? 'warning' : 'error'
188
+ result.details.message = result.details.framework
189
+ ? `${result.details.framework} detected but no coverage configuration`
190
+ : 'No test framework or coverage configuration detected'
191
+ } else if (result.score < 2) {
192
+ result.status = 'warning'
193
+ result.details.message = 'Coverage partially configured — add thresholds for enforcement'
194
+ } else {
195
+ result.details.message = `Coverage well-configured (${result.details.framework})`
196
+ }
197
+
198
+ return result
199
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Health Check: Dependency Cruiser
3
+ *
4
+ * Checks for dependency analysis and circular dependency prevention.
5
+ * Score: up to 2 points:
6
+ * +1 for dependency-cruiser config file exists
7
+ * +1 for no-circular rule defined or dep:check script
8
+ */
9
+
10
+ import { existsSync, readFileSync } from 'fs'
11
+ import { join } from 'path'
12
+ import { createCheck } from './types.js'
13
+
14
+ export async function check(projectPath) {
15
+ const result = createCheck('dependency-cruiser', 2, {
16
+ configFound: false,
17
+ configPath: null,
18
+ hasNoCircularRule: false,
19
+ hasCheckScript: false,
20
+ checkScript: null,
21
+ installed: false,
22
+ message: ''
23
+ })
24
+
25
+ // Check for dependency-cruiser config files
26
+ const depCruiserConfigs = [
27
+ '.dependency-cruiser.js',
28
+ '.dependency-cruiser.cjs',
29
+ '.dependency-cruiser.mjs',
30
+ '.dependency-cruiser.json',
31
+ 'dependency-cruiser.config.js',
32
+ 'dependency-cruiser.config.cjs',
33
+ // Sub-packages
34
+ 'backend/.dependency-cruiser.js',
35
+ 'backend/.dependency-cruiser.cjs'
36
+ ]
37
+
38
+ let configContent = null
39
+
40
+ for (const config of depCruiserConfigs) {
41
+ const configPath = join(projectPath, config)
42
+ if (!existsSync(configPath)) continue
43
+
44
+ result.details.configFound = true
45
+ result.details.configPath = config
46
+ result.score += 1
47
+
48
+ try {
49
+ configContent = readFileSync(configPath, 'utf-8')
50
+ } catch { /* ignore */ }
51
+ break
52
+ }
53
+
54
+ // Check for dependency-cruiser in dependencies
55
+ const packageLocations = [
56
+ 'package.json',
57
+ 'backend/package.json'
58
+ ]
59
+
60
+ for (const loc of packageLocations) {
61
+ const pkgPath = join(projectPath, loc)
62
+ if (!existsSync(pkgPath)) continue
63
+
64
+ try {
65
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
66
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
67
+
68
+ if (allDeps['dependency-cruiser']) {
69
+ result.details.installed = true
70
+ }
71
+
72
+ // Check for dep check scripts
73
+ const scripts = pkg.scripts || {}
74
+ for (const [name, cmd] of Object.entries(scripts)) {
75
+ if (typeof cmd !== 'string') continue
76
+ if (cmd.includes('dependency-cruiser') || cmd.includes('depcruise') ||
77
+ name === 'check:deps' || name === 'check:circular') {
78
+ result.details.hasCheckScript = true
79
+ result.details.checkScript = `${loc} → ${name}`
80
+ break
81
+ }
82
+ }
83
+ } catch { /* ignore */ }
84
+ }
85
+
86
+ // Check config content for no-circular rule
87
+ if (configContent) {
88
+ if (configContent.includes('no-circular') || configContent.includes('circular')) {
89
+ result.details.hasNoCircularRule = true
90
+ }
91
+ }
92
+
93
+ if (result.details.hasNoCircularRule || result.details.hasCheckScript) {
94
+ result.score += 1
95
+ }
96
+
97
+ result.score = Math.min(result.score, result.maxScore)
98
+
99
+ // Determine status
100
+ if (result.score === 0) {
101
+ result.status = 'warning'
102
+ result.details.message = 'No dependency analysis configured — circular dependencies go undetected'
103
+ } else if (result.score < 2) {
104
+ result.status = 'warning'
105
+ result.details.message = result.details.configFound
106
+ ? 'Dependency cruiser configured but no circular dependency rule'
107
+ : 'Dependency check script found but no config file'
108
+ } else {
109
+ result.details.message = 'Dependency analysis well-configured'
110
+ }
111
+
112
+ return result
113
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Health Check: ESLint Security Plugins
3
+ *
4
+ * Checks for security-focused ESLint plugins.
5
+ * Score: up to 3 points:
6
+ * +1 for eslint-plugin-security installed
7
+ * +1 for eslint-plugin-sonarjs installed
8
+ * +1 for custom security rules (no-restricted-imports/syntax for dangerous patterns)
9
+ */
10
+
11
+ import { existsSync, readFileSync } from 'fs'
12
+ import { join } from 'path'
13
+ import { createCheck } from './types.js'
14
+
15
+ export async function check(projectPath) {
16
+ const result = createCheck('eslint-security', 3, {
17
+ eslintConfigFound: false,
18
+ eslintConfigPath: null,
19
+ hasSecurityPlugin: false,
20
+ hasSonarPlugin: false,
21
+ hasCustomSecurityRules: false,
22
+ securityPlugins: [],
23
+ message: ''
24
+ })
25
+
26
+ // Check if ESLint is configured at all
27
+ const eslintConfigs = [
28
+ '.eslintrc.js',
29
+ '.eslintrc.cjs',
30
+ '.eslintrc.json',
31
+ '.eslintrc.yml',
32
+ '.eslintrc.yaml',
33
+ '.eslintrc',
34
+ 'eslint.config.js',
35
+ 'eslint.config.cjs',
36
+ 'eslint.config.mjs',
37
+ 'eslint.config.ts',
38
+ // Sub-packages
39
+ 'backend/.eslintrc.js',
40
+ 'backend/.eslintrc.json',
41
+ 'backend/eslint.config.js',
42
+ 'backend/eslint.config.mjs',
43
+ 'frontend/.eslintrc.js',
44
+ 'frontend/.eslintrc.json',
45
+ 'frontend/eslint.config.js',
46
+ 'frontend/eslint.config.mjs'
47
+ ]
48
+
49
+ let eslintContent = null
50
+
51
+ for (const config of eslintConfigs) {
52
+ const configPath = join(projectPath, config)
53
+ if (!existsSync(configPath)) continue
54
+
55
+ result.details.eslintConfigFound = true
56
+ result.details.eslintConfigPath = config
57
+
58
+ try {
59
+ eslintContent = readFileSync(configPath, 'utf-8')
60
+ } catch { /* ignore */ }
61
+ break
62
+ }
63
+
64
+ if (!result.details.eslintConfigFound) {
65
+ result.status = 'error'
66
+ result.details.message = 'No ESLint configuration found'
67
+ return result
68
+ }
69
+
70
+ // Check package.json for security plugins in dependencies
71
+ const packageLocations = [
72
+ 'package.json',
73
+ 'backend/package.json',
74
+ 'frontend/package.json'
75
+ ]
76
+
77
+ const allDeps = {}
78
+ for (const loc of packageLocations) {
79
+ const pkgPath = join(projectPath, loc)
80
+ if (!existsSync(pkgPath)) continue
81
+
82
+ try {
83
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
84
+ Object.assign(allDeps, pkg.dependencies || {}, pkg.devDependencies || {})
85
+ } catch { /* ignore */ }
86
+ }
87
+
88
+ // Check for security plugins
89
+ const securityPlugins = [
90
+ 'eslint-plugin-security',
91
+ '@eslint/plugin-security',
92
+ 'eslint-plugin-no-secrets'
93
+ ]
94
+ for (const plugin of securityPlugins) {
95
+ if (allDeps[plugin]) {
96
+ result.details.hasSecurityPlugin = true
97
+ result.details.securityPlugins.push(plugin)
98
+ }
99
+ }
100
+
101
+ // Also check ESLint config content for security plugin references
102
+ if (eslintContent) {
103
+ if (eslintContent.includes('plugin:security') || eslintContent.includes("'security'") ||
104
+ eslintContent.includes('"security"') || eslintContent.includes('eslint-plugin-security')) {
105
+ result.details.hasSecurityPlugin = true
106
+ if (!result.details.securityPlugins.includes('eslint-plugin-security')) {
107
+ result.details.securityPlugins.push('eslint-plugin-security (in config)')
108
+ }
109
+ }
110
+ }
111
+
112
+ if (result.details.hasSecurityPlugin) {
113
+ result.score += 1
114
+ }
115
+
116
+ // Check for SonarJS plugin
117
+ const sonarPlugins = [
118
+ 'eslint-plugin-sonarjs',
119
+ '@eslint/plugin-sonarjs'
120
+ ]
121
+ for (const plugin of sonarPlugins) {
122
+ if (allDeps[plugin]) {
123
+ result.details.hasSonarPlugin = true
124
+ result.details.securityPlugins.push(plugin)
125
+ }
126
+ }
127
+
128
+ if (eslintContent) {
129
+ if (eslintContent.includes('sonarjs') || eslintContent.includes('plugin:sonarjs')) {
130
+ result.details.hasSonarPlugin = true
131
+ if (!result.details.securityPlugins.some(p => p.includes('sonarjs'))) {
132
+ result.details.securityPlugins.push('eslint-plugin-sonarjs (in config)')
133
+ }
134
+ }
135
+ }
136
+
137
+ if (result.details.hasSonarPlugin) {
138
+ result.score += 1
139
+ }
140
+
141
+ // Check for custom security rules (no-restricted-imports/syntax)
142
+ if (eslintContent) {
143
+ const hasRestrictedImports = eslintContent.includes('no-restricted-imports') ||
144
+ eslintContent.includes('no-restricted-syntax')
145
+ const hasSecurityKeywords = eslintContent.includes('SupabaseService') ||
146
+ eslintContent.includes('service_role') ||
147
+ eslintContent.includes('SERVICE_ROLE') ||
148
+ eslintContent.includes('@anthropic-ai') ||
149
+ eslintContent.includes('eval') ||
150
+ eslintContent.includes('DANGEROUS') ||
151
+ eslintContent.includes('detect-unsafe-regex') ||
152
+ eslintContent.includes('detect-eval')
153
+
154
+ if (hasRestrictedImports && hasSecurityKeywords) {
155
+ result.details.hasCustomSecurityRules = true
156
+ result.score += 1
157
+ }
158
+ }
159
+
160
+ // Determine status
161
+ if (result.score === 0) {
162
+ result.status = 'error'
163
+ result.details.message = 'ESLint configured but no security plugins detected'
164
+ } else if (result.score < 2) {
165
+ result.status = 'warning'
166
+ result.details.message = `${result.details.securityPlugins.length} security plugin(s) — consider adding more`
167
+ } else {
168
+ result.details.message = `ESLint security well-configured (${result.details.securityPlugins.join(', ')})`
169
+ }
170
+
171
+ return result
172
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Health Checks — All 16 project health checks
2
+ * Health Checks — All 23 project health checks
3
3
  *
4
4
  * Main entry: scanProjectHealth(projectPath, projectName, options?)
5
5
  * Individual checks available via named imports.
@@ -26,3 +26,9 @@ export { check as checkDopplerCompliance } from './doppler-compliance.js'
26
26
  export { check as checkInfrastructureYml } from './infrastructure-yml.js'
27
27
  export { check as checkFileOrganization } from './file-organization.js'
28
28
  export { check as checkRpcParamMismatch } from './rpc-param-mismatch.js'
29
+ export { check as checkTypescriptStrict } from './typescript-strict.js'
30
+ export { check as checkPrettier } from './prettier.js'
31
+ export { check as checkCoverageThresholds } from './coverage-thresholds.js'
32
+ export { check as checkEslintSecurity } from './eslint-security.js'
33
+ export { check as checkDependencyCruiser } from './dependency-cruiser.js'
34
+ export { check as checkConventionalCommits } from './conventional-commits.js'
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Health Check: Prettier / Code Formatting
3
+ *
4
+ * Checks for consistent code formatting setup.
5
+ * Score: up to 3 points:
6
+ * +1 for Prettier config file exists
7
+ * +1 for format:check or format script in package.json
8
+ * +1 for lint-staged config (auto-format on commit)
9
+ */
10
+
11
+ import { existsSync, readFileSync } from 'fs'
12
+ import { join } from 'path'
13
+ import { createCheck } from './types.js'
14
+
15
+ export async function check(projectPath) {
16
+ const result = createCheck('prettier', 3, {
17
+ configFound: false,
18
+ configPath: null,
19
+ hasFormatScript: false,
20
+ formatScript: null,
21
+ hasLintStaged: false,
22
+ lintStagedConfig: null,
23
+ message: ''
24
+ })
25
+
26
+ // Check for Prettier config files
27
+ const prettierConfigs = [
28
+ '.prettierrc',
29
+ '.prettierrc.json',
30
+ '.prettierrc.js',
31
+ '.prettierrc.cjs',
32
+ '.prettierrc.mjs',
33
+ '.prettierrc.yml',
34
+ '.prettierrc.yaml',
35
+ '.prettierrc.toml',
36
+ 'prettier.config.js',
37
+ 'prettier.config.cjs',
38
+ 'prettier.config.mjs',
39
+ // Also check in sub-packages
40
+ 'frontend/.prettierrc',
41
+ 'frontend/.prettierrc.json',
42
+ 'backend/.prettierrc',
43
+ 'backend/.prettierrc.json'
44
+ ]
45
+
46
+ for (const config of prettierConfigs) {
47
+ if (existsSync(join(projectPath, config))) {
48
+ result.details.configFound = true
49
+ result.details.configPath = config
50
+ result.score += 1
51
+ break
52
+ }
53
+ }
54
+
55
+ // Also check for prettier key in package.json
56
+ if (!result.details.configFound) {
57
+ const pkgPath = join(projectPath, 'package.json')
58
+ if (existsSync(pkgPath)) {
59
+ try {
60
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
61
+ if (pkg.prettier) {
62
+ result.details.configFound = true
63
+ result.details.configPath = 'package.json (prettier key)'
64
+ result.score += 1
65
+ }
66
+ } catch { /* ignore */ }
67
+ }
68
+ }
69
+
70
+ // Check for format scripts
71
+ const packageLocations = [
72
+ 'package.json',
73
+ 'backend/package.json',
74
+ 'frontend/package.json',
75
+ 'frontend-user/package.json'
76
+ ]
77
+
78
+ for (const loc of packageLocations) {
79
+ const pkgPath = join(projectPath, loc)
80
+ if (!existsSync(pkgPath)) continue
81
+
82
+ try {
83
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
84
+ const scripts = pkg.scripts || {}
85
+
86
+ for (const [name, cmd] of Object.entries(scripts)) {
87
+ if (typeof cmd !== 'string') continue
88
+ if (name === 'format' || name === 'format:check' || name === 'prettier' ||
89
+ name === 'prettier:check' || cmd.includes('prettier')) {
90
+ result.details.hasFormatScript = true
91
+ result.details.formatScript = `${loc} → ${name}`
92
+ break
93
+ }
94
+ }
95
+ if (result.details.hasFormatScript) break
96
+ } catch { /* ignore */ }
97
+ }
98
+
99
+ if (result.details.hasFormatScript) {
100
+ result.score += 1
101
+ }
102
+
103
+ // Check for lint-staged config
104
+ const lintStagedConfigs = [
105
+ '.lintstagedrc',
106
+ '.lintstagedrc.json',
107
+ '.lintstagedrc.js',
108
+ '.lintstagedrc.cjs',
109
+ '.lintstagedrc.mjs',
110
+ '.lintstagedrc.yml',
111
+ 'lint-staged.config.js',
112
+ 'lint-staged.config.cjs',
113
+ 'lint-staged.config.mjs',
114
+ // Sub-packages
115
+ 'frontend/.lintstagedrc',
116
+ 'frontend/.lintstagedrc.json',
117
+ 'backend/.lintstagedrc',
118
+ 'backend/.lintstagedrc.json'
119
+ ]
120
+
121
+ for (const config of lintStagedConfigs) {
122
+ if (existsSync(join(projectPath, config))) {
123
+ result.details.hasLintStaged = true
124
+ result.details.lintStagedConfig = config
125
+ result.score += 1
126
+ break
127
+ }
128
+ }
129
+
130
+ // Also check package.json for lint-staged key
131
+ if (!result.details.hasLintStaged) {
132
+ for (const loc of packageLocations) {
133
+ const pkgPath = join(projectPath, loc)
134
+ if (!existsSync(pkgPath)) continue
135
+
136
+ try {
137
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
138
+ if (pkg['lint-staged']) {
139
+ result.details.hasLintStaged = true
140
+ result.details.lintStagedConfig = `${loc} (lint-staged key)`
141
+ result.score += 1
142
+ break
143
+ }
144
+ } catch { /* ignore */ }
145
+ }
146
+ }
147
+
148
+ // Determine status
149
+ if (result.score === 0) {
150
+ result.status = 'error'
151
+ result.details.message = 'No code formatting setup detected'
152
+ } else if (result.score < 2) {
153
+ result.status = 'warning'
154
+ result.details.message = result.details.configFound
155
+ ? 'Prettier config exists but no format script or lint-staged'
156
+ : 'Format script exists but no Prettier config'
157
+ } else {
158
+ result.details.message = `Code formatting well-configured`
159
+ }
160
+
161
+ return result
162
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Project Health Scanner
3
3
  *
4
- * Orchestrates all 16 health checks and produces a HealthReport.
4
+ * Orchestrates all 23 health checks and produces a HealthReport.
5
5
  * This is the main entry point — consumers call scanProjectHealth().
6
6
  */
7
7
 
@@ -22,6 +22,12 @@ import { check as checkDopplerCompliance } from './doppler-compliance.js'
22
22
  import { check as checkInfrastructureYml } from './infrastructure-yml.js'
23
23
  import { check as checkFileOrganization } from './file-organization.js'
24
24
  import { check as checkRpcParamMismatch } from './rpc-param-mismatch.js'
25
+ import { check as checkTypescriptStrict } from './typescript-strict.js'
26
+ import { check as checkPrettier } from './prettier.js'
27
+ import { check as checkCoverageThresholds } from './coverage-thresholds.js'
28
+ import { check as checkEslintSecurity } from './eslint-security.js'
29
+ import { check as checkDependencyCruiser } from './dependency-cruiser.js'
30
+ import { check as checkConventionalCommits } from './conventional-commits.js'
25
31
  import { calculateHealthStatus } from './types.js'
26
32
 
27
33
  /**
@@ -52,7 +58,13 @@ export async function scanProjectHealth(projectPath, projectName, options = {})
52
58
  checkDopplerCompliance(projectPath),
53
59
  checkInfrastructureYml(projectPath),
54
60
  checkFileOrganization(projectPath),
55
- checkRpcParamMismatch(projectPath)
61
+ checkRpcParamMismatch(projectPath),
62
+ checkTypescriptStrict(projectPath),
63
+ checkPrettier(projectPath),
64
+ checkCoverageThresholds(projectPath),
65
+ checkEslintSecurity(projectPath),
66
+ checkDependencyCruiser(projectPath),
67
+ checkConventionalCommits(projectPath)
56
68
  ])
57
69
 
58
70
  const totalScore = checks.reduce((sum, c) => sum + c.score, 0)