shipwright-cli 1.10.0 → 2.1.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 (121) hide show
  1. package/README.md +221 -55
  2. package/completions/_shipwright +264 -32
  3. package/completions/shipwright.bash +118 -26
  4. package/completions/shipwright.fish +80 -2
  5. package/dashboard/server.ts +208 -0
  6. package/docs/strategy/01-market-research.md +619 -0
  7. package/docs/strategy/02-mission-and-brand.md +587 -0
  8. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  9. package/docs/strategy/QUICK-START.txt +289 -0
  10. package/docs/strategy/README.md +172 -0
  11. package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
  12. package/docs/tmux-research/TMUX-AUDIT.md +925 -0
  13. package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
  14. package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
  15. package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
  16. package/package.json +4 -2
  17. package/scripts/lib/helpers.sh +7 -0
  18. package/scripts/sw +323 -2
  19. package/scripts/sw-activity.sh +500 -0
  20. package/scripts/sw-adaptive.sh +925 -0
  21. package/scripts/sw-adversarial.sh +1 -1
  22. package/scripts/sw-architecture-enforcer.sh +1 -1
  23. package/scripts/sw-auth.sh +613 -0
  24. package/scripts/sw-autonomous.sh +754 -0
  25. package/scripts/sw-changelog.sh +704 -0
  26. package/scripts/sw-checkpoint.sh +1 -1
  27. package/scripts/sw-ci.sh +602 -0
  28. package/scripts/sw-cleanup.sh +1 -1
  29. package/scripts/sw-code-review.sh +698 -0
  30. package/scripts/sw-connect.sh +1 -1
  31. package/scripts/sw-context.sh +605 -0
  32. package/scripts/sw-cost.sh +44 -3
  33. package/scripts/sw-daemon.sh +568 -138
  34. package/scripts/sw-dashboard.sh +1 -1
  35. package/scripts/sw-db.sh +1380 -0
  36. package/scripts/sw-decompose.sh +539 -0
  37. package/scripts/sw-deps.sh +551 -0
  38. package/scripts/sw-developer-simulation.sh +1 -1
  39. package/scripts/sw-discovery.sh +412 -0
  40. package/scripts/sw-docs-agent.sh +539 -0
  41. package/scripts/sw-docs.sh +1 -1
  42. package/scripts/sw-doctor.sh +107 -1
  43. package/scripts/sw-dora.sh +615 -0
  44. package/scripts/sw-durable.sh +710 -0
  45. package/scripts/sw-e2e-orchestrator.sh +535 -0
  46. package/scripts/sw-eventbus.sh +393 -0
  47. package/scripts/sw-feedback.sh +479 -0
  48. package/scripts/sw-fix.sh +1 -1
  49. package/scripts/sw-fleet-discover.sh +567 -0
  50. package/scripts/sw-fleet-viz.sh +404 -0
  51. package/scripts/sw-fleet.sh +8 -1
  52. package/scripts/sw-github-app.sh +596 -0
  53. package/scripts/sw-github-checks.sh +4 -4
  54. package/scripts/sw-github-deploy.sh +1 -1
  55. package/scripts/sw-github-graphql.sh +1 -1
  56. package/scripts/sw-guild.sh +569 -0
  57. package/scripts/sw-heartbeat.sh +1 -1
  58. package/scripts/sw-hygiene.sh +559 -0
  59. package/scripts/sw-incident.sh +656 -0
  60. package/scripts/sw-init.sh +237 -24
  61. package/scripts/sw-instrument.sh +699 -0
  62. package/scripts/sw-intelligence.sh +1 -1
  63. package/scripts/sw-jira.sh +1 -1
  64. package/scripts/sw-launchd.sh +363 -28
  65. package/scripts/sw-linear.sh +1 -1
  66. package/scripts/sw-logs.sh +1 -1
  67. package/scripts/sw-loop.sh +267 -21
  68. package/scripts/sw-memory.sh +18 -1
  69. package/scripts/sw-mission-control.sh +487 -0
  70. package/scripts/sw-model-router.sh +545 -0
  71. package/scripts/sw-otel.sh +596 -0
  72. package/scripts/sw-oversight.sh +764 -0
  73. package/scripts/sw-pipeline-composer.sh +1 -1
  74. package/scripts/sw-pipeline-vitals.sh +1 -1
  75. package/scripts/sw-pipeline.sh +947 -35
  76. package/scripts/sw-pm.sh +758 -0
  77. package/scripts/sw-pr-lifecycle.sh +522 -0
  78. package/scripts/sw-predictive.sh +8 -1
  79. package/scripts/sw-prep.sh +1 -1
  80. package/scripts/sw-ps.sh +1 -1
  81. package/scripts/sw-public-dashboard.sh +798 -0
  82. package/scripts/sw-quality.sh +595 -0
  83. package/scripts/sw-reaper.sh +1 -1
  84. package/scripts/sw-recruit.sh +2248 -0
  85. package/scripts/sw-regression.sh +642 -0
  86. package/scripts/sw-release-manager.sh +736 -0
  87. package/scripts/sw-release.sh +706 -0
  88. package/scripts/sw-remote.sh +1 -1
  89. package/scripts/sw-replay.sh +520 -0
  90. package/scripts/sw-retro.sh +691 -0
  91. package/scripts/sw-scale.sh +444 -0
  92. package/scripts/sw-security-audit.sh +505 -0
  93. package/scripts/sw-self-optimize.sh +1 -1
  94. package/scripts/sw-session.sh +1 -1
  95. package/scripts/sw-setup.sh +263 -127
  96. package/scripts/sw-standup.sh +712 -0
  97. package/scripts/sw-status.sh +44 -2
  98. package/scripts/sw-strategic.sh +806 -0
  99. package/scripts/sw-stream.sh +450 -0
  100. package/scripts/sw-swarm.sh +620 -0
  101. package/scripts/sw-team-stages.sh +511 -0
  102. package/scripts/sw-templates.sh +4 -4
  103. package/scripts/sw-testgen.sh +566 -0
  104. package/scripts/sw-tmux-pipeline.sh +554 -0
  105. package/scripts/sw-tmux-role-color.sh +58 -0
  106. package/scripts/sw-tmux-status.sh +128 -0
  107. package/scripts/sw-tmux.sh +1 -1
  108. package/scripts/sw-trace.sh +485 -0
  109. package/scripts/sw-tracker-github.sh +188 -0
  110. package/scripts/sw-tracker-jira.sh +172 -0
  111. package/scripts/sw-tracker-linear.sh +251 -0
  112. package/scripts/sw-tracker.sh +117 -2
  113. package/scripts/sw-triage.sh +627 -0
  114. package/scripts/sw-upgrade.sh +1 -1
  115. package/scripts/sw-ux.sh +677 -0
  116. package/scripts/sw-webhook.sh +627 -0
  117. package/scripts/sw-widgets.sh +530 -0
  118. package/scripts/sw-worktree.sh +1 -1
  119. package/templates/pipelines/autonomous.json +2 -2
  120. package/tmux/shipwright-overlay.conf +35 -17
  121. package/tmux/tmux.conf +23 -21
@@ -0,0 +1,698 @@
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.1.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
+ # ─── Claude-powered semantic review (logic, race conditions, API usage, requirements) ──
386
+ run_claude_semantic_review() {
387
+ local diff_content="$1"
388
+ local requirements="${2:-}"
389
+ [[ -z "$diff_content" ]] && return 0
390
+ if ! command -v claude &>/dev/null; then
391
+ return 0
392
+ fi
393
+
394
+ local prompt="You are a senior code reviewer. Review this git diff for semantic issues (not just style).
395
+
396
+ Focus on:
397
+ 1. Logic errors and edge cases (off-by-one, null/empty handling, wrong conditions)
398
+ 2. Race conditions and concurrency issues (shared state, ordering, locks)
399
+ 3. Incorrect or unsafe API usage (wrong arguments, missing error handling, deprecated APIs)
400
+ 4. Security issues (injection, auth bypass, sensitive data exposure)
401
+ 5. Requirements alignment: does the change match the intended behavior?
402
+
403
+ For each issue use this format on its own line:
404
+ - **[SEVERITY]** file:line — brief description
405
+
406
+ Severity: Critical, Bug, Security, Warning, Suggestion.
407
+ If no issues found, reply with exactly: Review clean — no semantic issues found.
408
+
409
+ ## Diff
410
+ ${diff_content}
411
+ "
412
+ [[ -n "$requirements" ]] && prompt="${prompt}
413
+
414
+ ## Requirements / intended behavior
415
+ ${requirements}
416
+ "
417
+
418
+ local claude_out
419
+ claude_out=$(claude -p "$prompt" --max-turns 3 2>/dev/null || true)
420
+ [[ -z "$claude_out" ]] && return 0
421
+
422
+ if echo "$claude_out" | grep -qi "Review clean — no semantic issues found"; then
423
+ return 0
424
+ fi
425
+ echo "$claude_out" | grep -oE '\*\*\[?(Critical|Bug|Security|Warning|Suggestion)\]?\*\*[^—]*—[^$]+' 2>/dev/null || \
426
+ echo "$claude_out" | grep -oE '-\s+\*\*[^*]+\*\*[^\n]+' 2>/dev/null || true
427
+ }
428
+
429
+ # ─── Review Subcommand ───────────────────────────────────────────────────────
430
+
431
+ review_changes() {
432
+ local pr_number="${1:-}"
433
+ local review_scope="staged"
434
+
435
+ if [[ -n "$pr_number" ]]; then
436
+ review_scope="pr:$pr_number"
437
+ fi
438
+
439
+ info "Reviewing code changes ($review_scope)..."
440
+
441
+ mkdir -p "${REPO_DIR}/.claude/pipeline-artifacts"
442
+
443
+ local review_output="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"scope\":\"$review_scope\",\"findings\":{}}"
444
+ local total_issues=0
445
+
446
+ # Get changed files
447
+ local changed_files=()
448
+ if [[ "$review_scope" == "staged" ]]; then
449
+ mapfile -t changed_files < <(cd "$REPO_DIR" && git diff --cached --name-only 2>/dev/null || true)
450
+ else
451
+ # For PR: get diff against main
452
+ mapfile -t changed_files < <(cd "$REPO_DIR" && git diff main...HEAD --name-only 2>/dev/null || true)
453
+ fi
454
+
455
+ [[ ${#changed_files[@]} -eq 0 ]] && { success "No changes to review"; return 0; }
456
+
457
+ # Claude-powered semantic review (logic, race conditions, API usage) when available
458
+ local diff_content
459
+ if [[ "$review_scope" == "staged" ]]; then
460
+ diff_content=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null || true)
461
+ else
462
+ diff_content=$(cd "$REPO_DIR" && git diff main...HEAD 2>/dev/null || true)
463
+ fi
464
+ local semantic_issues=()
465
+ if [[ -n "$diff_content" ]] && command -v claude &>/dev/null; then
466
+ info "Running Claude semantic review (logic, race conditions, API usage)..."
467
+ mapfile -t semantic_issues < <(run_claude_semantic_review "$diff_content" "${REVIEW_REQUIREMENTS:-}" || true)
468
+ if [[ ${#semantic_issues[@]} -gt 0 ]]; then
469
+ total_issues=$((total_issues + ${#semantic_issues[@]}))
470
+ review_output=$(echo "$review_output" | jq --argjson arr "$(printf '%s\n' "${semantic_issues[@]}" | jq -R . | jq -s .)" '.semantic_findings = $arr' 2>/dev/null || echo "$review_output")
471
+ fi
472
+ fi
473
+
474
+ for file in "${changed_files[@]}"; do
475
+ local file_path="${REPO_DIR}/${file}"
476
+ [[ ! -f "$file_path" ]] && continue
477
+
478
+ info "Analyzing $file..."
479
+
480
+ local smells=()
481
+ local solids=()
482
+ local arch_issues=()
483
+ local style_issues=()
484
+
485
+ mapfile -t smells < <(detect_code_smells "$file_path")
486
+ mapfile -t solids < <(check_solid_principles "$file_path")
487
+ mapfile -t arch_issues < <(check_architecture_boundaries "$file_path")
488
+ mapfile -t style_issues < <(check_style_consistency "$file_path")
489
+
490
+ local file_issues=$((${#smells[@]} + ${#solids[@]} + ${#arch_issues[@]} + ${#style_issues[@]}))
491
+ total_issues=$((total_issues + file_issues))
492
+
493
+ if [[ $file_issues -gt 0 ]]; then
494
+ local file_summary="{\"code_smells\":"
495
+ file_summary+=$(printf '%s\n' "${smells[@]}" | jq -R . | jq -s . 2>/dev/null || echo "[]")
496
+ file_summary+=",\"solid_violations\":"
497
+ file_summary+=$(printf '%s\n' "${solids[@]}" | jq -R . | jq -s . 2>/dev/null || echo "[]")
498
+ file_summary+=",\"architecture_issues\":"
499
+ file_summary+=$(printf '%s\n' "${arch_issues[@]}" | jq -R . | jq -s . 2>/dev/null || echo "[]")
500
+ file_summary+=",\"style_issues\":"
501
+ file_summary+=$(printf '%s\n' "${style_issues[@]}" | jq -R . | jq -s . 2>/dev/null || echo "[]")
502
+ file_summary+="}"
503
+
504
+ review_output=$(echo "$review_output" | jq --arg fname "$file" --argjson summary "$file_summary" \
505
+ '.findings[$fname] = $summary' 2>/dev/null || echo "$review_output")
506
+ fi
507
+ done
508
+
509
+ review_output=$(echo "$review_output" | jq --arg tc "$total_issues" '.total_issues = $tc' 2>/dev/null || echo "$review_output")
510
+
511
+ echo "$review_output" | jq '.' 2>/dev/null || echo "$review_output"
512
+
513
+ mkdir -p "$(dirname "$QUALITY_METRICS_FILE")"
514
+ echo "$review_output" | jq '.' > "$QUALITY_METRICS_FILE" 2>/dev/null || true
515
+
516
+ emit_event "code_review.complete" "scope=$review_scope" "total_issues=$total_issues" "file_count=${#changed_files[@]}"
517
+
518
+ [[ $total_issues -gt 0 ]] && warn "Review found $total_issues issues"
519
+ [[ $total_issues -eq 0 ]] && success "No issues found"
520
+ }
521
+
522
+ # ─── Scan Subcommand ────────────────────────────────────────────────────────
523
+
524
+ scan_codebase() {
525
+ info "Running full codebase quality scan..."
526
+
527
+ local scan_output="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"files\":[]}"
528
+ local total_issues=0
529
+
530
+ find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | while read -r file; do
531
+ local file_rel="${file#$REPO_DIR/}"
532
+ local smells=0
533
+ local solids=0
534
+ local arch_issues=0
535
+
536
+ smells=$(detect_code_smells "$file" 2>/dev/null | wc -l || echo 0)
537
+ solids=$(check_solid_principles "$file" 2>/dev/null | wc -l || echo 0)
538
+ arch_issues=$(check_architecture_boundaries "$file" 2>/dev/null | wc -l || echo 0)
539
+
540
+ local file_issues=$((smells + solids + arch_issues))
541
+ total_issues=$((total_issues + file_issues))
542
+
543
+ if [[ $file_issues -gt 0 ]]; then
544
+ echo "$file_rel: $file_issues issues (smells: $smells, SOLID: $solids, arch: $arch_issues)"
545
+ fi
546
+ done
547
+
548
+ success "Scan complete: $total_issues total issues found"
549
+ emit_event "code_review.scan" "total_issues=$total_issues"
550
+ }
551
+
552
+ # ─── Complexity Subcommand ──────────────────────────────────────────────────
553
+
554
+ complexity_report() {
555
+ info "Analyzing code complexity metrics..."
556
+
557
+ local complexity_data="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"files\":[]}"
558
+
559
+ find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | while read -r file; do
560
+ local file_metrics
561
+ file_metrics=$(analyze_complexity "$file" 2>/dev/null || echo "{}")
562
+ complexity_data=$(echo "$complexity_data" | jq --argjson metrics "$file_metrics" '.files += [$metrics]' 2>/dev/null || echo "$complexity_data")
563
+
564
+ # Output per-file summary
565
+ echo "$file_metrics" | jq -r '.functions[] | "\(.name): CC=\(.cyclomatic_complexity), LOC=\(.lines), Cog=\(.cognitive_complexity)"' 2>/dev/null || true
566
+ done
567
+
568
+ success "Complexity analysis complete"
569
+ }
570
+
571
+ # ─── Trends Subcommand ──────────────────────────────────────────────────────
572
+
573
+ show_trends() {
574
+ [[ ! -f "$TRENDS_FILE" ]] && { info "No trend data available yet"; return 0; }
575
+
576
+ info "Code Quality Trends:"
577
+ echo ""
578
+
579
+ tail -10 "$TRENDS_FILE" | jq -r '.timestamp + ": " + (.total_issues | tostring) + " issues"' 2>/dev/null || cat "$TRENDS_FILE"
580
+ }
581
+
582
+ # ─── Config Subcommand ──────────────────────────────────────────────────────
583
+
584
+ manage_config() {
585
+ local action="${1:-show}"
586
+
587
+ case "$action" in
588
+ show)
589
+ init_config
590
+ cat "$REVIEW_CONFIG" | jq '.' 2>/dev/null || cat "$REVIEW_CONFIG"
591
+ ;;
592
+ set)
593
+ local key="$2" value="$3"
594
+ init_config
595
+ jq --arg k "$key" --arg v "$value" '.[$k] = $v' "$REVIEW_CONFIG" > "${REVIEW_CONFIG}.tmp"
596
+ mv "${REVIEW_CONFIG}.tmp" "$REVIEW_CONFIG"
597
+ success "Updated $key = $value"
598
+ ;;
599
+ *)
600
+ error "Unknown config action: $action"
601
+ return 1
602
+ ;;
603
+ esac
604
+ }
605
+
606
+ # ─── Help ────────────────────────────────────────────────────────────────────
607
+
608
+ show_help() {
609
+ echo -e "${BOLD}Autonomous Code Review Agent${RESET} — Issue #76"
610
+ echo ""
611
+ echo -e "${BOLD}USAGE${RESET}"
612
+ echo -e " ${CYAN}sw code-review${RESET} <subcommand> [options]"
613
+ echo ""
614
+ echo -e "${BOLD}SUBCOMMANDS${RESET}"
615
+ echo -e " ${CYAN}review${RESET} [--pr N] Review staged changes (or specific PR)"
616
+ echo -e " ${CYAN}scan${RESET} Full codebase quality scan"
617
+ echo -e " ${CYAN}complexity${RESET} Show complexity metrics per function"
618
+ echo -e " ${CYAN}boundaries${RESET} Check architecture boundary violations"
619
+ echo -e " ${CYAN}fix${RESET} Auto-fix simple code quality issues"
620
+ echo -e " ${CYAN}trends${RESET} Show code quality trends over time"
621
+ echo -e " ${CYAN}config${RESET} [show|set K V] Manage review configuration"
622
+ echo -e " ${CYAN}help${RESET} Show this help message"
623
+ echo ""
624
+ echo -e "${BOLD}CHECKS${RESET}"
625
+ echo -e " • Code smells: long functions, deep nesting, duplication, magic numbers"
626
+ echo -e " • SOLID principles: SRP, OCP, LSP, ISP violations"
627
+ echo -e " • Architecture: layer boundaries, provider isolation"
628
+ echo -e " • Complexity: cyclomatic, cognitive, function length"
629
+ echo -e " • Style: consistency in error handling, quotes, indentation"
630
+ echo ""
631
+ echo -e "${BOLD}EXAMPLES${RESET}"
632
+ echo -e " ${DIM}sw code-review review${RESET} # Review staged changes"
633
+ echo -e " ${DIM}sw code-review review --pr 42${RESET} # Review specific PR"
634
+ echo -e " ${DIM}sw code-review complexity${RESET} # Show complexity metrics"
635
+ echo -e " ${DIM}sw code-review fix${RESET} # Auto-fix simple issues"
636
+ echo ""
637
+ }
638
+
639
+ # ─── Main ────────────────────────────────────────────────────────────────────
640
+
641
+ main() {
642
+ local subcommand="${1:-help}"
643
+
644
+ init_config
645
+ load_config
646
+
647
+ case "$subcommand" in
648
+ review)
649
+ shift || true
650
+ local pr_opt=""
651
+ [[ "${1:-}" == "--pr" ]] && pr_opt="${2:-}"
652
+ review_changes "$pr_opt"
653
+ ;;
654
+ scan)
655
+ scan_codebase
656
+ ;;
657
+ complexity)
658
+ complexity_report
659
+ ;;
660
+ boundaries)
661
+ info "Checking architecture boundaries..."
662
+ find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | while read -r file; do
663
+ check_architecture_boundaries "$file" 2>/dev/null | grep . && echo " in $file"
664
+ done
665
+ success "Architecture check complete"
666
+ ;;
667
+ fix)
668
+ info "Auto-fixing simple issues..."
669
+ local fixed_count=0
670
+ find "$REPO_DIR/scripts" -name "*.sh" -type f 2>/dev/null | while read -r file; do
671
+ local fixes
672
+ fixes=$(auto_fix "$file" 2>/dev/null || echo "0")
673
+ [[ "$fixes" -gt 0 ]] && fixed_count=$((fixed_count + fixes))
674
+ done
675
+ success "Auto-fix complete: $fixed_count issues addressed"
676
+ emit_event "code_review.autofix" "issues_fixed=$fixed_count"
677
+ ;;
678
+ trends)
679
+ show_trends
680
+ ;;
681
+ config)
682
+ shift || true
683
+ manage_config "$@"
684
+ ;;
685
+ help|--help|-h)
686
+ show_help
687
+ ;;
688
+ *)
689
+ error "Unknown subcommand: $subcommand"
690
+ show_help
691
+ return 1
692
+ ;;
693
+ esac
694
+ }
695
+
696
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
697
+ main "$@"
698
+ fi