@soulbatical/tetra-dev-toolkit 1.2.0 → 1.3.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,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,