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.
- package/README.md +114 -36
- package/completions/_shipwright +212 -32
- package/completions/shipwright.bash +97 -25
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/package.json +4 -2
- package/scripts/sw +208 -1
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +664 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +637 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +432 -130
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +540 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +59 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +471 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +617 -0
- package/scripts/sw-init.sh +88 -1
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +64 -3
- package/scripts/sw-memory.sh +1 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +689 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +687 -24
- package/scripts/sw-pm.sh +693 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +1 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +573 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +658 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +583 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +515 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +603 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- 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
|
package/scripts/sw-reaper.sh
CHANGED
|
@@ -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="
|
|
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
|
|