@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.
@@ -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'|'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'|'gitignore'|'repo-visibility'|'vincifox-widget'|'stella-integration'|'claude-md'|'doppler-compliance'|'infrastructure-yml'|'file-organization'} HealthCheckType
9
9
  *
10
10
  * @typedef {'ok'|'warning'|'error'} HealthStatus
11
11
  *
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Health Check: TypeScript Strictness
3
+ *
4
+ * Checks TypeScript configuration for strict mode and type safety.
5
+ * Score: up to 3 points:
6
+ * +1 for strict: true in tsconfig.json
7
+ * +1 for tsc --noEmit script in package.json (typecheck command)
8
+ * +1 for noImplicitAny + strictNullChecks (if not using strict: true)
9
+ * OR bonus for strict: true + noUncheckedIndexedAccess
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('typescript-strict', 3, {
18
+ tsconfigFound: false,
19
+ strict: false,
20
+ noImplicitAny: false,
21
+ strictNullChecks: false,
22
+ noUncheckedIndexedAccess: false,
23
+ hasTypecheckScript: false,
24
+ typecheckScript: null,
25
+ tsconfigPaths: [],
26
+ message: ''
27
+ })
28
+
29
+ // Find tsconfig files in root + common sub-packages
30
+ const tsconfigLocations = [
31
+ 'tsconfig.json',
32
+ 'backend/tsconfig.json',
33
+ 'frontend/tsconfig.json',
34
+ 'frontend-user/tsconfig.json',
35
+ 'src/tsconfig.json',
36
+ 'mcp/tsconfig.json'
37
+ ]
38
+
39
+ let bestConfig = null
40
+
41
+ for (const loc of tsconfigLocations) {
42
+ const tsconfigPath = join(projectPath, loc)
43
+ if (!existsSync(tsconfigPath)) continue
44
+
45
+ result.details.tsconfigPaths.push(loc)
46
+ result.details.tsconfigFound = true
47
+
48
+ try {
49
+ // Strip JSON comments (// and /* */) before parsing
50
+ const raw = readFileSync(tsconfigPath, 'utf-8')
51
+ const stripped = raw
52
+ .replace(/\/\/.*$/gm, '')
53
+ .replace(/\/\*[\s\S]*?\*\//g, '')
54
+ .replace(/,\s*([}\]])/g, '$1')
55
+ const tsconfig = JSON.parse(stripped)
56
+ const opts = tsconfig.compilerOptions || {}
57
+
58
+ if (opts.strict && !bestConfig) {
59
+ bestConfig = { path: loc, strict: true, opts }
60
+ } else if (!bestConfig) {
61
+ bestConfig = { path: loc, strict: false, opts }
62
+ }
63
+ } catch { /* invalid tsconfig, skip */ }
64
+ }
65
+
66
+ if (!result.details.tsconfigFound) {
67
+ result.status = 'warning'
68
+ result.details.message = 'No tsconfig.json found'
69
+ return result
70
+ }
71
+
72
+ if (bestConfig) {
73
+ const opts = bestConfig.opts
74
+
75
+ if (opts.strict) {
76
+ result.score += 1
77
+ result.details.strict = true
78
+ }
79
+
80
+ if (opts.noImplicitAny) result.details.noImplicitAny = true
81
+ if (opts.strictNullChecks) result.details.strictNullChecks = true
82
+ if (opts.noUncheckedIndexedAccess) result.details.noUncheckedIndexedAccess = true
83
+
84
+ // Bonus point: strict + extra strictness OR individual flags
85
+ if (opts.strict && opts.noUncheckedIndexedAccess) {
86
+ result.score += 1
87
+ } else if (!opts.strict && opts.noImplicitAny && opts.strictNullChecks) {
88
+ result.score += 1
89
+ }
90
+ }
91
+
92
+ // Check for typecheck script in package.json files
93
+ const packageLocations = [
94
+ 'package.json',
95
+ 'backend/package.json',
96
+ 'frontend/package.json',
97
+ 'frontend-user/package.json'
98
+ ]
99
+
100
+ for (const loc of packageLocations) {
101
+ const pkgPath = join(projectPath, loc)
102
+ if (!existsSync(pkgPath)) continue
103
+
104
+ try {
105
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
106
+ const scripts = pkg.scripts || {}
107
+
108
+ // Look for typecheck or tsc --noEmit scripts
109
+ for (const [name, cmd] of Object.entries(scripts)) {
110
+ if (typeof cmd !== 'string') continue
111
+ if (name === 'typecheck' || name === 'type-check' ||
112
+ cmd.includes('tsc --noEmit') || cmd.includes('tsc -noEmit') ||
113
+ (name === 'check' && cmd.includes('tsc'))) {
114
+ result.details.hasTypecheckScript = true
115
+ result.details.typecheckScript = `${loc} → ${name}: ${cmd}`
116
+ break
117
+ }
118
+ }
119
+ if (result.details.hasTypecheckScript) break
120
+ } catch { /* ignore */ }
121
+ }
122
+
123
+ if (result.details.hasTypecheckScript) {
124
+ result.score += 1
125
+ }
126
+
127
+ // Determine status
128
+ if (result.score === 0) {
129
+ result.status = 'error'
130
+ result.details.message = 'TypeScript not strict and no typecheck script found'
131
+ } else if (result.score < 2) {
132
+ result.status = 'warning'
133
+ result.details.message = result.details.strict
134
+ ? 'strict: true but no typecheck script'
135
+ : result.details.hasTypecheckScript
136
+ ? 'Typecheck script exists but strict mode disabled'
137
+ : 'Partial TypeScript strictness'
138
+ } else {
139
+ result.details.message = `TypeScript well-configured (${result.details.tsconfigPaths.length} tsconfig(s))`
140
+ }
141
+
142
+ return result
143
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulbatical/tetra-dev-toolkit",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "publishConfig": {
5
5
  "access": "restricted"
6
6
  },
@@ -35,7 +35,8 @@
35
35
  "templates/"
36
36
  ],
37
37
  "scripts": {
38
- "test": "node --test src/**/*.test.js",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest",
39
40
  "lint": "eslint src/ lib/ bin/",
40
41
  "build": "echo 'No build step needed'"
41
42
  },
@@ -52,7 +53,8 @@
52
53
  "yaml": "^2.8.2"
53
54
  },
54
55
  "devDependencies": {
55
- "eslint": "^9.0.0"
56
+ "eslint": "^9.0.0",
57
+ "vitest": "^4.0.18"
56
58
  },
57
59
  "peerDependencies": {
58
60
  "eslint": ">=8.0.0",