@soulbatical/tetra-dev-toolkit 1.2.0 → 1.3.1

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 ADDED
@@ -0,0 +1,198 @@
1
+ # @soulbatical/tetra-dev-toolkit
2
+
3
+ Quality, security, and hygiene checks for all Soulbatical projects.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install --save-dev @soulbatical/tetra-dev-toolkit
9
+ ```
10
+
11
+ ## Setup (A-Z)
12
+
13
+ One command installs everything — hooks, CI, config:
14
+
15
+ ```bash
16
+ npx tetra-setup
17
+ ```
18
+
19
+ This creates:
20
+ - `.husky/pre-commit` — quick security checks before every commit
21
+ - `.husky/pre-push` — hygiene check before every push (blocks clutter)
22
+ - `.github/workflows/quality.yml` — full audit on PR/push to main
23
+ - `.tetra-quality.json` — project config (override defaults)
24
+
25
+ To install individual components:
26
+
27
+ ```bash
28
+ npx tetra-setup hooks # Husky hooks only
29
+ npx tetra-setup ci # GitHub Actions only
30
+ npx tetra-setup config # Config file only
31
+ ```
32
+
33
+ Re-running `tetra-setup hooks` on an existing project adds missing hooks without overwriting existing ones.
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ npx tetra-audit # Run all checks
39
+ npx tetra-audit security # Security checks only
40
+ npx tetra-audit stability # Stability checks only
41
+ npx tetra-audit codeQuality # Code quality checks only
42
+ npx tetra-audit supabase # Supabase checks only
43
+ npx tetra-audit hygiene # Repo hygiene checks only
44
+ npx tetra-audit quick # Quick critical checks (pre-commit)
45
+ npx tetra-audit --ci # CI mode (GitHub Actions annotations)
46
+ npx tetra-audit --json # JSON output
47
+ npx tetra-audit --verbose # Detailed output with fix suggestions
48
+ ```
49
+
50
+ Exit codes: `0` = passed, `1` = failed, `2` = error.
51
+
52
+ ## Check Suites
53
+
54
+ ### Security (5 checks)
55
+
56
+ | Check | Severity | What it catches |
57
+ |-------|----------|-----------------|
58
+ | Hardcoded Secrets | critical | API keys, tokens, JWTs in source code |
59
+ | Service Key Exposure | critical | Supabase service role keys in frontend |
60
+ | Deprecated Supabase Admin | high | Legacy `createClient(serviceKey)` patterns |
61
+ | SystemDB Whitelist | high | Unauthorized system database access |
62
+ | Gitignore Validation | high | Missing .gitignore entries, tracked .env files |
63
+
64
+ ### Stability (3 checks)
65
+
66
+ | Check | Severity | What it catches |
67
+ |-------|----------|-----------------|
68
+ | Husky Hooks | medium | Missing pre-commit/pre-push hooks |
69
+ | CI Pipeline | medium | Missing or incomplete GitHub Actions config |
70
+ | NPM Audit | high | Known vulnerabilities in dependencies |
71
+
72
+ ### Code Quality (1 check)
73
+
74
+ | Check | Severity | What it catches |
75
+ |-------|----------|-----------------|
76
+ | API Response Format | medium | Non-standard `{ success, data }` response format |
77
+
78
+ ### Supabase (2 checks, auto-detected)
79
+
80
+ | Check | Severity | What it catches |
81
+ |-------|----------|-----------------|
82
+ | RLS Policy Audit | critical | Tables without Row Level Security |
83
+ | RPC Param Mismatch | critical | TypeScript `.rpc()` calls with wrong parameter names vs SQL |
84
+
85
+ ### Hygiene (1 check)
86
+
87
+ | Check | Severity | What it catches |
88
+ |-------|----------|-----------------|
89
+ | File Organization | high | Stray .md, .sh, clutter in code dirs, root mess, nested docs/ |
90
+
91
+ ## Health Checks
92
+
93
+ Separate from audit suites, health checks provide a scored assessment (0-N points) used by the Ralph Manager dashboard:
94
+
95
+ | Check | Max | What it measures |
96
+ |-------|-----|------------------|
97
+ | File Organization | 6pt | Docs in /docs, scripts in /scripts, clean root & code dirs |
98
+ | Git | 4pt | Clean working tree, branch hygiene, commit frequency |
99
+ | Gitignore | 3pt | Critical entries present |
100
+ | CLAUDE.md | 3pt | Project instructions for AI assistants |
101
+ | Secrets | 3pt | No exposed secrets |
102
+ | Tests | 4pt | Test framework, coverage, test files |
103
+ | Naming Conventions | 5pt | File/dir naming consistency |
104
+ | Infrastructure YML | 3pt | Railway/Docker config |
105
+ | Doppler Compliance | 2pt | Secrets management via Doppler |
106
+ | MCP Servers | 2pt | MCP configuration |
107
+ | Stella Integration | 2pt | Stella package integration |
108
+ | Quality Toolkit | 2pt | Tetra dev-toolkit installed |
109
+ | Repo Visibility | 1pt | Private repo |
110
+ | RLS Audit | 3pt | Row Level Security policies |
111
+ | Plugins | 2pt | Claude Code plugin config |
112
+ | VinciFox Widget | 1pt | Widget installation |
113
+
114
+ ## Auto-fix: Cleanup Script
115
+
116
+ For hygiene issues, an auto-fix script is included:
117
+
118
+ ```bash
119
+ # Dry run (shows what would change)
120
+ bash node_modules/@soulbatical/tetra-dev-toolkit/bin/cleanup-repos.sh
121
+
122
+ # Execute
123
+ bash node_modules/@soulbatical/tetra-dev-toolkit/bin/cleanup-repos.sh --execute
124
+ ```
125
+
126
+ What it does:
127
+ - `.md` files in code dirs -> `docs/_moved/`
128
+ - `.sh` scripts in code dirs -> `scripts/_moved/`
129
+ - Root clutter (`.txt`, `.png`, `.csv`) -> `docs/_cleanup/`
130
+ - Code dir clutter -> `docs/_cleanup/{dir}/`
131
+ - `.env` secrets in code dirs -> deleted
132
+ - `tmp/`, `logs/`, `data/` dirs -> added to `.gitignore`
133
+
134
+ ## Configuration
135
+
136
+ Override defaults in `.tetra-quality.json` or `"tetra-quality"` key in `package.json`:
137
+
138
+ ```json
139
+ {
140
+ "suites": {
141
+ "security": true,
142
+ "stability": true,
143
+ "codeQuality": true,
144
+ "supabase": "auto",
145
+ "hygiene": true
146
+ },
147
+ "supabase": {
148
+ "publicRpcFunctions": ["get_public_data"],
149
+ "publicTables": ["public_lookup"]
150
+ },
151
+ "stability": {
152
+ "allowedVulnerabilities": {
153
+ "critical": 0,
154
+ "high": 0,
155
+ "moderate": 10
156
+ }
157
+ }
158
+ }
159
+ ```
160
+
161
+ ## CLI Tools
162
+
163
+ | Command | Description |
164
+ |---------|-------------|
165
+ | `tetra-audit` | Run quality/security/hygiene checks |
166
+ | `tetra-setup` | Install hooks, CI, and config |
167
+ | `tetra-dev-token` | Generate development tokens |
168
+
169
+ ## Changelog
170
+
171
+ ### 1.3.0 (2025-02-21)
172
+
173
+ **New: Hygiene suite**
174
+ - Added `tetra-audit hygiene` — detects stray docs, scripts, clutter in code dirs, root mess
175
+ - Added `cleanup-repos.sh` auto-fix script in `bin/`
176
+ - `tetra-setup hooks` now creates pre-push hook with hygiene gate
177
+ - Re-running `tetra-setup hooks` on existing repos adds hygiene check without overwriting
178
+
179
+ **New: RPC Param Mismatch check**
180
+ - Added `rpc-param-mismatch` check in supabase suite
181
+ - Statically compares `.rpc()` calls in TypeScript with SQL function parameter names
182
+ - Catches PGRST202 errors before they hit production
183
+
184
+ **Improved: File Organization health check**
185
+ - Extended from 5pt to 6pt — added root clutter detection
186
+ - Root clutter: `.txt`, `.png`, `.csv`, `.pdf`, `.py` files and `tmp/`, `logs/`, `data/` dirs
187
+ - Gitignored dirs no longer counted as clutter
188
+ - `CLAUDE.md` allowed anywhere (not just root)
189
+
190
+ ### 1.2.0
191
+
192
+ - Initial public version
193
+ - Security suite: hardcoded secrets, service key exposure, deprecated admin, systemdb whitelist, gitignore
194
+ - Stability suite: husky hooks, CI pipeline, npm audit
195
+ - Code quality suite: API response format
196
+ - Supabase suite: RLS policy audit
197
+ - Health checks: 16 checks, max 37pt
198
+ - CLI: `tetra-audit`, `tetra-setup`, `tetra-dev-token`
@@ -0,0 +1,287 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # Repo Cleanup Script — Verplaats alles naar de juiste plek
4
+ #
5
+ # Wat het doet:
6
+ # 1. Stray .md files → /docs/_moved/{oorspronkelijk-pad}/
7
+ # 2. Stray .sh scripts → /scripts/_moved/{oorspronkelijk-pad}/
8
+ # 3. Nested docs/ (frontend/docs/, backend/docs/) → /docs/{subdir-naam}/
9
+ # 4. Root clutter files (.txt, .png, .csv, .py) → /docs/_cleanup/
10
+ # 5. Code dir clutter (.txt, .log, .env) → /docs/_cleanup/{code-dir}/
11
+ # 6. Clutter dirs (tmp/, logs/, data/, etc.) → .gitignore
12
+ # 7. .env/.env.local in code dirs → verwijder (secrets horen niet in git)
13
+ #
14
+ # DRY RUN: standaard toont het alleen wat het zou doen.
15
+ # EXECUTE: ./cleanup-repos.sh --execute
16
+ # =============================================================================
17
+
18
+ set -euo pipefail
19
+
20
+ DRY_RUN=true
21
+ [[ "${1:-}" == "--execute" ]] && DRY_RUN=false
22
+
23
+ BASE="$HOME/projecten"
24
+ PROJECTS=(sparkbuddy-live agentrook ralph-manager vincifox snelstart-mcp web-mcp)
25
+
26
+ # Colors
27
+ RED='\033[0;31m'
28
+ GREEN='\033[0;32m'
29
+ YELLOW='\033[1;33m'
30
+ BLUE='\033[0;34m'
31
+ NC='\033[0m'
32
+
33
+ # Counters
34
+ TOTAL_MOVED=0
35
+ TOTAL_GITIGNORED=0
36
+ TOTAL_DELETED=0
37
+
38
+ lower() { echo "$1" | tr '[:upper:]' '[:lower:]'; }
39
+
40
+ log() { echo -e "${BLUE}[INFO]${NC} $1"; }
41
+ warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
42
+ action() { echo -e "${GREEN}[MOVE]${NC} $1"; }
43
+ danger() { echo -e "${RED}[DEL]${NC} $1"; }
44
+
45
+ safe_move() {
46
+ local src="$1" dst="$2"
47
+
48
+ if $DRY_RUN; then
49
+ action "$src → $dst"
50
+ else
51
+ mkdir -p "$(dirname "$PROJECT_PATH/$dst")"
52
+ mv "$PROJECT_PATH/$src" "$PROJECT_PATH/$dst"
53
+ fi
54
+ TOTAL_MOVED=$((TOTAL_MOVED + 1))
55
+ }
56
+
57
+ safe_delete() {
58
+ local target="$1"
59
+ if $DRY_RUN; then
60
+ danger "DELETE $target"
61
+ else
62
+ rm -f "$target"
63
+ fi
64
+ TOTAL_DELETED=$((TOTAL_DELETED + 1))
65
+ }
66
+
67
+ add_gitignore() {
68
+ local project_path="$1" entry="$2"
69
+ local gitignore="$project_path/.gitignore"
70
+
71
+ if [ -f "$gitignore" ] && grep -qF "$entry" "$gitignore" 2>/dev/null; then
72
+ return
73
+ fi
74
+
75
+ if $DRY_RUN; then
76
+ log "Add to .gitignore: $entry"
77
+ else
78
+ echo "$entry" >> "$gitignore"
79
+ fi
80
+ TOTAL_GITIGNORED=$((TOTAL_GITIGNORED + 1))
81
+ }
82
+
83
+ is_allowed_root_md() {
84
+ local name_lc
85
+ name_lc=$(lower "$1")
86
+ case "$name_lc" in
87
+ readme.md|claude.md|changelog.md|license.md|prompt.md|plan.md|contributing.md|code_of_conduct.md)
88
+ return 0 ;;
89
+ *) return 1 ;;
90
+ esac
91
+ }
92
+
93
+ # =============================================================================
94
+ # Main cleanup per project
95
+ # =============================================================================
96
+
97
+ for PROJECT in "${PROJECTS[@]}"; do
98
+ PROJECT_PATH="$BASE/$PROJECT"
99
+ [ -d "$PROJECT_PATH" ] || continue
100
+
101
+ echo ""
102
+ echo "================================================================"
103
+ echo -e "${BLUE}=== $PROJECT ===${NC}"
104
+ echo "================================================================"
105
+
106
+ # --- 1. Nested docs/ → merge into /docs/{subdir-naam}/ (FIRST, before stray .md scan) ---
107
+ log "Checking nested docs/ directories..."
108
+ for subdir in frontend backend backend-mcp backend-pdf mcp shell; do
109
+ nested_docs="$PROJECT_PATH/$subdir/docs"
110
+ [ -d "$nested_docs" ] || continue
111
+
112
+ file_count=$(find "$nested_docs" -type f 2>/dev/null | wc -l | tr -d ' ')
113
+ [[ "$file_count" == "0" ]] && continue
114
+
115
+ log "Moving $subdir/docs/ ($file_count files) → docs/$subdir/"
116
+ while IFS= read -r -d '' file; do
117
+ rel_from_nested="${file#$nested_docs/}"
118
+ safe_move "$subdir/docs/$rel_from_nested" "docs/$subdir/$rel_from_nested"
119
+ done < <(find "$nested_docs" -type f -print0 2>/dev/null)
120
+ done
121
+
122
+ # --- 2. Stray .md files → /docs/_moved/ ---
123
+ log "Checking stray .md files..."
124
+ while IFS= read -r -d '' file; do
125
+ rel="${file#$PROJECT_PATH/}"
126
+ name=$(basename "$file")
127
+ name_lc=$(lower "$name")
128
+ top_dir="${rel%%/*}"
129
+
130
+ # Skip README.md and CLAUDE.md anywhere (standard practice)
131
+ case "$name_lc" in
132
+ readme.md|claude.md) continue ;;
133
+ esac
134
+
135
+ # Skip root allowed .md files
136
+ [[ "$rel" == "$name" ]] && is_allowed_root_md "$name" && continue
137
+
138
+ # Skip /docs/, .ralph/, .claude/, .agents/, e2e/
139
+ case "$top_dir" in
140
+ docs|.ralph|.claude|.agents|e2e) continue ;;
141
+ esac
142
+
143
+ # Skip shell/templates/
144
+ [[ "$rel" == shell/templates/* ]] && continue
145
+
146
+ # Skip files already handled by nested docs merge (avoid double-move)
147
+ [[ "$rel" == */docs/* ]] && continue
148
+
149
+ safe_move "$rel" "docs/_moved/$rel"
150
+ done < <(find "$PROJECT_PATH" -maxdepth 5 -name "*.md" -type f \
151
+ -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/dist/*" \
152
+ -not -path "*/.next/*" -not -path "*/coverage/*" -print0 2>/dev/null)
153
+
154
+ # --- 3. Stray .sh scripts → /scripts/_moved/ ---
155
+ log "Checking stray .sh scripts..."
156
+ while IFS= read -r -d '' file; do
157
+ rel="${file#$PROJECT_PATH/}"
158
+ top_dir="${rel%%/*}"
159
+
160
+ # Skip root-level scripts
161
+ [[ "$rel" != */* ]] && continue
162
+
163
+ # Skip allowed dirs
164
+ case "$top_dir" in
165
+ scripts|shell|hooks|.husky|.ralph|.claude) continue ;;
166
+ esac
167
+
168
+ safe_move "$rel" "scripts/_moved/$rel"
169
+ done < <(find "$PROJECT_PATH" -maxdepth 5 -name "*.sh" -type f \
170
+ -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/dist/*" \
171
+ -print0 2>/dev/null)
172
+
173
+ # --- 4. Root clutter files → /docs/_cleanup/ ---
174
+ log "Checking root clutter..."
175
+ for file in "$PROJECT_PATH"/*; do
176
+ [ -f "$file" ] || continue
177
+ name=$(basename "$file")
178
+ name_lc=$(lower "$name")
179
+
180
+ # Skip dotfiles
181
+ [[ "$name" == .* ]] && continue
182
+
183
+ # Skip known config extensions and types handled by other checks
184
+ case "$name_lc" in
185
+ *.json|*.js|*.cjs|*.mjs|*.ts|*.toml|*.lock) continue ;;
186
+ *.md|*.sh|*.yml|*.yaml) continue ;;
187
+ dockerfile*|procfile) continue ;;
188
+ esac
189
+
190
+ # Check for clutter extensions
191
+ ext=".${name_lc##*.}"
192
+ case "$ext" in
193
+ .txt|.log|.pdf|.csv|.png|.jpg|.jpeg|.gif|.py|.bak|.tmp|.orig|.patch|.diff)
194
+ safe_move "$name" "docs/_cleanup/$name"
195
+ ;;
196
+ esac
197
+ done
198
+
199
+ # --- 5. Code dir clutter → /docs/_cleanup/{code-dir}/ ---
200
+ log "Checking code dir clutter..."
201
+ for codeDir in backend frontend backend-mcp backend-pdf mcp; do
202
+ code_path="$PROJECT_PATH/$codeDir"
203
+ [ -d "$code_path" ] || continue
204
+
205
+ while IFS= read -r -d '' file; do
206
+ name=$(basename "$file")
207
+ name_lc=$(lower "$name")
208
+ rel="${file#$PROJECT_PATH/}"
209
+ rel_from_code="${file#$code_path/}"
210
+ sub_top="${rel_from_code%%/*}"
211
+
212
+ # Skip allowed subdirs
213
+ case "$sub_top" in
214
+ public|static|assets|fixtures|supabase|migrations|prisma|test-fixtures) continue ;;
215
+ esac
216
+
217
+ # .env files → DELETE (secrets!)
218
+ case "$name_lc" in
219
+ .env|.env.local|.env.development|.env.production|.env.staging)
220
+ danger "SECRET: $rel"
221
+ safe_delete "$file"
222
+ continue
223
+ ;;
224
+ esac
225
+
226
+ # Skip templates
227
+ case "$name_lc" in
228
+ *.example|*.test) continue ;;
229
+ esac
230
+
231
+ # Skip standard web files
232
+ case "$name_lc" in
233
+ robots.txt|llms.txt|llms-full.txt|humans.txt|security.txt|ads.txt) continue ;;
234
+ esac
235
+
236
+ # Check for clutter extensions
237
+ ext=".${name_lc##*.}"
238
+ case "$ext" in
239
+ .txt|.log|.pdf|.csv|.bak|.tmp|.orig|.patch|.diff)
240
+ safe_move "$rel" "docs/_cleanup/$rel"
241
+ ;;
242
+ esac
243
+ done < <(find "$code_path" -maxdepth 4 -type f \
244
+ -not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/.next/*" \
245
+ -not -path "*/coverage/*" -not -path "*/.turbo/*" -not -path "*/test-results/*" \
246
+ \( -name "*.txt" -o -name "*.log" -o -name "*.pdf" -o -name "*.csv" \
247
+ -o -name "*.bak" -o -name "*.tmp" -o -name "*.orig" \
248
+ -o -name ".env" -o -name ".env.*" \) -print0 2>/dev/null)
249
+ done
250
+
251
+ # --- 6. Clutter dirs → .gitignore ---
252
+ log "Checking clutter directories..."
253
+ for dir_name in tmp temp logs log data venv .venv reports output out backup backups; do
254
+ if [ -d "$PROJECT_PATH/$dir_name" ]; then
255
+ add_gitignore "$PROJECT_PATH" "$dir_name/"
256
+ warn "Directory $dir_name/ → added to .gitignore"
257
+ fi
258
+ done
259
+
260
+ done
261
+
262
+ # =============================================================================
263
+ # Summary
264
+ # =============================================================================
265
+
266
+ echo ""
267
+ echo "================================================================"
268
+ if $DRY_RUN; then
269
+ echo -e "${YELLOW}DRY RUN COMPLETE${NC}"
270
+ echo " Files to move: $TOTAL_MOVED"
271
+ echo " Files to delete: $TOTAL_DELETED"
272
+ echo " Gitignore entries: $TOTAL_GITIGNORED"
273
+ echo ""
274
+ echo "Run with --execute to apply changes:"
275
+ echo " bash cleanup-repos.sh --execute"
276
+ else
277
+ echo -e "${GREEN}CLEANUP COMPLETE${NC}"
278
+ echo " Files moved: $TOTAL_MOVED"
279
+ echo " Files deleted: $TOTAL_DELETED"
280
+ echo " Gitignore entries: $TOTAL_GITIGNORED"
281
+ echo ""
282
+ echo "Next steps:"
283
+ echo " 1. Review /docs/_moved/ and /docs/_cleanup/ per project"
284
+ echo " 2. Delete what you don't need, keep what's useful"
285
+ echo " 3. git add + commit per project"
286
+ fi
287
+ echo "================================================================"
@@ -7,13 +7,14 @@
7
7
  * tetra-audit # Run all checks
8
8
  * tetra-audit security # Run security checks only
9
9
  * tetra-audit stability # Run stability checks only
10
+ * tetra-audit hygiene # Run repo hygiene checks only
10
11
  * tetra-audit quick # Run quick critical checks
11
12
  * tetra-audit --ci # CI mode (GitHub Actions annotations)
12
13
  * tetra-audit --json # JSON output
13
14
  */
14
15
 
15
16
  import { program } from 'commander'
16
- import { runAllChecks, runSecurityChecks, runStabilityChecks, runCodeQualityChecks, runQuickCheck } from '../lib/runner.js'
17
+ import { runAllChecks, runSecurityChecks, runStabilityChecks, runCodeQualityChecks, runHygieneChecks, runQuickCheck } from '../lib/runner.js'
17
18
  import { formatResults, formatGitHubActions } from '../lib/reporters/terminal.js'
18
19
 
19
20
  program
@@ -40,6 +41,9 @@ program
40
41
  case 'code-quality':
41
42
  results = await runCodeQualityChecks()
42
43
  break
44
+ case 'hygiene':
45
+ results = await runHygieneChecks()
46
+ break
43
47
  case 'quick':
44
48
  results = await runQuickCheck()
45
49
  break
@@ -120,6 +120,39 @@ echo "✅ Pre-commit checks passed"
120
120
  console.log(' ⏭️ .husky/pre-commit already exists (use --force to overwrite)')
121
121
  }
122
122
 
123
+ // Create or extend pre-push hook with hygiene check
124
+ const prePushPath = join(huskyDir, 'pre-push')
125
+ const hygieneBlock = `
126
+ # Tetra hygiene check — blocks push if repo contains clutter
127
+ echo "🧹 Running repo hygiene check..."
128
+ npx tetra-audit hygiene
129
+ if [ $? -ne 0 ]; then
130
+ echo ""
131
+ echo "❌ Repo hygiene issues found! Clean up before pushing."
132
+ echo " Run 'npx tetra-audit hygiene --verbose' for details."
133
+ echo " Run 'bash node_modules/@soulbatical/tetra-dev-toolkit/bin/cleanup-repos.sh' to auto-fix."
134
+ exit 1
135
+ fi
136
+ echo "✅ Repo hygiene passed"
137
+ `
138
+
139
+ if (!existsSync(prePushPath)) {
140
+ // No pre-push hook yet — create one
141
+ const prePushContent = `#!/bin/sh\n${hygieneBlock}\n`
142
+ writeFileSync(prePushPath, prePushContent)
143
+ execSync(`chmod +x ${prePushPath}`)
144
+ console.log(' ✅ Created .husky/pre-push with hygiene check')
145
+ } else {
146
+ // Pre-push hook exists — add hygiene check if not already there
147
+ const existing = readFileSync(prePushPath, 'utf-8')
148
+ if (!existing.includes('tetra-audit hygiene')) {
149
+ writeFileSync(prePushPath, existing.trimEnd() + '\n' + hygieneBlock)
150
+ console.log(' ✅ Added hygiene check to existing .husky/pre-push')
151
+ } else {
152
+ console.log(' ⏭️ .husky/pre-push already has hygiene check')
153
+ }
154
+ }
155
+
123
156
  // Add prepare script to package.json
124
157
  if (!pkg.scripts?.prepare?.includes('husky')) {
125
158
  pkg.scripts = pkg.scripts || {}
@@ -194,7 +227,8 @@ async function setupConfig(options) {
194
227
  "security": true,
195
228
  "stability": true,
196
229
  "codeQuality": true,
197
- "supabase": "auto"
230
+ "supabase": "auto",
231
+ "hygiene": true
198
232
  },
199
233
  "security": {
200
234
  "checkHardcodedSecrets": true,