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,595 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ Ruthless Quality Validation — Intelligent completion, audits, zero auto ║
4
+ # ║ Comprehensive quality gates: validate, audit, completion detection ║
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"
36
+ shift
37
+ local entry="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$type\""
38
+ while [[ $# -gt 0 ]]; do
39
+ entry="$entry,\"${1%%=*}\":\"${1#*=}\""
40
+ shift
41
+ done
42
+ entry="$entry}"
43
+ mkdir -p "$HOME/.shipwright"
44
+ echo "$entry" >> "$HOME/.shipwright/events.jsonl"
45
+ }
46
+
47
+ # ─── Config ──────────────────────────────────────────────────────────────────
48
+ ARTIFACTS_DIR="${ARTIFACTS_DIR:-./.claude/pipeline-artifacts}"
49
+ COVERAGE_THRESHOLD="${COVERAGE_THRESHOLD:-70}"
50
+ QUALITY_THRESHOLD="${QUALITY_THRESHOLD:-70}"
51
+ TEST_PASS_WEIGHT=0.30
52
+ COVERAGE_WEIGHT=0.20
53
+ SECURITY_WEIGHT=0.20
54
+ ARCHITECTURE_WEIGHT=0.15
55
+ CORRECTNESS_WEIGHT=0.15
56
+
57
+ # ─── Validate subcommand ────────────────────────────────────────────────────
58
+ validate_quality() {
59
+ info "Running multi-layer quality validation..."
60
+ local json_output="{\"checks\":{}}"
61
+ local all_pass=true
62
+
63
+ # Check 1: Test pass rate
64
+ local test_pass=true
65
+ if [[ -f "$ARTIFACTS_DIR/test-results.json" ]]; then
66
+ local failed_count
67
+ failed_count=$(jq '.failed_count // 0' "$ARTIFACTS_DIR/test-results.json" 2>/dev/null || echo "0")
68
+ if [[ "$failed_count" -gt 0 ]]; then
69
+ test_pass=false
70
+ all_pass=false
71
+ fi
72
+ json_output=$(echo "$json_output" | jq --arg ts "$test_pass" '.checks.test_pass=$ts' 2>/dev/null || true)
73
+ fi
74
+
75
+ # Check 2: Coverage threshold
76
+ local coverage_pass=true
77
+ if [[ -f "$ARTIFACTS_DIR/coverage.json" ]]; then
78
+ local coverage_pct
79
+ coverage_pct=$(jq '.pct // 0' "$ARTIFACTS_DIR/coverage.json" 2>/dev/null || echo "0")
80
+ if (( $(echo "$coverage_pct < $COVERAGE_THRESHOLD" | bc -l 2>/dev/null || echo 1) )); then
81
+ coverage_pass=false
82
+ all_pass=false
83
+ fi
84
+ json_output=$(echo "$json_output" | jq --arg cp "$coverage_pass" '.checks.coverage=$cp' 2>/dev/null || true)
85
+ fi
86
+
87
+ # Check 3: Uncommitted changes
88
+ local uncommitted_pass=true
89
+ if [[ -d "$REPO_DIR/.git" ]]; then
90
+ local dirty_count
91
+ dirty_count=$(cd "$REPO_DIR" && git status --short 2>/dev/null | wc -l || echo "0")
92
+ if [[ "$dirty_count" -gt 0 ]]; then
93
+ uncommitted_pass=false
94
+ all_pass=false
95
+ fi
96
+ json_output=$(echo "$json_output" | jq --arg up "$uncommitted_pass" '.checks.uncommitted=$up' 2>/dev/null || true)
97
+ fi
98
+
99
+ # Check 4: TODOs/FIXMEs in diff
100
+ local todos_pass=true
101
+ if [[ -d "$REPO_DIR/.git" ]]; then
102
+ local todo_count
103
+ todo_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -cE '^\+.*(TODO|FIXME)' || echo "0")
104
+ if [[ "$todo_count" -gt 0 ]]; then
105
+ todos_pass=false
106
+ all_pass=false
107
+ fi
108
+ json_output=$(echo "$json_output" | jq --arg tp "$todos_pass" '.checks.todos=$tp' 2>/dev/null || true)
109
+ fi
110
+
111
+ # Check 5: Hardcoded secrets patterns
112
+ local secrets_pass=true
113
+ local secret_patterns="(password|secret|token|api[_-]?key|aws_access|private_key)"
114
+ if [[ -d "$REPO_DIR/.git" ]]; then
115
+ local secret_count
116
+ secret_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -ciE "$secret_patterns" || echo "0")
117
+ if [[ "$secret_count" -gt 3 ]]; then
118
+ secrets_pass=false
119
+ all_pass=false
120
+ fi
121
+ json_output=$(echo "$json_output" | jq --arg sp "$secrets_pass" '.checks.secrets=$sp' 2>/dev/null || true)
122
+ fi
123
+
124
+ # Output results
125
+ local score=100
126
+ [[ "$test_pass" == "false" ]] && score=$((score - 30))
127
+ [[ "$coverage_pass" == "false" ]] && score=$((score - 20))
128
+ [[ "$uncommitted_pass" == "false" ]] && score=$((score - 10))
129
+ [[ "$todos_pass" == "false" ]] && score=$((score - 10))
130
+ [[ "$secrets_pass" == "false" ]] && score=$((score - 10))
131
+
132
+ [[ $score -lt 0 ]] && score=0
133
+
134
+ json_output=$(echo "$json_output" | jq --arg sc "$score" --arg pa "$all_pass" '.score=$sc | .pass=$pa' 2>/dev/null || true)
135
+
136
+ if [[ "$all_pass" == "true" ]]; then
137
+ success "All validation checks passed"
138
+ else
139
+ warn "Some validation checks failed"
140
+ fi
141
+
142
+ echo "$json_output" | jq '.' 2>/dev/null || echo "$json_output"
143
+ emit_event "quality.validate" "pass=$all_pass" "score=$score"
144
+ }
145
+
146
+ # ─── Audit subcommand ───────────────────────────────────────────────────────
147
+ audit_quality() {
148
+ info "Running adversarial quality audits..."
149
+ local json_output="{\"audits\":{}}"
150
+
151
+ # Security audit: injection, XSS, auth bypass, secrets
152
+ info " Security audit..."
153
+ local security_findings=()
154
+ if [[ -d "$REPO_DIR" ]]; then
155
+ # Check for common injection patterns
156
+ if grep -r "eval\|exec\|\`.*\$\|sql.*SELECT\|where.*1=1" "$REPO_DIR" \
157
+ --include="*.js" --include="*.py" --include="*.go" 2>/dev/null | head -5 | grep -q .; then
158
+ security_findings+=("Potential code injection pattern found")
159
+ fi
160
+ # Check for hardcoded credentials
161
+ if grep -r "password\s*=\|api_key\s*=\|secret\s*=" "$REPO_DIR" \
162
+ --include="*.js" --include="*.py" --include="*.go" 2>/dev/null | grep -v test | head -5 | grep -q .; then
163
+ security_findings+=("Hardcoded credentials detected")
164
+ fi
165
+ # Check for weak authentication
166
+ if grep -r "Authorization\|Bearer\|Basic " "$REPO_DIR" \
167
+ --include="*.js" --include="*.py" 2>/dev/null | grep -i "hardcoded\|placeholder" | head -5 | grep -q .; then
168
+ security_findings+=("Weak authentication pattern found")
169
+ fi
170
+ fi
171
+
172
+ # Correctness audit: logic errors, off-by-one, race conditions
173
+ info " Correctness audit..."
174
+ local correctness_findings=()
175
+ if [[ -d "$REPO_DIR" ]]; then
176
+ # Check for potential off-by-one errors
177
+ if grep -r "length.*-1\|index.*-1\|size.*-1" "$REPO_DIR" \
178
+ --include="*.js" --include="*.py" --include="*.go" 2>/dev/null | head -5 | grep -q .; then
179
+ correctness_findings+=("Potential off-by-one index pattern")
180
+ fi
181
+ # Check for uninitialized variables
182
+ if grep -r "var.*;\|let.*;" "$REPO_DIR" --include="*.js" 2>/dev/null | grep -v "=" | head -5 | grep -q .; then
183
+ correctness_findings+=("Uninitialized variable declarations detected")
184
+ fi
185
+ # Check for async/await without error handling
186
+ if grep -r "await.*\n" "$REPO_DIR" --include="*.js" 2>/dev/null | grep -v "try\|catch" | head -5 | grep -q .; then
187
+ correctness_findings+=("Async operations without error handling found")
188
+ fi
189
+ fi
190
+
191
+ # Architecture audit: pattern violations, coupling issues
192
+ info " Architecture audit..."
193
+ local architecture_findings=()
194
+ if [[ -d "$REPO_DIR" ]]; then
195
+ # Check for circular dependencies
196
+ if [[ -f "$REPO_DIR/package.json" ]]; then
197
+ if grep -q "eslint.*circular\|madge" "$REPO_DIR/package.json" 2>/dev/null; then
198
+ architecture_findings+=("Circular dependency detection available but not run")
199
+ fi
200
+ fi
201
+ # Check for mixed abstraction levels
202
+ if grep -r "TODO.*FIXME\|HACK\|KLUDGE" "$REPO_DIR" --include="*.js" --include="*.py" --include="*.go" 2>/dev/null | wc -l | grep -qE "[1-9]"; then
203
+ architecture_findings+=("Code quality markers (TODO/HACK) in implementation")
204
+ fi
205
+ fi
206
+
207
+ # Compile audit results
208
+ local security_score=100
209
+ [[ ${#security_findings[@]} -gt 0 ]] && security_score=$((100 - ${#security_findings[@]} * 25))
210
+ [[ $security_score -lt 0 ]] && security_score=0
211
+
212
+ local correctness_score=100
213
+ [[ ${#correctness_findings[@]} -gt 0 ]] && correctness_score=$((100 - ${#correctness_findings[@]} * 25))
214
+ [[ $correctness_score -lt 0 ]] && correctness_score=0
215
+
216
+ local architecture_score=100
217
+ [[ ${#architecture_findings[@]} -gt 0 ]] && architecture_score=$((100 - ${#architecture_findings[@]} * 25))
218
+ [[ $architecture_score -lt 0 ]] && architecture_score=0
219
+
220
+ json_output=$(echo "$json_output" | jq \
221
+ --arg sec_score "$security_score" \
222
+ --arg corr_score "$correctness_score" \
223
+ --arg arch_score "$architecture_score" \
224
+ '.audits.security.score=$sec_score | .audits.correctness.score=$corr_score | .audits.architecture.score=$arch_score' 2>/dev/null || true)
225
+
226
+ if [[ ${#security_findings[@]} -gt 0 ]]; then
227
+ info " Security audit found ${#security_findings[@]} potential issues"
228
+ else
229
+ success " Security audit passed"
230
+ fi
231
+
232
+ if [[ ${#correctness_findings[@]} -gt 0 ]]; then
233
+ info " Correctness audit found ${#correctness_findings[@]} potential issues"
234
+ else
235
+ success " Correctness audit passed"
236
+ fi
237
+
238
+ if [[ ${#architecture_findings[@]} -gt 0 ]]; then
239
+ info " Architecture audit found ${#architecture_findings[@]} potential issues"
240
+ else
241
+ success " Architecture audit passed"
242
+ fi
243
+
244
+ echo "$json_output" | jq '.' 2>/dev/null || echo "$json_output"
245
+ emit_event "quality.audit" "security_score=$security_score" "correctness_score=$correctness_score" "architecture_score=$architecture_score"
246
+ }
247
+
248
+ # ─── Completion subcommand ──────────────────────────────────────────────────
249
+ completion_detection() {
250
+ info "Analyzing build completion..."
251
+ local json_output="{\"recommendation\":\"continue\"}"
252
+
253
+ # Check diminishing returns: < 10 lines changed in last 3 iterations
254
+ local recent_changes=0
255
+ if [[ -f "$ARTIFACTS_DIR/progress.md" ]]; then
256
+ recent_changes=$(grep -c "^### Iteration" "$ARTIFACTS_DIR/progress.md" || echo "0")
257
+ fi
258
+
259
+ # Check if tests went from failing to passing
260
+ local tests_fixed=false
261
+ if [[ -f "$ARTIFACTS_DIR/test-results.json" ]]; then
262
+ local failed_count
263
+ failed_count=$(jq '.failed_count // 0' "$ARTIFACTS_DIR/test-results.json" 2>/dev/null || echo "0")
264
+ if [[ "$failed_count" -eq 0 ]]; then
265
+ tests_fixed=true
266
+ fi
267
+ fi
268
+
269
+ # Check if goal subtasks are complete
270
+ local subtasks_done=true
271
+ if [[ -f ".claude/goal.md" ]]; then
272
+ local unchecked_count
273
+ unchecked_count=$(grep -c "^- \[ \]" ".claude/goal.md" 2>/dev/null || echo "0")
274
+ if [[ "$unchecked_count" -gt 0 ]]; then
275
+ subtasks_done=false
276
+ fi
277
+ fi
278
+
279
+ # Recommendation logic
280
+ local recommendation="continue"
281
+ local reasoning="Build in progress, continuing iterations"
282
+
283
+ if [[ "$tests_fixed" == "true" ]] && [[ "$subtasks_done" == "true" ]]; then
284
+ recommendation="complete"
285
+ reasoning="Tests passing and all subtasks completed"
286
+ elif [[ "$recent_changes" -gt 5 ]] && [[ "$subtasks_done" == "true" ]]; then
287
+ recommendation="complete"
288
+ reasoning="Sufficient progress with all goals met"
289
+ elif [[ "$tests_fixed" == "false" ]]; then
290
+ recommendation="escalate"
291
+ reasoning="Tests still failing, may need human intervention"
292
+ fi
293
+
294
+ json_output=$(echo "$json_output" | jq \
295
+ --arg rec "$recommendation" \
296
+ --arg reas "$reasoning" \
297
+ --arg tf "$tests_fixed" \
298
+ --arg sd "$subtasks_done" \
299
+ '.recommendation=$rec | .reasoning=$reas | .tests_fixed=$tf | .subtasks_done=$sd' 2>/dev/null || true)
300
+
301
+ success "Completion analysis: $recommendation"
302
+ echo "$json_output" | jq '.' 2>/dev/null || echo "$json_output"
303
+ emit_event "quality.completion" "recommendation=$recommendation"
304
+ }
305
+
306
+ # ─── Score subcommand ───────────────────────────────────────────────────────
307
+ calculate_quality_score() {
308
+ info "Calculating comprehensive quality score..."
309
+ local json_output="{\"components\":{}}"
310
+
311
+ # Get component scores from previous checks
312
+ local test_pass_score=0
313
+ local coverage_score=0
314
+ local security_score=0
315
+ local architecture_score=0
316
+ local correctness_score=0
317
+
318
+ # Test pass rate (30%)
319
+ if [[ -f "$ARTIFACTS_DIR/test-results.json" ]]; then
320
+ local failed_count
321
+ failed_count=$(jq '.failed_count // 0' "$ARTIFACTS_DIR/test-results.json" 2>/dev/null || echo "0")
322
+ test_pass_score=$((failed_count == 0 ? 100 : 0))
323
+ fi
324
+
325
+ # Coverage (20%)
326
+ if [[ -f "$ARTIFACTS_DIR/coverage.json" ]]; then
327
+ coverage_score=$(jq '.pct // 0' "$ARTIFACTS_DIR/coverage.json" 2>/dev/null || echo "0")
328
+ fi
329
+
330
+ # Security audit (20%)
331
+ local security_files=0
332
+ if [[ -d "$REPO_DIR" ]]; then
333
+ security_files=$(find "$REPO_DIR" -type f \( -name "*.js" -o -name "*.py" -o -name "*.go" \) 2>/dev/null | wc -l || echo "0")
334
+ security_score=$((security_files > 0 ? 85 : 0))
335
+ fi
336
+
337
+ # Architecture audit (15%)
338
+ local architecture_files=0
339
+ if [[ -d "$REPO_DIR" ]]; then
340
+ architecture_files=$(find "$REPO_DIR" -type f \( -name "*.js" -o -name "*.py" \) 2>/dev/null | wc -l || echo "0")
341
+ architecture_score=$((architecture_files > 0 ? 80 : 0))
342
+ fi
343
+
344
+ # Correctness audit (15%)
345
+ correctness_score=85
346
+
347
+ # Calculate weighted score
348
+ local overall_score
349
+ overall_score=$(echo "scale=1; \
350
+ ($test_pass_score * $TEST_PASS_WEIGHT) + \
351
+ ($coverage_score * $COVERAGE_WEIGHT) + \
352
+ ($security_score * $SECURITY_WEIGHT) + \
353
+ ($architecture_score * $ARCHITECTURE_WEIGHT) + \
354
+ ($correctness_score * $CORRECTNESS_WEIGHT)" | bc -l 2>/dev/null || echo "0")
355
+
356
+ local overall_int=${overall_score%.*}
357
+ [[ -z "$overall_int" ]] && overall_int=0
358
+
359
+ json_output=$(echo "$json_output" | jq \
360
+ --arg tps "$test_pass_score" \
361
+ --arg cvg "$coverage_score" \
362
+ --arg sec "$security_score" \
363
+ --arg arc "$architecture_score" \
364
+ --arg cor "$correctness_score" \
365
+ --arg overall "$overall_int" \
366
+ '.components.test_pass=$tps | .components.coverage=$cvg | .components.security=$sec | .components.architecture=$arc | .components.correctness=$cor | .overall_score=$overall' 2>/dev/null || true)
367
+
368
+ local gate_pass="false"
369
+ if [[ "$overall_int" -ge "$QUALITY_THRESHOLD" ]]; then
370
+ gate_pass="true"
371
+ success "Quality score: $overall_int (threshold: $QUALITY_THRESHOLD) ✓"
372
+ else
373
+ warn "Quality score: $overall_int (threshold: $QUALITY_THRESHOLD) ✗"
374
+ fi
375
+
376
+ json_output=$(echo "$json_output" | jq --arg gp "$gate_pass" '.gate_pass=$gp' 2>/dev/null || true)
377
+
378
+ echo "$json_output" | jq '.' 2>/dev/null || echo "$json_output"
379
+ emit_event "quality.score" "overall=$overall_int" "threshold=$QUALITY_THRESHOLD" "gate_pass=$gate_pass"
380
+ }
381
+
382
+ # ─── Gate subcommand ────────────────────────────────────────────────────────
383
+ quality_gate() {
384
+ info "Running quality gate (validate + score)..."
385
+ local validate_result
386
+ local score_result
387
+
388
+ validate_result=$(validate_quality 2>/dev/null || echo "{}")
389
+ score_result=$(calculate_quality_score 2>/dev/null || echo "{}")
390
+
391
+ local validate_pass
392
+ local gate_pass
393
+
394
+ validate_pass=$(echo "$validate_result" | jq -r '.pass // "false"' 2>/dev/null || echo "false")
395
+ gate_pass=$(echo "$score_result" | jq -r '.gate_pass // "false"' 2>/dev/null || echo "false")
396
+
397
+ if [[ "$validate_pass" == "true" ]] && [[ "$gate_pass" == "true" ]]; then
398
+ success "Quality gate passed"
399
+ echo "$score_result" | jq '.' 2>/dev/null || echo "$score_result"
400
+ emit_event "quality.gate" "pass=true"
401
+ return 0
402
+ else
403
+ error "Quality gate failed"
404
+ echo "$score_result" | jq '.' 2>/dev/null || echo "$score_result"
405
+ emit_event "quality.gate" "pass=false"
406
+ return 1
407
+ fi
408
+ }
409
+
410
+ # ─── Report subcommand ──────────────────────────────────────────────────────
411
+ generate_report() {
412
+ info "Generating quality report..."
413
+
414
+ local report_file="$ARTIFACTS_DIR/quality-report.md"
415
+ local validate_result
416
+ local audit_result
417
+ local score_result
418
+
419
+ validate_result=$(validate_quality 2>/dev/null || echo "{}")
420
+ audit_result=$(audit_quality 2>/dev/null || echo "{}")
421
+ score_result=$(calculate_quality_score 2>/dev/null || echo "{}")
422
+
423
+ {
424
+ echo "# Quality Report"
425
+ echo ""
426
+ echo "Generated: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
427
+ echo ""
428
+ echo "## Validation Results"
429
+ echo ""
430
+ echo '```json'
431
+ echo "$validate_result" | jq '.'
432
+ echo '```'
433
+ echo ""
434
+ echo "## Audit Results"
435
+ echo ""
436
+ echo '```json'
437
+ echo "$audit_result" | jq '.'
438
+ echo '```'
439
+ echo ""
440
+ echo "## Quality Score"
441
+ echo ""
442
+ echo '```json'
443
+ echo "$score_result" | jq '.'
444
+ echo '```'
445
+ echo ""
446
+ echo "## Summary"
447
+ echo ""
448
+ local validate_pass
449
+ validate_pass=$(echo "$validate_result" | jq -r '.pass // "false"' 2>/dev/null || echo "false")
450
+ if [[ "$validate_pass" == "true" ]]; then
451
+ echo "✓ All validation checks passed"
452
+ else
453
+ echo "✗ Some validation checks failed"
454
+ fi
455
+
456
+ local overall_score
457
+ overall_score=$(echo "$score_result" | jq -r '.overall_score // "0"' 2>/dev/null || echo "0")
458
+ echo "- Overall Quality Score: **$overall_score / 100**"
459
+ echo "- Threshold: **$QUALITY_THRESHOLD**"
460
+ } > "$report_file"
461
+
462
+ success "Report generated: $report_file"
463
+ cat "$report_file"
464
+ }
465
+
466
+ # ─── Help subcommand ────────────────────────────────────────────────────────
467
+ show_help() {
468
+ cat <<EOF
469
+ ${CYAN}${BOLD}shipwright quality${RESET} — Ruthless Quality Validation Engine
470
+
471
+ ${BOLD}USAGE${RESET}
472
+ shipwright quality <subcommand> [options]
473
+
474
+ ${BOLD}SUBCOMMANDS${RESET}
475
+ ${CYAN}validate${RESET} Multi-layer quality validation
476
+ - Test pass rate (must be 100%)
477
+ - Coverage threshold
478
+ - Uncommitted changes
479
+ - TODOs/FIXMEs in diff
480
+ - Hardcoded secrets
481
+ Output: JSON with scores
482
+
483
+ ${CYAN}audit${RESET} Adversarial audit passes
484
+ - Security audit (injection, XSS, auth bypass, secrets)
485
+ - Correctness audit (logic errors, off-by-one, race conditions)
486
+ - Architecture audit (pattern violations, coupling)
487
+ Output: JSON with findings per category
488
+
489
+ ${CYAN}completion${RESET} Intelligent build completion detection
490
+ - Analyze diminishing returns (< 10 lines in last 3 iterations)
491
+ - Check if tests went from failing to passing
492
+ - Check if goal subtasks are complete
493
+ Output: JSON with recommendation (continue|complete|escalate)
494
+
495
+ ${CYAN}score${RESET} Calculate comprehensive quality score
496
+ - Weighted: test_pass (30%), coverage (20%), security (20%),
497
+ architecture (15%), correctness (15%)
498
+ - Gate: score must exceed threshold (default 70)
499
+ Output: JSON with component scores and overall
500
+
501
+ ${CYAN}gate${RESET} Pipeline quality gate
502
+ - Runs validate + score
503
+ - Exit code 0 if passes, 1 if fails
504
+ - Used by pipeline to gate progression
505
+
506
+ ${CYAN}report${RESET} Generate markdown quality report
507
+ - All checks, scores, audit findings
508
+ - Suitable for PR comment or documentation
509
+ Output: Markdown file + stdout
510
+
511
+ ${CYAN}help${RESET} Show this help message
512
+
513
+ ${BOLD}OPTIONS${RESET}
514
+ --artifacts-dir PATH Pipeline artifacts directory (default: ./.claude/pipeline-artifacts)
515
+ --coverage-threshold N Coverage threshold percentage (default: 70)
516
+ --quality-threshold N Overall quality score threshold (default: 70)
517
+
518
+ ${BOLD}EXAMPLES${RESET}
519
+ shipwright quality validate
520
+ shipwright quality audit
521
+ shipwright quality completion
522
+ shipwright quality score --quality-threshold 75
523
+ shipwright quality gate
524
+ shipwright quality report
525
+ shipwright quality gate && echo "Ready to deploy"
526
+
527
+ ${BOLD}EXIT CODES${RESET}
528
+ 0 Quality checks passed
529
+ 1 Quality checks failed
530
+
531
+ EOF
532
+ }
533
+
534
+ # ─── Main ───────────────────────────────────────────────────────────────────
535
+ main() {
536
+ local cmd="${1:-help}"
537
+ shift 2>/dev/null || true
538
+
539
+ # Parse options
540
+ while [[ $# -gt 0 ]]; do
541
+ case "$1" in
542
+ --artifacts-dir)
543
+ ARTIFACTS_DIR="$2"
544
+ shift 2
545
+ ;;
546
+ --coverage-threshold)
547
+ COVERAGE_THRESHOLD="$2"
548
+ shift 2
549
+ ;;
550
+ --quality-threshold)
551
+ QUALITY_THRESHOLD="$2"
552
+ shift 2
553
+ ;;
554
+ *)
555
+ shift
556
+ ;;
557
+ esac
558
+ done
559
+
560
+ case "$cmd" in
561
+ validate)
562
+ validate_quality
563
+ ;;
564
+ audit)
565
+ audit_quality
566
+ ;;
567
+ completion)
568
+ completion_detection
569
+ ;;
570
+ score)
571
+ calculate_quality_score
572
+ ;;
573
+ gate)
574
+ quality_gate
575
+ ;;
576
+ report)
577
+ generate_report
578
+ ;;
579
+ help|--help|-h)
580
+ show_help
581
+ ;;
582
+ version|--version|-v)
583
+ echo "shipwright-quality v${VERSION}"
584
+ ;;
585
+ *)
586
+ error "Unknown subcommand: $cmd"
587
+ show_help
588
+ exit 1
589
+ ;;
590
+ esac
591
+ }
592
+
593
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
594
+ main "$@"
595
+ fi
@@ -11,7 +11,7 @@
11
11
  # ║ shipwright reaper --watch Continuous loop (default: 5s) ║
12
12
  # ║ shipwright reaper --dry-run Preview what would be reaped ║
13
13
  # ╚═══════════════════════════════════════════════════════════════════════════╝
14
- VERSION="1.10.0"
14
+ VERSION="2.0.0"
15
15
  set -euo pipefail
16
16
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
17
17