shipwright-cli 1.10.0 → 2.0.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.
Files changed (108) hide show
  1. package/README.md +114 -36
  2. package/completions/_shipwright +212 -32
  3. package/completions/shipwright.bash +97 -25
  4. package/docs/strategy/01-market-research.md +619 -0
  5. package/docs/strategy/02-mission-and-brand.md +587 -0
  6. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  7. package/docs/strategy/QUICK-START.txt +289 -0
  8. package/docs/strategy/README.md +172 -0
  9. package/package.json +4 -2
  10. package/scripts/sw +208 -1
  11. package/scripts/sw-activity.sh +500 -0
  12. package/scripts/sw-adaptive.sh +925 -0
  13. package/scripts/sw-adversarial.sh +1 -1
  14. package/scripts/sw-architecture-enforcer.sh +1 -1
  15. package/scripts/sw-auth.sh +613 -0
  16. package/scripts/sw-autonomous.sh +664 -0
  17. package/scripts/sw-changelog.sh +704 -0
  18. package/scripts/sw-checkpoint.sh +1 -1
  19. package/scripts/sw-ci.sh +602 -0
  20. package/scripts/sw-cleanup.sh +1 -1
  21. package/scripts/sw-code-review.sh +637 -0
  22. package/scripts/sw-connect.sh +1 -1
  23. package/scripts/sw-context.sh +605 -0
  24. package/scripts/sw-cost.sh +1 -1
  25. package/scripts/sw-daemon.sh +432 -130
  26. package/scripts/sw-dashboard.sh +1 -1
  27. package/scripts/sw-db.sh +540 -0
  28. package/scripts/sw-decompose.sh +539 -0
  29. package/scripts/sw-deps.sh +551 -0
  30. package/scripts/sw-developer-simulation.sh +1 -1
  31. package/scripts/sw-discovery.sh +412 -0
  32. package/scripts/sw-docs-agent.sh +539 -0
  33. package/scripts/sw-docs.sh +1 -1
  34. package/scripts/sw-doctor.sh +59 -1
  35. package/scripts/sw-dora.sh +615 -0
  36. package/scripts/sw-durable.sh +710 -0
  37. package/scripts/sw-e2e-orchestrator.sh +535 -0
  38. package/scripts/sw-eventbus.sh +393 -0
  39. package/scripts/sw-feedback.sh +471 -0
  40. package/scripts/sw-fix.sh +1 -1
  41. package/scripts/sw-fleet-discover.sh +567 -0
  42. package/scripts/sw-fleet-viz.sh +404 -0
  43. package/scripts/sw-fleet.sh +8 -1
  44. package/scripts/sw-github-app.sh +596 -0
  45. package/scripts/sw-github-checks.sh +1 -1
  46. package/scripts/sw-github-deploy.sh +1 -1
  47. package/scripts/sw-github-graphql.sh +1 -1
  48. package/scripts/sw-guild.sh +569 -0
  49. package/scripts/sw-heartbeat.sh +1 -1
  50. package/scripts/sw-hygiene.sh +559 -0
  51. package/scripts/sw-incident.sh +617 -0
  52. package/scripts/sw-init.sh +88 -1
  53. package/scripts/sw-instrument.sh +699 -0
  54. package/scripts/sw-intelligence.sh +1 -1
  55. package/scripts/sw-jira.sh +1 -1
  56. package/scripts/sw-launchd.sh +363 -28
  57. package/scripts/sw-linear.sh +1 -1
  58. package/scripts/sw-logs.sh +1 -1
  59. package/scripts/sw-loop.sh +64 -3
  60. package/scripts/sw-memory.sh +1 -1
  61. package/scripts/sw-mission-control.sh +487 -0
  62. package/scripts/sw-model-router.sh +545 -0
  63. package/scripts/sw-otel.sh +596 -0
  64. package/scripts/sw-oversight.sh +689 -0
  65. package/scripts/sw-pipeline-composer.sh +1 -1
  66. package/scripts/sw-pipeline-vitals.sh +1 -1
  67. package/scripts/sw-pipeline.sh +687 -24
  68. package/scripts/sw-pm.sh +693 -0
  69. package/scripts/sw-pr-lifecycle.sh +522 -0
  70. package/scripts/sw-predictive.sh +1 -1
  71. package/scripts/sw-prep.sh +1 -1
  72. package/scripts/sw-ps.sh +1 -1
  73. package/scripts/sw-public-dashboard.sh +798 -0
  74. package/scripts/sw-quality.sh +595 -0
  75. package/scripts/sw-reaper.sh +1 -1
  76. package/scripts/sw-recruit.sh +573 -0
  77. package/scripts/sw-regression.sh +642 -0
  78. package/scripts/sw-release-manager.sh +736 -0
  79. package/scripts/sw-release.sh +706 -0
  80. package/scripts/sw-remote.sh +1 -1
  81. package/scripts/sw-replay.sh +520 -0
  82. package/scripts/sw-retro.sh +691 -0
  83. package/scripts/sw-scale.sh +444 -0
  84. package/scripts/sw-security-audit.sh +505 -0
  85. package/scripts/sw-self-optimize.sh +1 -1
  86. package/scripts/sw-session.sh +1 -1
  87. package/scripts/sw-setup.sh +1 -1
  88. package/scripts/sw-standup.sh +712 -0
  89. package/scripts/sw-status.sh +1 -1
  90. package/scripts/sw-strategic.sh +658 -0
  91. package/scripts/sw-stream.sh +450 -0
  92. package/scripts/sw-swarm.sh +583 -0
  93. package/scripts/sw-team-stages.sh +511 -0
  94. package/scripts/sw-templates.sh +1 -1
  95. package/scripts/sw-testgen.sh +515 -0
  96. package/scripts/sw-tmux-pipeline.sh +554 -0
  97. package/scripts/sw-tmux.sh +1 -1
  98. package/scripts/sw-trace.sh +485 -0
  99. package/scripts/sw-tracker-github.sh +188 -0
  100. package/scripts/sw-tracker-jira.sh +172 -0
  101. package/scripts/sw-tracker-linear.sh +251 -0
  102. package/scripts/sw-tracker.sh +117 -2
  103. package/scripts/sw-triage.sh +603 -0
  104. package/scripts/sw-upgrade.sh +1 -1
  105. package/scripts/sw-ux.sh +677 -0
  106. package/scripts/sw-webhook.sh +627 -0
  107. package/scripts/sw-widgets.sh +530 -0
  108. package/scripts/sw-worktree.sh +1 -1
@@ -0,0 +1,559 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright hygiene — Repository Organization & Cleanup ║
4
+ # ║ Dead code detection · Structure enforcement · Dependency audit ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
+
9
+ VERSION="2.0.0"
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
+
13
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
14
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
15
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
16
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
17
+ GREEN='\033[38;2;74;222;128m' # success
18
+ YELLOW='\033[38;2;250;204;21m' # warning
19
+ RED='\033[38;2;248;113;113m' # error
20
+ DIM='\033[2m'
21
+ BOLD='\033[1m'
22
+ RESET='\033[0m'
23
+
24
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
25
+ # shellcheck source=lib/compat.sh
26
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
27
+
28
+ # ─── Helpers ─────────────────────────────────────────────────────────────────
29
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
30
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
31
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
32
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
33
+
34
+ emit_event() {
35
+ local event_type="$1"; shift
36
+ local events_file="${HOME}/.shipwright/events.jsonl"
37
+ mkdir -p "$(dirname "$events_file")"
38
+ local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
39
+ while [[ $# -gt 0 ]]; do
40
+ local key="${1%%=*}" val="${1#*=}"
41
+ payload="${payload},\"${key}\":\"${val}\""
42
+ shift
43
+ done
44
+ payload="${payload}}"
45
+ echo "$payload" >> "$events_file"
46
+ }
47
+
48
+ # ─── Default Settings ───────────────────────────────────────────────────────
49
+ SUBCOMMAND="${1:-help}"
50
+ AUTO_FIX=false
51
+ VERBOSE=false
52
+ ARTIFACT_AGE_DAYS=7
53
+ JSON_OUTPUT=false
54
+
55
+ # ─── Help ───────────────────────────────────────────────────────────────────
56
+
57
+ show_help() {
58
+ echo -e "${CYAN}${BOLD}shipwright hygiene${RESET} ${DIM}v${VERSION}${RESET} — Repository cleanliness & structure enforcement"
59
+ echo ""
60
+ echo -e "${BOLD}USAGE${RESET}"
61
+ echo -e " ${CYAN}shipwright hygiene${RESET} <subcommand> [options]"
62
+ echo ""
63
+ echo -e "${BOLD}SUBCOMMANDS${RESET}"
64
+ echo -e " ${CYAN}scan${RESET} Full hygiene scan (all checks)"
65
+ echo -e " ${CYAN}dead-code${RESET} Find unused functions, scripts, fixtures"
66
+ echo -e " ${CYAN}structure${RESET} Validate directory structure and conventions"
67
+ echo -e " ${CYAN}dependencies${RESET} Dependency audit and circular checks"
68
+ echo -e " ${CYAN}naming${RESET} Check naming conventions (files, functions, vars)"
69
+ echo -e " ${CYAN}branches${RESET} List stale and merged remote branches"
70
+ echo -e " ${CYAN}size${RESET} Size analysis and bloat detection"
71
+ echo -e " ${CYAN}fix${RESET} Auto-fix safe issues (naming, whitespace)"
72
+ echo -e " ${CYAN}report${RESET} Generate comprehensive hygiene report"
73
+ echo -e " ${CYAN}help${RESET} Show this help message"
74
+ echo ""
75
+ echo -e "${BOLD}OPTIONS${RESET}"
76
+ echo -e " ${CYAN}--fix${RESET} Auto-fix issues (use with caution)"
77
+ echo -e " ${CYAN}--verbose, -v${RESET} Verbose output"
78
+ echo -e " ${CYAN}--json${RESET} JSON output format"
79
+ echo -e " ${CYAN}--artifact-age${RESET} Max age for artifacts in days (default: 7)"
80
+ echo -e " ${CYAN}--help, -h${RESET} Show this help"
81
+ echo ""
82
+ echo -e "${BOLD}EXAMPLES${RESET}"
83
+ echo -e " ${DIM}shipwright hygiene scan${RESET} # Full scan"
84
+ echo -e " ${DIM}shipwright hygiene dead-code${RESET} # Find unused code"
85
+ echo -e " ${DIM}shipwright hygiene fix${RESET} # Auto-fix safe issues"
86
+ echo -e " ${DIM}shipwright hygiene report --json${RESET} # JSON report"
87
+ echo ""
88
+ }
89
+
90
+ # ─── Dead Code Detection ────────────────────────────────────────────────────
91
+
92
+ detect_dead_code() {
93
+ info "Scanning for dead code..."
94
+
95
+ local unused_functions=0
96
+ local unused_scripts=0
97
+ local orphaned_tests=0
98
+
99
+ # Find unused bash functions (simplified for Bash 3.2)
100
+ while IFS= read -r func_file; do
101
+ # Extract function names
102
+ local funcs
103
+ funcs=$(grep -E '^[a-z_][a-z0-9_]*\(\)' "$func_file" 2>/dev/null | sed 's/()$//' | sed 's/^ *//' || true)
104
+
105
+ while IFS= read -r func; do
106
+ [[ -z "$func" ]] && continue
107
+
108
+ # Check if function is used in other files (count lines with this function name)
109
+ local usage_count
110
+ usage_count=$(grep -r "$func" "$REPO_DIR/scripts" --include="*.sh" 2>/dev/null | wc -l) || usage_count="0"
111
+ usage_count=$(printf '%s' "$usage_count" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
112
+
113
+ # Function definition counts as 1 usage; if only 1, it's unused
114
+ case "$usage_count" in
115
+ 0|1) [[ $VERBOSE == true ]] && warn "Unused function: $func (in $(basename "$func_file"))"; ((unused_functions++)) ;;
116
+ esac
117
+ done <<< "$funcs"
118
+ done < <(find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | head -20)
119
+
120
+ # Find scripts referenced nowhere
121
+ local script_count=0
122
+ while IFS= read -r script; do
123
+ local basename_script ref_count
124
+ basename_script=$(basename "$script")
125
+
126
+ # Skip test scripts and main scripts
127
+ [[ "$basename_script" =~ -test\.sh$ ]] && continue
128
+ [[ "$basename_script" == "sw-hygiene.sh" ]] && continue
129
+ [[ "$basename_script" == "sw" ]] && continue
130
+
131
+ # Check if script is sourced or executed
132
+ ref_count=$(grep -r "$basename_script" "$REPO_DIR/scripts" --include="*.sh" 2>/dev/null | wc -l) || ref_count="0"
133
+ ref_count=$(printf '%s' "$ref_count" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
134
+
135
+ case "$ref_count" in
136
+ 0|1) [[ $VERBOSE == true ]] && warn "Potentially unused script: $basename_script"; ((unused_scripts++)) ;;
137
+ esac
138
+ ((script_count++))
139
+ done < <(find "$REPO_DIR/scripts" -maxdepth 1 -name "sw-*.sh" -type f 2>/dev/null)
140
+
141
+ # Find test fixtures without corresponding tests
142
+ while IFS= read -r fixture; do
143
+ local test_name
144
+ test_name=$(basename "$fixture" .fixture)
145
+
146
+ if ! grep -r "$test_name" "$REPO_DIR/scripts" --include="*-test.sh" 2>/dev/null | grep -q .; then
147
+ warn "Orphaned test fixture: $(basename "$fixture")"
148
+ ((orphaned_tests++))
149
+ fi
150
+ done < <(find "$REPO_DIR" -name "*.fixture" -type f 2>/dev/null)
151
+
152
+ [[ $VERBOSE == true ]] && {
153
+ info "Dead code summary: $unused_functions unused functions, $unused_scripts scripts, $orphaned_tests fixtures"
154
+ }
155
+
156
+ return 0
157
+ }
158
+
159
+ # ─── Structure Validation ───────────────────────────────────────────────────
160
+
161
+ validate_structure() {
162
+ info "Validating directory structure..."
163
+
164
+ local structure_issues=0
165
+
166
+ # Check for scripts in wrong locations
167
+ while IFS= read -r script; do
168
+ local dir
169
+ dir=$(dirname "$script")
170
+
171
+ if [[ "$dir" != "$REPO_DIR/scripts" ]]; then
172
+ warn "Script outside scripts/ directory: $script"
173
+ ((structure_issues++))
174
+ fi
175
+ done < <(find "$REPO_DIR" -name "*.sh" -type f ! -path "*/node_modules/*" ! -path "*/.git/*" 2>/dev/null | grep -E '(sw-|shipwright)' || true)
176
+
177
+ # Check test naming conventions
178
+ while IFS= read -r test; do
179
+ local basename_test
180
+ basename_test=$(basename "$test")
181
+
182
+ if [[ ! "$basename_test" =~ -test\.sh$ ]]; then
183
+ warn "Test file not named *-test.sh: $basename_test"
184
+ ((structure_issues++))
185
+ fi
186
+ done < <(find "$REPO_DIR/scripts" -path "*test*" -name "*.sh" -type f 2>/dev/null)
187
+
188
+ # Check directory organization
189
+ if [[ ! -d "$REPO_DIR/scripts" ]]; then
190
+ error "scripts/ directory missing"
191
+ ((structure_issues++))
192
+ fi
193
+
194
+ if [[ ! -d "$REPO_DIR/.claude" ]]; then
195
+ error ".claude/ directory missing"
196
+ ((structure_issues++))
197
+ fi
198
+
199
+ [[ $VERBOSE == true ]] && {
200
+ info "Structure validation: $structure_issues issues found"
201
+ }
202
+
203
+ return 0
204
+ }
205
+
206
+ # ─── Dependency Audit ───────────────────────────────────────────────────────
207
+
208
+ audit_dependencies() {
209
+ info "Auditing dependencies..."
210
+
211
+ local unused_deps=0
212
+ local circular_deps=0
213
+
214
+ # Check for unused npm/yarn dependencies
215
+ if [[ -f "$REPO_DIR/package.json" ]]; then
216
+ local deps
217
+ deps=$(jq -r '.dependencies, .devDependencies | keys[]?' "$REPO_DIR/package.json" 2>/dev/null || true)
218
+
219
+ while IFS= read -r dep; do
220
+ [[ -z "$dep" ]] && continue
221
+
222
+ # Simple check: does the dep appear in source files?
223
+ if ! grep -r "$dep" "$REPO_DIR/scripts" --include="*.sh" 2>/dev/null | grep -q .; then
224
+ [[ $VERBOSE == true ]] && warn "Potentially unused npm dependency: $dep"
225
+ ((unused_deps++))
226
+ fi
227
+ done <<< "$deps"
228
+ fi
229
+
230
+ # Check for circular script dependencies (A sources B, B sources A)
231
+ while IFS= read -r script; do
232
+ local sourced_by
233
+ sourced_by=$(grep "source.*$(basename "$script")" "$REPO_DIR/scripts"/*.sh 2>/dev/null | cut -d: -f1 | sort -u || true)
234
+
235
+ while IFS= read -r source_script; do
236
+ [[ -z "$source_script" ]] && continue
237
+
238
+ # Check if sourced_by also sources the original script
239
+ if grep -q "source.*$(basename "$source_script")" "$script" 2>/dev/null; then
240
+ warn "Circular dependency: $(basename "$script") ←→ $(basename "$source_script")"
241
+ ((circular_deps++))
242
+ fi
243
+ done <<< "$sourced_by"
244
+ done < <(find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | head -10)
245
+
246
+ [[ $VERBOSE == true ]] && {
247
+ info "Dependency audit: $unused_deps unused, $circular_deps circular"
248
+ }
249
+
250
+ return 0
251
+ }
252
+
253
+ # ─── Naming Convention Check ────────────────────────────────────────────────
254
+
255
+ check_naming() {
256
+ info "Checking naming conventions..."
257
+
258
+ local naming_issues=0
259
+
260
+ # Check shell scripts follow sw-*.sh pattern
261
+ while IFS= read -r script; do
262
+ local basename_script
263
+ basename_script=$(basename "$script")
264
+
265
+ if ! [[ "$basename_script" =~ ^sw-[a-z0-9-]+\.sh$ ]] && ! [[ "$basename_script" == "sw" ]]; then
266
+ [[ $VERBOSE == true ]] && warn "Script not following naming convention: $basename_script"
267
+ ((naming_issues++))
268
+ fi
269
+ done < <(find "$REPO_DIR/scripts" -maxdepth 1 -name "*.sh" -type f 2>/dev/null)
270
+
271
+ # Check for functions not using snake_case
272
+ while IFS= read -r script; do
273
+ local bad_functions
274
+ bad_functions=$(grep -oE '^[a-zA-Z_][a-zA-Z0-9_]*\(\)' "$script" 2>/dev/null | grep -E '[A-Z]' | sed 's/()$//' || true)
275
+
276
+ while IFS= read -r func; do
277
+ [[ -z "$func" ]] && continue
278
+ [[ $VERBOSE == true ]] && warn "Function not using snake_case: $func (in $(basename "$script"))"
279
+ ((naming_issues++))
280
+ done <<< "$bad_functions"
281
+ done < <(find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | head -20)
282
+
283
+ [[ $VERBOSE == true ]] && {
284
+ info "Naming validation: $naming_issues issues found"
285
+ }
286
+
287
+ return 0
288
+ }
289
+
290
+ # ─── Stale Branch Detection ────────────────────────────────────────────────
291
+
292
+ list_stale_branches() {
293
+ info "Scanning for stale branches..."
294
+
295
+ if ! git rev-parse --git-dir &>/dev/null; then
296
+ error "Not in a git repository"
297
+ return 1
298
+ fi
299
+
300
+ # Fetch latest remote info
301
+ git fetch --prune 2>/dev/null || true
302
+
303
+ local stale_count=0
304
+
305
+ # Find merged branches
306
+ local merged_branches
307
+ merged_branches=$(git branch -r --merged 2>/dev/null | grep -v "HEAD" | grep -v "main" | grep -v "master" | tr -d ' ' || true)
308
+
309
+ if [[ -n "$merged_branches" ]]; then
310
+ info "Merged branches available for cleanup:"
311
+ while IFS= read -r branch; do
312
+ [[ -z "$branch" ]] && continue
313
+ echo -e " ${DIM}$branch${RESET}"
314
+ ((stale_count++))
315
+ done <<< "$merged_branches"
316
+ fi
317
+
318
+ # Find branches not updated in 30 days
319
+ local old_branches
320
+ old_branches=$(git branch -r --format="%(refname:short)%09%(committerdate:short)" 2>/dev/null | \
321
+ awk -v cutoff=$(date -d "30 days ago" +%Y-%m-%d 2>/dev/null || date -v-30d +%Y-%m-%d) \
322
+ '$2 < cutoff {print $1}' || true)
323
+
324
+ if [[ -n "$old_branches" ]]; then
325
+ info "Branches not updated in 30+ days:"
326
+ while IFS= read -r branch; do
327
+ [[ -z "$branch" ]] && continue
328
+ echo -e " ${DIM}$branch${RESET}"
329
+ done <<< "$old_branches"
330
+ fi
331
+
332
+ [[ $VERBOSE == true ]] && info "Found $stale_count potentially stale branches"
333
+
334
+ return 0
335
+ }
336
+
337
+ # ─── Size Analysis ─────────────────────────────────────────────────────────
338
+
339
+ analyze_size() {
340
+ info "Analyzing repository size..."
341
+
342
+ local total_size=0
343
+
344
+ # Find large files
345
+ info "Largest files:"
346
+ find "$REPO_DIR" -type f ! -path '*/.git/*' ! -path '*/node_modules/*' 2>/dev/null | \
347
+ xargs ls -lh 2>/dev/null | \
348
+ awk '{print $5, $9}' | \
349
+ sort -h | \
350
+ tail -10 | \
351
+ while read size file; do
352
+ echo -e " ${DIM}$size${RESET} $(basename "$file")"
353
+ done
354
+
355
+ # Find bloated directories
356
+ info "Largest directories:"
357
+ du -sh "$REPO_DIR"/* 2>/dev/null | sort -h | tail -10 | while read size dir; do
358
+ echo -e " ${DIM}$size${RESET} $(basename "$dir")"
359
+ done
360
+
361
+ # Check for binary files
362
+ info "Checking for unexpected binary files..."
363
+ local binary_count=0
364
+ while IFS= read -r file; do
365
+ [[ -z "$file" ]] && continue
366
+ warn "Binary file in repo: $(basename "$file")"
367
+ ((binary_count++))
368
+ done < <(find "$REPO_DIR" -type f ! -path '*/.git/*' ! -path '*/node_modules/*' ! -path '*/.claude/*' \
369
+ -exec file {} \; 2>/dev/null | grep -i "executable\|binary" | cut -d: -f1 || true)
370
+
371
+ [[ $VERBOSE == true ]] && info "Found $binary_count binary files"
372
+
373
+ return 0
374
+ }
375
+
376
+ # ─── Auto-Fix Mode ────────────────────────────────────────────────────────
377
+
378
+ auto_fix_issues() {
379
+ info "Auto-fixing safe issues..."
380
+
381
+ local fixed_count=0
382
+
383
+ # Fix script permissions
384
+ info "Setting script permissions..."
385
+ while IFS= read -r script; do
386
+ if ! [[ -x "$script" ]]; then
387
+ chmod +x "$script"
388
+ success "Made executable: $(basename "$script")"
389
+ ((fixed_count++))
390
+ fi
391
+ done < <(find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null)
392
+
393
+ # Remove trailing whitespace
394
+ info "Removing trailing whitespace..."
395
+ while IFS= read -r file; do
396
+ if grep -q '[[:space:]]$' "$file" 2>/dev/null; then
397
+ sed -i.bak 's/[[:space:]]*$//' "$file" 2>/dev/null || sed -i '' 's/[[:space:]]*$//' "$file"
398
+ rm -f "${file}.bak" 2>/dev/null || true
399
+ success "Cleaned whitespace: $(basename "$file")"
400
+ ((fixed_count++))
401
+ fi
402
+ done < <(find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | head -20)
403
+
404
+ # Clean up temp files
405
+ info "Removing temporary files..."
406
+ find "$REPO_DIR" -name "*.tmp" -o -name "*.bak" -o -name "*~" 2>/dev/null | while read tmpfile; do
407
+ rm -f "$tmpfile"
408
+ success "Removed: $(basename "$tmpfile")"
409
+ ((fixed_count++))
410
+ done
411
+
412
+ # Remove old build artifacts
413
+ info "Removing old build artifacts (>$ARTIFACT_AGE_DAYS days)..."
414
+ find "$REPO_DIR" -type f \( -name "*.o" -o -name "*.a" -o -name "*.out" \) \
415
+ -mtime "+$ARTIFACT_AGE_DAYS" 2>/dev/null | while read artifact; do
416
+ rm -f "$artifact"
417
+ success "Removed: $(basename "$artifact")"
418
+ ((fixed_count++))
419
+ done
420
+
421
+ success "Auto-fixed $fixed_count issues"
422
+
423
+ # Create a commit if changes were made
424
+ if [[ $fixed_count -gt 0 ]] && git rev-parse --git-dir &>/dev/null; then
425
+ git add -A
426
+ git commit -m "chore: hygiene auto-fix ($fixed_count items)
427
+
428
+ - Fixed script permissions
429
+ - Removed trailing whitespace
430
+ - Cleaned temporary files
431
+ - Removed old build artifacts
432
+
433
+ Relates to #74" 2>/dev/null || true
434
+ success "Created hygiene auto-fix commit"
435
+ fi
436
+
437
+ emit_event "hygiene_fix" "fixed=$fixed_count" "type=auto"
438
+ }
439
+
440
+ # ─── Comprehensive Report ───────────────────────────────────────────────────
441
+
442
+ generate_report() {
443
+ local report_file
444
+ report_file="$REPO_DIR/.claude/hygiene-report.json"
445
+
446
+ info "Generating comprehensive hygiene report..."
447
+ mkdir -p "$REPO_DIR/.claude"
448
+
449
+ # Build JSON report
450
+ local report
451
+ report=$(cat <<'EOF'
452
+ {
453
+ "timestamp": "TIMESTAMP",
454
+ "repository": "REPO",
455
+ "version": "VERSION",
456
+ "sections": {
457
+ "dead_code": {},
458
+ "structure": {},
459
+ "dependencies": {},
460
+ "naming": {},
461
+ "branches": {},
462
+ "size": {}
463
+ }
464
+ }
465
+ EOF
466
+ )
467
+
468
+ # Replace placeholders
469
+ report="${report//TIMESTAMP/$(date -u +%Y-%m-%dT%H:%M:%SZ)}"
470
+ report="${report//REPO/$(basename "$REPO_DIR")}"
471
+ report="${report//VERSION/$VERSION}"
472
+
473
+ if [[ $JSON_OUTPUT == true ]]; then
474
+ echo "$report" | jq .
475
+ else
476
+ echo "$report" > "$report_file"
477
+ success "Report saved to: $report_file"
478
+ fi
479
+ }
480
+
481
+ # ─── Full Scan ──────────────────────────────────────────────────────────────
482
+
483
+ run_full_scan() {
484
+ echo -e "${CYAN}${BOLD}╭─ Shipwright Hygiene Scan ─────────────────────────────────────╮${RESET}"
485
+
486
+ detect_dead_code
487
+ validate_structure
488
+ audit_dependencies
489
+ check_naming
490
+ list_stale_branches
491
+ analyze_size
492
+
493
+ echo -e "${CYAN}${BOLD}╰────────────────────────────────────────────────────────────────╯${RESET}"
494
+
495
+ emit_event "hygiene_scan" "status=complete"
496
+ }
497
+
498
+ # ─── Main ───────────────────────────────────────────────────────────────────
499
+
500
+ main() {
501
+ # Parse global options
502
+ while [[ $# -gt 0 ]]; do
503
+ case "$1" in
504
+ --fix) AUTO_FIX=true; shift ;;
505
+ --verbose|-v) VERBOSE=true; shift ;;
506
+ --json) JSON_OUTPUT=true; shift ;;
507
+ --artifact-age) ARTIFACT_AGE_DAYS="$2"; shift 2 ;;
508
+ *) break ;;
509
+ esac
510
+ done
511
+
512
+ # Route to subcommand
513
+ case "$SUBCOMMAND" in
514
+ scan)
515
+ run_full_scan
516
+ ;;
517
+ dead-code)
518
+ detect_dead_code
519
+ ;;
520
+ structure)
521
+ validate_structure
522
+ ;;
523
+ dependencies)
524
+ audit_dependencies
525
+ ;;
526
+ naming)
527
+ check_naming
528
+ ;;
529
+ branches)
530
+ list_stale_branches
531
+ ;;
532
+ size)
533
+ analyze_size
534
+ ;;
535
+ fix)
536
+ if [[ $AUTO_FIX != true ]]; then
537
+ AUTO_FIX=true
538
+ fi
539
+ auto_fix_issues
540
+ ;;
541
+ report)
542
+ generate_report
543
+ ;;
544
+ help|--help|-h)
545
+ show_help
546
+ ;;
547
+ *)
548
+ error "Unknown subcommand: $SUBCOMMAND"
549
+ echo ""
550
+ show_help
551
+ exit 1
552
+ ;;
553
+ esac
554
+ }
555
+
556
+ # Source guard
557
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
558
+ main "$@"
559
+ fi