shipwright-cli 1.9.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 (117) hide show
  1. package/.claude/hooks/post-tool-use.sh +12 -5
  2. package/README.md +114 -36
  3. package/completions/_shipwright +212 -32
  4. package/completions/shipwright.bash +97 -25
  5. package/docs/strategy/01-market-research.md +619 -0
  6. package/docs/strategy/02-mission-and-brand.md +587 -0
  7. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  8. package/docs/strategy/QUICK-START.txt +289 -0
  9. package/docs/strategy/README.md +172 -0
  10. package/package.json +4 -2
  11. package/scripts/sw +217 -2
  12. package/scripts/sw-activity.sh +500 -0
  13. package/scripts/sw-adaptive.sh +925 -0
  14. package/scripts/sw-adversarial.sh +1 -1
  15. package/scripts/sw-architecture-enforcer.sh +1 -1
  16. package/scripts/sw-auth.sh +613 -0
  17. package/scripts/sw-autonomous.sh +664 -0
  18. package/scripts/sw-changelog.sh +704 -0
  19. package/scripts/sw-checkpoint.sh +79 -1
  20. package/scripts/sw-ci.sh +602 -0
  21. package/scripts/sw-cleanup.sh +192 -7
  22. package/scripts/sw-code-review.sh +637 -0
  23. package/scripts/sw-connect.sh +1 -1
  24. package/scripts/sw-context.sh +605 -0
  25. package/scripts/sw-cost.sh +1 -1
  26. package/scripts/sw-daemon.sh +812 -138
  27. package/scripts/sw-dashboard.sh +1 -1
  28. package/scripts/sw-db.sh +540 -0
  29. package/scripts/sw-decompose.sh +539 -0
  30. package/scripts/sw-deps.sh +551 -0
  31. package/scripts/sw-developer-simulation.sh +1 -1
  32. package/scripts/sw-discovery.sh +412 -0
  33. package/scripts/sw-docs-agent.sh +539 -0
  34. package/scripts/sw-docs.sh +1 -1
  35. package/scripts/sw-doctor.sh +59 -1
  36. package/scripts/sw-dora.sh +615 -0
  37. package/scripts/sw-durable.sh +710 -0
  38. package/scripts/sw-e2e-orchestrator.sh +535 -0
  39. package/scripts/sw-eventbus.sh +393 -0
  40. package/scripts/sw-feedback.sh +471 -0
  41. package/scripts/sw-fix.sh +1 -1
  42. package/scripts/sw-fleet-discover.sh +567 -0
  43. package/scripts/sw-fleet-viz.sh +404 -0
  44. package/scripts/sw-fleet.sh +8 -1
  45. package/scripts/sw-github-app.sh +596 -0
  46. package/scripts/sw-github-checks.sh +1 -1
  47. package/scripts/sw-github-deploy.sh +1 -1
  48. package/scripts/sw-github-graphql.sh +1 -1
  49. package/scripts/sw-guild.sh +569 -0
  50. package/scripts/sw-heartbeat.sh +1 -1
  51. package/scripts/sw-hygiene.sh +559 -0
  52. package/scripts/sw-incident.sh +617 -0
  53. package/scripts/sw-init.sh +88 -1
  54. package/scripts/sw-instrument.sh +699 -0
  55. package/scripts/sw-intelligence.sh +1 -1
  56. package/scripts/sw-jira.sh +1 -1
  57. package/scripts/sw-launchd.sh +366 -31
  58. package/scripts/sw-linear.sh +1 -1
  59. package/scripts/sw-logs.sh +1 -1
  60. package/scripts/sw-loop.sh +507 -51
  61. package/scripts/sw-memory.sh +198 -3
  62. package/scripts/sw-mission-control.sh +487 -0
  63. package/scripts/sw-model-router.sh +545 -0
  64. package/scripts/sw-otel.sh +596 -0
  65. package/scripts/sw-oversight.sh +689 -0
  66. package/scripts/sw-pipeline-composer.sh +8 -8
  67. package/scripts/sw-pipeline-vitals.sh +1096 -0
  68. package/scripts/sw-pipeline.sh +2451 -180
  69. package/scripts/sw-pm.sh +693 -0
  70. package/scripts/sw-pr-lifecycle.sh +522 -0
  71. package/scripts/sw-predictive.sh +1 -1
  72. package/scripts/sw-prep.sh +1 -1
  73. package/scripts/sw-ps.sh +4 -3
  74. package/scripts/sw-public-dashboard.sh +798 -0
  75. package/scripts/sw-quality.sh +595 -0
  76. package/scripts/sw-reaper.sh +5 -3
  77. package/scripts/sw-recruit.sh +573 -0
  78. package/scripts/sw-regression.sh +642 -0
  79. package/scripts/sw-release-manager.sh +736 -0
  80. package/scripts/sw-release.sh +706 -0
  81. package/scripts/sw-remote.sh +1 -1
  82. package/scripts/sw-replay.sh +520 -0
  83. package/scripts/sw-retro.sh +691 -0
  84. package/scripts/sw-scale.sh +444 -0
  85. package/scripts/sw-security-audit.sh +505 -0
  86. package/scripts/sw-self-optimize.sh +109 -8
  87. package/scripts/sw-session.sh +31 -9
  88. package/scripts/sw-setup.sh +1 -1
  89. package/scripts/sw-standup.sh +712 -0
  90. package/scripts/sw-status.sh +192 -1
  91. package/scripts/sw-strategic.sh +658 -0
  92. package/scripts/sw-stream.sh +450 -0
  93. package/scripts/sw-swarm.sh +583 -0
  94. package/scripts/sw-team-stages.sh +511 -0
  95. package/scripts/sw-templates.sh +1 -1
  96. package/scripts/sw-testgen.sh +515 -0
  97. package/scripts/sw-tmux-pipeline.sh +554 -0
  98. package/scripts/sw-tmux.sh +1 -1
  99. package/scripts/sw-trace.sh +485 -0
  100. package/scripts/sw-tracker-github.sh +188 -0
  101. package/scripts/sw-tracker-jira.sh +172 -0
  102. package/scripts/sw-tracker-linear.sh +251 -0
  103. package/scripts/sw-tracker.sh +117 -2
  104. package/scripts/sw-triage.sh +603 -0
  105. package/scripts/sw-upgrade.sh +1 -1
  106. package/scripts/sw-ux.sh +677 -0
  107. package/scripts/sw-webhook.sh +627 -0
  108. package/scripts/sw-widgets.sh +530 -0
  109. package/scripts/sw-worktree.sh +1 -1
  110. package/templates/pipelines/autonomous.json +8 -1
  111. package/templates/pipelines/cost-aware.json +21 -0
  112. package/templates/pipelines/deployed.json +40 -6
  113. package/templates/pipelines/enterprise.json +16 -2
  114. package/templates/pipelines/fast.json +19 -0
  115. package/templates/pipelines/full.json +16 -2
  116. package/templates/pipelines/hotfix.json +19 -0
  117. package/templates/pipelines/standard.json +19 -0
@@ -0,0 +1,637 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ Autonomous Code Review Agent — Clean Code & Architecture Analysis ║
4
+ # ║ Quality enforcement: code smells, SOLID, layer boundaries, complexity ║
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 type="$1"; shift
36
+ local entry="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$type\""
37
+ while [[ $# -gt 0 ]]; do
38
+ local key="${1%%=*}"; local val="${1#*=}"
39
+ val="${val//\"/\\\"}"
40
+ entry="$entry,\"${key}\":\"${val}\""
41
+ shift
42
+ done
43
+ entry="$entry}"
44
+ mkdir -p "$HOME/.shipwright"
45
+ echo "$entry" >> "$HOME/.shipwright/events.jsonl"
46
+ }
47
+
48
+ # ─── Configuration ───────────────────────────────────────────────────────
49
+ REVIEW_CONFIG="${REPO_DIR}/.claude/code-review.json"
50
+ QUALITY_METRICS_FILE="${REPO_DIR}/.claude/pipeline-artifacts/quality-metrics.json"
51
+ TRENDS_FILE="${HOME}/.shipwright/code-review-trends.jsonl"
52
+ STRICTNESS="${STRICTNESS:-normal}" # relaxed, normal, strict
53
+
54
+ init_config() {
55
+ [[ -f "$REVIEW_CONFIG" ]] && return 0
56
+ mkdir -p "${REPO_DIR}/.claude"
57
+ cat > "$REVIEW_CONFIG" <<'EOF'
58
+ {
59
+ "strictness": "normal",
60
+ "ignore_patterns": ["test", "vendor", "node_modules", "dist", "build"],
61
+ "rules": {
62
+ "max_function_lines": 60,
63
+ "max_nesting_depth": 4,
64
+ "max_cyclomatic_complexity": 10,
65
+ "long_variable_name_chars": 25,
66
+ "magic_number_detection": true
67
+ },
68
+ "enabled_checks": [
69
+ "code_smells",
70
+ "solid_violations",
71
+ "architecture_boundaries",
72
+ "complexity_metrics",
73
+ "style_consistency",
74
+ "auto_fix_simple"
75
+ ]
76
+ }
77
+ EOF
78
+ }
79
+
80
+ load_config() {
81
+ if [[ -f "$REVIEW_CONFIG" ]]; then
82
+ STRICTNESS=$(jq -r '.strictness // "normal"' "$REVIEW_CONFIG" 2>/dev/null || echo "normal")
83
+ fi
84
+ }
85
+
86
+ # ─── Code Smell Detection ────────────────────────────────────────────────────
87
+
88
+ detect_code_smells() {
89
+ local target_file="$1"
90
+ local issues=()
91
+
92
+ [[ ! -f "$target_file" ]] && return 0
93
+
94
+ local ext="${target_file##*.}"
95
+ [[ "$ext" != "sh" ]] && return 0
96
+
97
+ # Check 1: Long functions (>60 lines in bash)
98
+ local func_count
99
+ func_count=$(grep -c "^[a-zA-Z_][a-zA-Z0-9_]*().*{" "$target_file" 2>/dev/null || echo "0")
100
+ if [[ "$func_count" -gt 0 ]]; then
101
+ while IFS= read -r line; do
102
+ if [[ "$line" =~ ^[a-zA-Z_][a-zA-Z0-9_]*\(\).*\{ ]]; then
103
+ local func_name="${line%%(*}"
104
+ local start_line
105
+ start_line=$(grep -n "^${func_name}()" "$target_file" | head -1 | cut -d: -f1)
106
+ local end_line
107
+ end_line=$(awk "NR>$start_line && /^}/ {print NR; exit}" "$target_file")
108
+ local func_lines=$((end_line - start_line))
109
+ if [[ $func_lines -gt 60 ]]; then
110
+ issues+=("LONG_FUNCTION: $func_name at line $start_line ($func_lines lines)")
111
+ fi
112
+ fi
113
+ done < <(grep "^[a-zA-Z_][a-zA-Z0-9_]*().*{" "$target_file" 2>/dev/null || true)
114
+ fi
115
+
116
+ # Check 2: Deep nesting (>4 levels)
117
+ local max_indent=0
118
+ while IFS= read -r line; do
119
+ local indent=0
120
+ while [[ "${line:0:1}" == " " || "${line:0:1}" == " " ]]; do
121
+ indent=$((indent + 1))
122
+ line="${line:1}"
123
+ done
124
+ [[ $indent -gt $max_indent ]] && max_indent=$indent
125
+ done < "$target_file"
126
+ local nesting_level=$((max_indent / 4))
127
+ if [[ $nesting_level -gt 4 ]]; then
128
+ issues+=("DEEP_NESTING: Maximum $nesting_level levels detected (threshold: 4)")
129
+ fi
130
+
131
+ # Check 3: Duplicate code patterns (repeated >3 times)
132
+ local dup_count=0
133
+ dup_count=$(grep -c '^\s*\(cd\|cd\|mkdir\|rm\|echo\)' "$target_file" 2>/dev/null || echo "0")
134
+ if [[ $dup_count -gt 3 ]]; then
135
+ issues+=("REPEATED_PATTERNS: Common operations appear $dup_count times (consider helper functions)")
136
+ fi
137
+
138
+ # Check 4: Magic numbers
139
+ if grep -qE '\s[0-9]{3,}\s' "$target_file" 2>/dev/null; then
140
+ issues+=("MAGIC_NUMBERS: Found numeric literals without clear purpose")
141
+ fi
142
+
143
+ # Check 5: Poor naming (single letter variables in conditionals)
144
+ if grep -qE 'for\s+[a-z]\s+in|if.*\[\[\s*[a-z]\s*(==|-\w)' "$target_file" 2>/dev/null; then
145
+ issues+=("POOR_NAMING: Single-letter variables in conditionals")
146
+ fi
147
+
148
+ for issue in "${issues[@]}"; do
149
+ echo "$issue"
150
+ done
151
+ }
152
+
153
+ # ─── SOLID Violations ────────────────────────────────────────────────────────
154
+
155
+ check_solid_principles() {
156
+ local target_file="$1"
157
+ local violations=()
158
+
159
+ [[ ! -f "$target_file" ]] && return 0
160
+
161
+ local ext="${target_file##*.}"
162
+ [[ "$ext" != "sh" ]] && return 0
163
+
164
+ # Single Responsibility: Check if scripts do multiple unrelated things
165
+ local sourced_count
166
+ sourced_count=$(grep -c '^\s*source\|^\s*\.\s' "$target_file" 2>/dev/null || echo "0")
167
+ if [[ $sourced_count -gt 3 ]]; then
168
+ violations+=("SRP_VIOLATION: Script sources $sourced_count modules (too many responsibilities)")
169
+ fi
170
+
171
+ # Open/Closed: Check for hardcoded config values
172
+ if grep -qE '^\s*(HARDCODED|MAGIC|CONFIG)=' "$target_file" 2>/dev/null; then
173
+ violations+=("OCP_VIOLATION: Hardcoded configuration values (use config files instead)")
174
+ fi
175
+
176
+ # Liskov Substitution: Check for function parameter assumptions
177
+ if grep -qE 'if.*type\s+\$|if.*\[\[\s*-x' "$target_file" 2>/dev/null; then
178
+ violations+=("LSP_CONCERN: Possible type checking in function (breaks substitution)")
179
+ fi
180
+
181
+ # Interface Segregation: Check if functions have too many parameters
182
+ while IFS= read -r line; do
183
+ if [[ "$line" =~ ^[a-zA-Z_][a-zA-Z0-9_]*\(\) ]]; then
184
+ local func_name="${line%%(*}"
185
+ local body
186
+ body=$(awk "/^${func_name}\\(\\)/,/^}/" "$target_file" | grep -c '\$[0-9]' || echo "0")
187
+ if [[ $body -gt 5 ]]; then
188
+ violations+=("ISP_VIOLATION: Function $func_name uses >5 parameters")
189
+ fi
190
+ fi
191
+ done < <(grep "^[a-zA-Z_][a-zA-Z0-9_]*().*{" "$target_file" 2>/dev/null || true)
192
+
193
+ for violation in "${violations[@]}"; do
194
+ echo "$violation"
195
+ done
196
+ }
197
+
198
+ # ─── Architecture Boundary Checks ────────────────────────────────────────────
199
+
200
+ check_architecture_boundaries() {
201
+ local target_file="$1"
202
+ local violations=()
203
+
204
+ [[ ! -f "$target_file" ]] && return 0
205
+
206
+ # Providers should not call each other directly
207
+ if [[ "$target_file" =~ sw-tracker-.*\.sh ]] || [[ "$target_file" =~ .*provider.*\.sh ]]; then
208
+ if grep -qE 'source.*provider|source.*tracker-' "$target_file" 2>/dev/null; then
209
+ violations+=("ARCH_VIOLATION: Provider sourcing another provider (use router pattern)")
210
+ fi
211
+ fi
212
+
213
+ # Non-router scripts shouldn't bypass the router
214
+ if [[ ! "$target_file" =~ /sw$ ]] && [[ ! "$target_file" =~ /sw-.*router.*\.sh ]]; then
215
+ if grep -qE 'exec\s+\$SCRIPT_DIR/sw-' "$target_file" 2>/dev/null; then
216
+ violations+=("ARCH_VIOLATION: Direct exec to script (use router pattern)")
217
+ fi
218
+ fi
219
+
220
+ # Tests shouldn't call production scripts
221
+ if [[ "$target_file" =~ -test\.sh$ ]]; then
222
+ if grep -qE 'source.*[^-test]\.sh' "$target_file" 2>/dev/null; then
223
+ violations+=("ARCH_VIOLATION: Test file sourcing production code directly")
224
+ fi
225
+ fi
226
+
227
+ for violation in "${violations[@]}"; do
228
+ echo "$violation"
229
+ done
230
+ }
231
+
232
+ # ─── Complexity Metrics ──────────────────────────────────────────────────────
233
+
234
+ analyze_complexity() {
235
+ local target_file="$1"
236
+
237
+ [[ ! -f "$target_file" ]] && return 0
238
+
239
+ local ext="${target_file##*.}"
240
+ [[ "$ext" != "sh" ]] && return 0
241
+
242
+ local metrics="{\"file\":\"$target_file\",\"functions\":[]}"
243
+
244
+ while IFS= read -r line; do
245
+ if [[ "$line" =~ ^[a-zA-Z_][a-zA-Z0-9_]*\(\) ]]; then
246
+ local func_name="${line%%(*}"
247
+ local start_line
248
+ start_line=$(grep -n "^${func_name}()" "$target_file" | head -1 | cut -d: -f1)
249
+ local end_line
250
+ end_line=$(awk "NR>$start_line && /^}/ {print NR; exit}" "$target_file")
251
+
252
+ # Cyclomatic complexity: count decision points (if, elif, case, &&, ||)
253
+ local cc=1
254
+ cc=$((cc + $(sed -n "${start_line},${end_line}p" "$target_file" | grep -cE '\s(if|elif|case|&&|\|\|)' || echo 0)))
255
+
256
+ # Lines of code
257
+ local loc=$((end_line - start_line))
258
+
259
+ # Cognitive complexity: nested decision depth
260
+ local max_depth=0
261
+ local curr_depth=0
262
+ while IFS= read -r func_line; do
263
+ if [[ "$func_line" =~ \[\[ ]] || [[ "$func_line" =~ if.*then ]]; then
264
+ curr_depth=$((curr_depth + 1))
265
+ [[ $curr_depth -gt $max_depth ]] && max_depth=$curr_depth
266
+ fi
267
+ [[ "$func_line" =~ fi ]] && [[ $curr_depth -gt 0 ]] && curr_depth=$((curr_depth - 1))
268
+ done < <(sed -n "${start_line},${end_line}p" "$target_file")
269
+
270
+ metrics=$(echo "$metrics" | jq --arg fn "$func_name" --arg cc "$cc" --arg loc "$loc" --arg cd "$max_depth" \
271
+ '.functions += [{"name": $fn, "cyclomatic_complexity": $cc, "lines": $loc, "cognitive_complexity": $cd}]' 2>/dev/null || echo "$metrics")
272
+ fi
273
+ done < <(grep "^[a-zA-Z_][a-zA-Z0-9_]*().*{" "$target_file" 2>/dev/null || true)
274
+
275
+ echo "$metrics"
276
+ }
277
+
278
+ # ─── Style Consistency ───────────────────────────────────────────────────────
279
+
280
+ check_style_consistency() {
281
+ local target_file="$1"
282
+ local issues=()
283
+
284
+ [[ ! -f "$target_file" ]] && return 0
285
+
286
+ local ext="${target_file##*.}"
287
+ [[ "$ext" != "sh" ]] && return 0
288
+
289
+ # Check error handling consistency
290
+ local has_trap=false
291
+ local has_set_e=false
292
+
293
+ [[ $(grep -c 'trap.*ERR' "$target_file" 2>/dev/null || echo 0) -gt 0 ]] && has_trap=true
294
+ [[ $(grep -c 'set -e' "$target_file" 2>/dev/null || echo 0) -gt 0 ]] && has_set_e=true
295
+
296
+ if [[ "$has_set_e" == "true" ]] && [[ "$has_trap" == "false" ]]; then
297
+ issues+=("STYLE: Missing ERR trap despite 'set -e' (inconsistent error handling)")
298
+ fi
299
+
300
+ # Check for inconsistent quote usage
301
+ local single_quotes
302
+ local double_quotes
303
+ single_quotes=$(grep -o "'" "$target_file" 2>/dev/null | wc -l || echo 0)
304
+ double_quotes=$(grep -o '"' "$target_file" 2>/dev/null | wc -l || echo 0)
305
+ if [[ $single_quotes -gt $((double_quotes * 3)) ]] || [[ $double_quotes -gt $((single_quotes * 3)) ]]; then
306
+ issues+=("STYLE: Inconsistent quote style (mix of single and double quotes)")
307
+ fi
308
+
309
+ # Check for inconsistent spacing/indentation
310
+ local tab_count
311
+ local space_count
312
+ tab_count=$(grep -c $'^\t' "$target_file" 2>/dev/null || echo 0)
313
+ space_count=$(grep -c '^ ' "$target_file" 2>/dev/null || echo 0)
314
+ if [[ $tab_count -gt 0 ]] && [[ $space_count -gt 0 ]]; then
315
+ issues+=("STYLE: Mixed tabs and spaces")
316
+ fi
317
+
318
+ # Check variable naming consistency
319
+ if grep -qE '\$\{[a-z]+_[a-z]+\}' "$target_file" && grep -qE '\$\{[A-Z]+\}' "$target_file"; then
320
+ if ! grep -qE '\$\{[A-Z]+_[A-Z]+\}' "$target_file"; then
321
+ issues+=("STYLE: Inconsistent variable naming (snake_case vs UPPERCASE)")
322
+ fi
323
+ fi
324
+
325
+ for issue in "${issues[@]}"; do
326
+ echo "$issue"
327
+ done
328
+ }
329
+
330
+ # ─── Auto-fix Simple Issues ─────────────────────────────────────────────────
331
+
332
+ auto_fix() {
333
+ local target_file="$1"
334
+ local fixed=0
335
+
336
+ [[ ! -f "$target_file" ]] && return 0
337
+
338
+ local ext="${target_file##*.}"
339
+ [[ "$ext" != "sh" ]] && return 0
340
+
341
+ local backup="${target_file}.review-backup"
342
+ cp "$target_file" "$backup"
343
+
344
+ # Fix 1: Run shellcheck and capture warnings
345
+ if command -v shellcheck &>/dev/null; then
346
+ local shellcheck_fixes=0
347
+ local warnings_file
348
+ warnings_file=$(mktemp)
349
+ shellcheck -f json "$target_file" > "$warnings_file" 2>/dev/null || true
350
+
351
+ if [[ -s "$warnings_file" ]]; then
352
+ shellcheck_fixes=$(jq 'length' "$warnings_file" 2>/dev/null || echo "0")
353
+ info "shellcheck found $shellcheck_fixes issues in $target_file"
354
+ fixed=$((fixed + shellcheck_fixes))
355
+ fi
356
+ rm -f "$warnings_file"
357
+ fi
358
+
359
+ # Fix 2: Trailing whitespace
360
+ local trailing_ws
361
+ trailing_ws=$(grep -c '[[:space:]]$' "$target_file" 2>/dev/null || echo "0")
362
+ if [[ $trailing_ws -gt 0 ]]; then
363
+ sed -i '' 's/[[:space:]]*$//' "$target_file"
364
+ info "Removed $trailing_ws lines of trailing whitespace"
365
+ fixed=$((fixed + trailing_ws))
366
+ fi
367
+
368
+ # Fix 3: Ensure final newline
369
+ if [[ -n "$(tail -c1 "$target_file" 2>/dev/null)" ]]; then
370
+ echo "" >> "$target_file"
371
+ info "Added final newline"
372
+ fixed=$((fixed + 1))
373
+ fi
374
+
375
+ # Fix 4: Consistent spacing around operators (simple cases)
376
+ local spacing_fixes=0
377
+ spacing_fixes=$(grep -c '==' "$target_file" 2>/dev/null || echo "0")
378
+ if [[ $spacing_fixes -gt 0 ]]; then
379
+ info "Flagged $spacing_fixes operator spacing cases (manual review recommended)"
380
+ fi
381
+
382
+ echo "$fixed"
383
+ }
384
+
385
+ # ─── Review Subcommand ───────────────────────────────────────────────────────
386
+
387
+ review_changes() {
388
+ local pr_number="${1:-}"
389
+ local review_scope="staged"
390
+
391
+ if [[ -n "$pr_number" ]]; then
392
+ review_scope="pr:$pr_number"
393
+ fi
394
+
395
+ info "Reviewing code changes ($review_scope)..."
396
+
397
+ mkdir -p "${REPO_DIR}/.claude/pipeline-artifacts"
398
+
399
+ local review_output="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"scope\":\"$review_scope\",\"findings\":{}}"
400
+ local total_issues=0
401
+
402
+ # Get changed files
403
+ local changed_files=()
404
+ if [[ "$review_scope" == "staged" ]]; then
405
+ mapfile -t changed_files < <(cd "$REPO_DIR" && git diff --cached --name-only 2>/dev/null || true)
406
+ else
407
+ # For PR: get diff against main
408
+ mapfile -t changed_files < <(cd "$REPO_DIR" && git diff main...HEAD --name-only 2>/dev/null || true)
409
+ fi
410
+
411
+ [[ ${#changed_files[@]} -eq 0 ]] && { success "No changes to review"; return 0; }
412
+
413
+ for file in "${changed_files[@]}"; do
414
+ local file_path="${REPO_DIR}/${file}"
415
+ [[ ! -f "$file_path" ]] && continue
416
+
417
+ info "Analyzing $file..."
418
+
419
+ local smells=()
420
+ local solids=()
421
+ local arch_issues=()
422
+ local style_issues=()
423
+
424
+ mapfile -t smells < <(detect_code_smells "$file_path")
425
+ mapfile -t solids < <(check_solid_principles "$file_path")
426
+ mapfile -t arch_issues < <(check_architecture_boundaries "$file_path")
427
+ mapfile -t style_issues < <(check_style_consistency "$file_path")
428
+
429
+ local file_issues=$((${#smells[@]} + ${#solids[@]} + ${#arch_issues[@]} + ${#style_issues[@]}))
430
+ total_issues=$((total_issues + file_issues))
431
+
432
+ if [[ $file_issues -gt 0 ]]; then
433
+ local file_summary="{\"code_smells\":"
434
+ file_summary+=$(printf '%s\n' "${smells[@]}" | jq -R . | jq -s . 2>/dev/null || echo "[]")
435
+ file_summary+=",\"solid_violations\":"
436
+ file_summary+=$(printf '%s\n' "${solids[@]}" | jq -R . | jq -s . 2>/dev/null || echo "[]")
437
+ file_summary+=",\"architecture_issues\":"
438
+ file_summary+=$(printf '%s\n' "${arch_issues[@]}" | jq -R . | jq -s . 2>/dev/null || echo "[]")
439
+ file_summary+=",\"style_issues\":"
440
+ file_summary+=$(printf '%s\n' "${style_issues[@]}" | jq -R . | jq -s . 2>/dev/null || echo "[]")
441
+ file_summary+="}"
442
+
443
+ review_output=$(echo "$review_output" | jq --arg fname "$file" --argjson summary "$file_summary" \
444
+ '.findings[$fname] = $summary' 2>/dev/null || echo "$review_output")
445
+ fi
446
+ done
447
+
448
+ review_output=$(echo "$review_output" | jq --arg tc "$total_issues" '.total_issues = $tc' 2>/dev/null || echo "$review_output")
449
+
450
+ echo "$review_output" | jq '.' 2>/dev/null || echo "$review_output"
451
+
452
+ mkdir -p "$(dirname "$QUALITY_METRICS_FILE")"
453
+ echo "$review_output" | jq '.' > "$QUALITY_METRICS_FILE" 2>/dev/null || true
454
+
455
+ emit_event "code_review.complete" "scope=$review_scope" "total_issues=$total_issues" "file_count=${#changed_files[@]}"
456
+
457
+ [[ $total_issues -gt 0 ]] && warn "Review found $total_issues issues"
458
+ [[ $total_issues -eq 0 ]] && success "No issues found"
459
+ }
460
+
461
+ # ─── Scan Subcommand ────────────────────────────────────────────────────────
462
+
463
+ scan_codebase() {
464
+ info "Running full codebase quality scan..."
465
+
466
+ local scan_output="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"files\":[]}"
467
+ local total_issues=0
468
+
469
+ find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | while read -r file; do
470
+ local file_rel="${file#$REPO_DIR/}"
471
+ local smells=0
472
+ local solids=0
473
+ local arch_issues=0
474
+
475
+ smells=$(detect_code_smells "$file" 2>/dev/null | wc -l || echo 0)
476
+ solids=$(check_solid_principles "$file" 2>/dev/null | wc -l || echo 0)
477
+ arch_issues=$(check_architecture_boundaries "$file" 2>/dev/null | wc -l || echo 0)
478
+
479
+ local file_issues=$((smells + solids + arch_issues))
480
+ total_issues=$((total_issues + file_issues))
481
+
482
+ if [[ $file_issues -gt 0 ]]; then
483
+ echo "$file_rel: $file_issues issues (smells: $smells, SOLID: $solids, arch: $arch_issues)"
484
+ fi
485
+ done
486
+
487
+ success "Scan complete: $total_issues total issues found"
488
+ emit_event "code_review.scan" "total_issues=$total_issues"
489
+ }
490
+
491
+ # ─── Complexity Subcommand ──────────────────────────────────────────────────
492
+
493
+ complexity_report() {
494
+ info "Analyzing code complexity metrics..."
495
+
496
+ local complexity_data="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"files\":[]}"
497
+
498
+ find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | while read -r file; do
499
+ local file_metrics
500
+ file_metrics=$(analyze_complexity "$file" 2>/dev/null || echo "{}")
501
+ complexity_data=$(echo "$complexity_data" | jq --argjson metrics "$file_metrics" '.files += [$metrics]' 2>/dev/null || echo "$complexity_data")
502
+
503
+ # Output per-file summary
504
+ echo "$file_metrics" | jq -r '.functions[] | "\(.name): CC=\(.cyclomatic_complexity), LOC=\(.lines), Cog=\(.cognitive_complexity)"' 2>/dev/null || true
505
+ done
506
+
507
+ success "Complexity analysis complete"
508
+ }
509
+
510
+ # ─── Trends Subcommand ──────────────────────────────────────────────────────
511
+
512
+ show_trends() {
513
+ [[ ! -f "$TRENDS_FILE" ]] && { info "No trend data available yet"; return 0; }
514
+
515
+ info "Code Quality Trends:"
516
+ echo ""
517
+
518
+ tail -10 "$TRENDS_FILE" | jq -r '.timestamp + ": " + (.total_issues | tostring) + " issues"' 2>/dev/null || cat "$TRENDS_FILE"
519
+ }
520
+
521
+ # ─── Config Subcommand ──────────────────────────────────────────────────────
522
+
523
+ manage_config() {
524
+ local action="${1:-show}"
525
+
526
+ case "$action" in
527
+ show)
528
+ init_config
529
+ cat "$REVIEW_CONFIG" | jq '.' 2>/dev/null || cat "$REVIEW_CONFIG"
530
+ ;;
531
+ set)
532
+ local key="$2" value="$3"
533
+ init_config
534
+ jq --arg k "$key" --arg v "$value" '.[$k] = $v' "$REVIEW_CONFIG" > "${REVIEW_CONFIG}.tmp"
535
+ mv "${REVIEW_CONFIG}.tmp" "$REVIEW_CONFIG"
536
+ success "Updated $key = $value"
537
+ ;;
538
+ *)
539
+ error "Unknown config action: $action"
540
+ return 1
541
+ ;;
542
+ esac
543
+ }
544
+
545
+ # ─── Help ────────────────────────────────────────────────────────────────────
546
+
547
+ show_help() {
548
+ echo -e "${BOLD}Autonomous Code Review Agent${RESET} — Issue #76"
549
+ echo ""
550
+ echo -e "${BOLD}USAGE${RESET}"
551
+ echo -e " ${CYAN}sw code-review${RESET} <subcommand> [options]"
552
+ echo ""
553
+ echo -e "${BOLD}SUBCOMMANDS${RESET}"
554
+ echo -e " ${CYAN}review${RESET} [--pr N] Review staged changes (or specific PR)"
555
+ echo -e " ${CYAN}scan${RESET} Full codebase quality scan"
556
+ echo -e " ${CYAN}complexity${RESET} Show complexity metrics per function"
557
+ echo -e " ${CYAN}boundaries${RESET} Check architecture boundary violations"
558
+ echo -e " ${CYAN}fix${RESET} Auto-fix simple code quality issues"
559
+ echo -e " ${CYAN}trends${RESET} Show code quality trends over time"
560
+ echo -e " ${CYAN}config${RESET} [show|set K V] Manage review configuration"
561
+ echo -e " ${CYAN}help${RESET} Show this help message"
562
+ echo ""
563
+ echo -e "${BOLD}CHECKS${RESET}"
564
+ echo -e " • Code smells: long functions, deep nesting, duplication, magic numbers"
565
+ echo -e " • SOLID principles: SRP, OCP, LSP, ISP violations"
566
+ echo -e " • Architecture: layer boundaries, provider isolation"
567
+ echo -e " • Complexity: cyclomatic, cognitive, function length"
568
+ echo -e " • Style: consistency in error handling, quotes, indentation"
569
+ echo ""
570
+ echo -e "${BOLD}EXAMPLES${RESET}"
571
+ echo -e " ${DIM}sw code-review review${RESET} # Review staged changes"
572
+ echo -e " ${DIM}sw code-review review --pr 42${RESET} # Review specific PR"
573
+ echo -e " ${DIM}sw code-review complexity${RESET} # Show complexity metrics"
574
+ echo -e " ${DIM}sw code-review fix${RESET} # Auto-fix simple issues"
575
+ echo ""
576
+ }
577
+
578
+ # ─── Main ────────────────────────────────────────────────────────────────────
579
+
580
+ main() {
581
+ local subcommand="${1:-help}"
582
+
583
+ init_config
584
+ load_config
585
+
586
+ case "$subcommand" in
587
+ review)
588
+ shift || true
589
+ local pr_opt=""
590
+ [[ "${1:-}" == "--pr" ]] && pr_opt="${2:-}"
591
+ review_changes "$pr_opt"
592
+ ;;
593
+ scan)
594
+ scan_codebase
595
+ ;;
596
+ complexity)
597
+ complexity_report
598
+ ;;
599
+ boundaries)
600
+ info "Checking architecture boundaries..."
601
+ find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | while read -r file; do
602
+ check_architecture_boundaries "$file" 2>/dev/null | grep . && echo " in $file"
603
+ done
604
+ success "Architecture check complete"
605
+ ;;
606
+ fix)
607
+ info "Auto-fixing simple issues..."
608
+ local fixed_count=0
609
+ find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | while read -r file; do
610
+ local fixes
611
+ fixes=$(auto_fix "$file" 2>/dev/null || echo "0")
612
+ [[ "$fixes" -gt 0 ]] && fixed_count=$((fixed_count + fixes))
613
+ done
614
+ success "Auto-fix complete: $fixed_count issues addressed"
615
+ emit_event "code_review.autofix" "issues_fixed=$fixed_count"
616
+ ;;
617
+ trends)
618
+ show_trends
619
+ ;;
620
+ config)
621
+ shift || true
622
+ manage_config "$@"
623
+ ;;
624
+ help|--help|-h)
625
+ show_help
626
+ ;;
627
+ *)
628
+ error "Unknown subcommand: $subcommand"
629
+ show_help
630
+ return 1
631
+ ;;
632
+ esac
633
+ }
634
+
635
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
636
+ main "$@"
637
+ fi
@@ -8,7 +8,7 @@
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
11
- VERSION="1.9.0"
11
+ VERSION="2.0.0"
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
 
14
14
  # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────