shipwright-cli 1.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/post-tool-use.sh +12 -5
- 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 +217 -2
- 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 +79 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +192 -7
- 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 +812 -138
- 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 +366 -31
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +507 -51
- package/scripts/sw-memory.sh +198 -3
- 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 +8 -8
- package/scripts/sw-pipeline-vitals.sh +1096 -0
- package/scripts/sw-pipeline.sh +2451 -180
- 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 +4 -3
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +5 -3
- 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 +109 -8
- package/scripts/sw-session.sh +31 -9
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +192 -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
- package/templates/pipelines/autonomous.json +8 -1
- package/templates/pipelines/cost-aware.json +21 -0
- package/templates/pipelines/deployed.json +40 -6
- package/templates/pipelines/enterprise.json +16 -2
- package/templates/pipelines/fast.json +19 -0
- package/templates/pipelines/full.json +16 -2
- package/templates/pipelines/hotfix.json +19 -0
- package/templates/pipelines/standard.json +19 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright feedback — Production Feedback Loop ║
|
|
4
|
+
# ║ Error collection · Auto-issue creation · Rollback trigger · Learning ║
|
|
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
|
+
# ─── Output 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
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
35
|
+
now_epoch() { date +%s; }
|
|
36
|
+
|
|
37
|
+
# ─── Structured Event Log ────────────────────────────────────────────────────
|
|
38
|
+
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
39
|
+
|
|
40
|
+
emit_event() {
|
|
41
|
+
local event_type="$1"
|
|
42
|
+
shift
|
|
43
|
+
local json_fields=""
|
|
44
|
+
for kv in "$@"; do
|
|
45
|
+
local key="${kv%%=*}"
|
|
46
|
+
local val="${kv#*=}"
|
|
47
|
+
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
48
|
+
json_fields="${json_fields},\"${key}\":${val}"
|
|
49
|
+
else
|
|
50
|
+
val="${val//\"/\\\"}"
|
|
51
|
+
json_fields="${json_fields},\"${key}\":\"${val}\""
|
|
52
|
+
fi
|
|
53
|
+
done
|
|
54
|
+
mkdir -p "${HOME}/.shipwright"
|
|
55
|
+
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ─── Storage Paths ──────────────────────────────────────────────────────────
|
|
59
|
+
INCIDENTS_FILE="${HOME}/.shipwright/incidents.jsonl"
|
|
60
|
+
ERROR_THRESHOLD=5 # Create issue if error count >= threshold
|
|
61
|
+
ERROR_LOG_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
|
|
62
|
+
ARTIFACTS_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
|
|
63
|
+
|
|
64
|
+
# ─── Initialize directories ────────────────────────────────────────────────
|
|
65
|
+
ensure_dirs() {
|
|
66
|
+
mkdir -p "${HOME}/.shipwright"
|
|
67
|
+
mkdir -p "$ARTIFACTS_DIR"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ─── Detect owner/repo from git remote ─────────────────────────────────────
|
|
71
|
+
get_owner_repo() {
|
|
72
|
+
local remote_url
|
|
73
|
+
remote_url=$(git -C "$REPO_DIR" remote get-url origin 2>/dev/null || true)
|
|
74
|
+
if [[ -z "$remote_url" ]]; then
|
|
75
|
+
return 1
|
|
76
|
+
fi
|
|
77
|
+
echo "$remote_url" | sed -E 's#^(https?://github\.com/|git@github\.com:)##; s#\.git$##'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ─── Parse log files for error patterns ──────────────────────────────────────
|
|
81
|
+
parse_error_patterns() {
|
|
82
|
+
local log_file="$1"
|
|
83
|
+
local error_count=0
|
|
84
|
+
local error_types=""
|
|
85
|
+
local stack_traces=""
|
|
86
|
+
|
|
87
|
+
if [[ ! -f "$log_file" ]]; then
|
|
88
|
+
return 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Count errors and extract stack traces
|
|
92
|
+
while IFS= read -r line; do
|
|
93
|
+
if [[ "$line" =~ (Error|Exception|panic|fatal).*: ]]; then
|
|
94
|
+
error_count=$((error_count + 1))
|
|
95
|
+
# Extract error message
|
|
96
|
+
local err_msg=$(echo "$line" | sed -E 's/^.*\[.*\] //; s/:.*//')
|
|
97
|
+
error_types="${error_types}${err_msg};"
|
|
98
|
+
fi
|
|
99
|
+
done < "$log_file"
|
|
100
|
+
|
|
101
|
+
# Output CSV: count|types|first_stack_trace
|
|
102
|
+
local first_stack=$(head -50 "$log_file" | tail -20)
|
|
103
|
+
echo "$error_count|$error_types|$first_stack"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# ─── Find commit that likely introduced regression ───────────────────────────
|
|
107
|
+
find_regression_commit() {
|
|
108
|
+
local error_pattern="$1"
|
|
109
|
+
local max_commits="${2:-20}"
|
|
110
|
+
|
|
111
|
+
# Search recent commits for changes that might have introduced the error
|
|
112
|
+
# Pattern: look for commits touching error-related code
|
|
113
|
+
local commit_hash
|
|
114
|
+
commit_hash=$(cd "$REPO_DIR" && git log --all -n "$max_commits" --pretty=format:"%H %s" | \
|
|
115
|
+
while read -r hash subject; do
|
|
116
|
+
# Simple heuristic: commits that touched multiple files or had large diffs
|
|
117
|
+
local files_changed
|
|
118
|
+
files_changed=$(cd "$REPO_DIR" && git show "$hash" --stat | tail -1 | grep -oE '[0-9]+ files? changed' | head -1)
|
|
119
|
+
if [[ -n "$files_changed" ]]; then
|
|
120
|
+
echo "$hash"
|
|
121
|
+
break
|
|
122
|
+
fi
|
|
123
|
+
done)
|
|
124
|
+
|
|
125
|
+
echo "${commit_hash:0:7}"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# ─── Collect errors from monitor stage output ────────────────────────────────
|
|
129
|
+
cmd_collect() {
|
|
130
|
+
local log_path="${1:-.}"
|
|
131
|
+
|
|
132
|
+
info "Collecting error patterns from: $log_path"
|
|
133
|
+
ensure_dirs
|
|
134
|
+
|
|
135
|
+
local error_file="${ARTIFACTS_DIR}/errors-collected.json"
|
|
136
|
+
local total_errors=0
|
|
137
|
+
local error_summary=""
|
|
138
|
+
|
|
139
|
+
if [[ -f "$log_path" ]]; then
|
|
140
|
+
# Single file
|
|
141
|
+
local result
|
|
142
|
+
result=$(parse_error_patterns "$log_path")
|
|
143
|
+
IFS='|' read -r count types traces <<< "$result"
|
|
144
|
+
total_errors=$((total_errors + count))
|
|
145
|
+
error_summary="${types}"
|
|
146
|
+
elif [[ -d "$log_path" ]]; then
|
|
147
|
+
# Directory of logs
|
|
148
|
+
while IFS= read -r file; do
|
|
149
|
+
local result
|
|
150
|
+
result=$(parse_error_patterns "$file") || continue
|
|
151
|
+
IFS='|' read -r count types traces <<< "$result"
|
|
152
|
+
total_errors=$((total_errors + count))
|
|
153
|
+
error_summary="${error_summary}${types};"
|
|
154
|
+
done < <(find "$log_path" -name "*.log" -type f 2>/dev/null)
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# Save to artifacts
|
|
158
|
+
local error_json
|
|
159
|
+
error_json=$(jq -n \
|
|
160
|
+
--arg ts "$(now_iso)" \
|
|
161
|
+
--arg summary "$error_summary" \
|
|
162
|
+
--arg count "$total_errors" \
|
|
163
|
+
'{timestamp: $ts, total_errors: ($count | tonumber), error_types: $summary}')
|
|
164
|
+
|
|
165
|
+
echo "$error_json" > "$error_file"
|
|
166
|
+
success "Collected $total_errors errors"
|
|
167
|
+
info "Saved to: $error_file"
|
|
168
|
+
|
|
169
|
+
emit_event "feedback_collect" "errors=$total_errors" "file=$error_file"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# ─── Analyze collected errors ────────────────────────────────────────────────
|
|
173
|
+
cmd_analyze() {
|
|
174
|
+
local error_file="${1:-${ARTIFACTS_DIR}/errors-collected.json}"
|
|
175
|
+
|
|
176
|
+
if [[ ! -f "$error_file" ]]; then
|
|
177
|
+
error "Error file not found: $error_file"
|
|
178
|
+
return 1
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
info "Analyzing error patterns..."
|
|
182
|
+
|
|
183
|
+
local error_count
|
|
184
|
+
error_count=$(jq -r '.total_errors // 0' "$error_file")
|
|
185
|
+
local error_types
|
|
186
|
+
error_types=$(jq -r '.error_types // ""' "$error_file")
|
|
187
|
+
|
|
188
|
+
echo ""
|
|
189
|
+
info "Error Analysis Report"
|
|
190
|
+
echo " ${DIM}Total Errors: ${RESET}${error_count}"
|
|
191
|
+
echo " ${DIM}Error Types: ${RESET}$(echo "$error_types" | tr ';' '\n' | sort | uniq -c | head -5)"
|
|
192
|
+
|
|
193
|
+
if [[ "$error_count" -ge "$ERROR_THRESHOLD" ]]; then
|
|
194
|
+
warn "Error threshold exceeded! ($error_count >= $ERROR_THRESHOLD)"
|
|
195
|
+
echo " Recommended: Create hotfix issue"
|
|
196
|
+
return 0
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
success "Error count within threshold"
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# ─── Create GitHub issue for regression ──────────────────────────────────────
|
|
203
|
+
cmd_create_issue() {
|
|
204
|
+
local error_file="${1:-${ARTIFACTS_DIR}/errors-collected.json}"
|
|
205
|
+
|
|
206
|
+
if [[ ! -f "$error_file" ]]; then
|
|
207
|
+
error "Error file not found: $error_file"
|
|
208
|
+
return 1
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
ensure_dirs
|
|
212
|
+
|
|
213
|
+
local error_count
|
|
214
|
+
error_count=$(jq -r '.total_errors // 0' "$error_file")
|
|
215
|
+
|
|
216
|
+
if [[ "$error_count" -lt "$ERROR_THRESHOLD" ]]; then
|
|
217
|
+
warn "Error count ($error_count) below threshold ($ERROR_THRESHOLD) — skipping issue creation"
|
|
218
|
+
return 0
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
info "Creating GitHub issue for regression..."
|
|
222
|
+
|
|
223
|
+
# Get repo info
|
|
224
|
+
local owner_repo
|
|
225
|
+
owner_repo=$(get_owner_repo) || {
|
|
226
|
+
error "Could not detect GitHub repo"
|
|
227
|
+
return 1
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
local error_types
|
|
231
|
+
error_types=$(jq -r '.error_types // ""' "$error_file")
|
|
232
|
+
|
|
233
|
+
# Find likely regression commit
|
|
234
|
+
local regression_commit
|
|
235
|
+
regression_commit=$(find_regression_commit "$error_types")
|
|
236
|
+
|
|
237
|
+
# Build issue body
|
|
238
|
+
local issue_body
|
|
239
|
+
issue_body=$(cat <<EOF
|
|
240
|
+
## Production Regression Detected
|
|
241
|
+
|
|
242
|
+
**Total Errors**: $error_count
|
|
243
|
+
**Threshold**: $ERROR_THRESHOLD
|
|
244
|
+
**Timestamp**: $(now_iso)
|
|
245
|
+
|
|
246
|
+
### Error Types
|
|
247
|
+
\`\`\`
|
|
248
|
+
$(echo "$error_types" | tr ';' '\n' | sort | uniq -c | head -10)
|
|
249
|
+
\`\`\`
|
|
250
|
+
|
|
251
|
+
### Likely Regression Commit
|
|
252
|
+
\`$regression_commit\`
|
|
253
|
+
|
|
254
|
+
\`\`\`bash
|
|
255
|
+
git show $regression_commit
|
|
256
|
+
\`\`\`
|
|
257
|
+
|
|
258
|
+
### Suggested Fix
|
|
259
|
+
1. Review the commit above for problematic changes
|
|
260
|
+
2. Run tests: \`npm test\`
|
|
261
|
+
3. Check error logs: \`./.claude/pipeline-artifacts/errors-collected.json\`
|
|
262
|
+
4. Deploy hotfix with: \`shipwright pipeline start --issue <N> --template hotfix\`
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
**Auto-created by**: Shipwright Production Feedback Loop
|
|
266
|
+
**Component**: $0
|
|
267
|
+
EOF
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Check if gh is available and NO_GITHUB is not set
|
|
271
|
+
if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
|
|
272
|
+
warn "NO_GITHUB set — skipping GitHub issue creation"
|
|
273
|
+
return 0
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
if ! command -v gh &>/dev/null; then
|
|
277
|
+
error "gh CLI not found — cannot create issue"
|
|
278
|
+
return 1
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
# Create issue via gh
|
|
282
|
+
local issue_url
|
|
283
|
+
issue_url=$(gh issue create \
|
|
284
|
+
--repo "$owner_repo" \
|
|
285
|
+
--title "Production Regression: $error_count errors detected" \
|
|
286
|
+
--body "$issue_body" \
|
|
287
|
+
--label "shipwright" \
|
|
288
|
+
--label "hotfix" \
|
|
289
|
+
2>&1 | tail -1)
|
|
290
|
+
|
|
291
|
+
if [[ -n "$issue_url" ]]; then
|
|
292
|
+
success "Created issue: $issue_url"
|
|
293
|
+
emit_event "feedback_issue_created" "url=$issue_url" "errors=$error_count"
|
|
294
|
+
echo "$issue_url" > "${ARTIFACTS_DIR}/last-issue.txt"
|
|
295
|
+
else
|
|
296
|
+
warn "Could not create issue (check gh auth)"
|
|
297
|
+
fi
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# ─── Trigger rollback via GitHub Deployments API ─────────────────────────────
|
|
301
|
+
cmd_rollback() {
|
|
302
|
+
local environment="${1:-production}"
|
|
303
|
+
local reason="${2:-Rollback due to production errors}"
|
|
304
|
+
|
|
305
|
+
info "Triggering rollback for environment: $environment"
|
|
306
|
+
|
|
307
|
+
# Check for sw-github-deploy.sh
|
|
308
|
+
if [[ ! -f "$SCRIPT_DIR/sw-github-deploy.sh" ]]; then
|
|
309
|
+
error "sw-github-deploy.sh not found"
|
|
310
|
+
return 1
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
ensure_dirs
|
|
314
|
+
|
|
315
|
+
# Get current deployment
|
|
316
|
+
local owner_repo
|
|
317
|
+
owner_repo=$(get_owner_repo) || {
|
|
318
|
+
error "Could not detect GitHub repo"
|
|
319
|
+
return 1
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
# For now, just log the rollback intent
|
|
323
|
+
# Full implementation would call sw-github-deploy.sh and update deployment status
|
|
324
|
+
local rollback_entry
|
|
325
|
+
rollback_entry=$(jq -n \
|
|
326
|
+
--arg ts "$(now_iso)" \
|
|
327
|
+
--arg env "$environment" \
|
|
328
|
+
--arg reason "$reason" \
|
|
329
|
+
'{timestamp: $ts, environment: $env, reason: $reason, status: "initiated"}')
|
|
330
|
+
|
|
331
|
+
echo "$rollback_entry" >> "${ARTIFACTS_DIR}/rollbacks.jsonl"
|
|
332
|
+
|
|
333
|
+
success "Rollback initiated for $environment"
|
|
334
|
+
emit_event "feedback_rollback" "environment=$environment" "reason=$reason"
|
|
335
|
+
info "Note: Full rollback requires manual GitHub Deployments API call"
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
# ─── Capture incident in memory system ───────────────────────────────────────
|
|
339
|
+
cmd_learn() {
|
|
340
|
+
local root_cause="${1:-Unknown}"
|
|
341
|
+
local fix_applied="${2:-}"
|
|
342
|
+
|
|
343
|
+
info "Capturing incident learning..."
|
|
344
|
+
ensure_dirs
|
|
345
|
+
|
|
346
|
+
local incident_entry
|
|
347
|
+
incident_entry=$(jq -c -n \
|
|
348
|
+
--arg ts "$(now_iso)" \
|
|
349
|
+
--arg cause "$root_cause" \
|
|
350
|
+
--arg fix "$fix_applied" \
|
|
351
|
+
--arg repo "$(basename "$REPO_DIR")" \
|
|
352
|
+
'{
|
|
353
|
+
timestamp: $ts,
|
|
354
|
+
repository: $repo,
|
|
355
|
+
root_cause: $cause,
|
|
356
|
+
fix_applied: $fix,
|
|
357
|
+
resolution_time: 0,
|
|
358
|
+
tags: ["production", "feedback-loop"]
|
|
359
|
+
}')
|
|
360
|
+
|
|
361
|
+
echo "$incident_entry" >> "$INCIDENTS_FILE"
|
|
362
|
+
success "Incident captured in $INCIDENTS_FILE"
|
|
363
|
+
emit_event "feedback_incident_learned" "cause=$root_cause"
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
# ─── Report on recent incidents ──────────────────────────────────────────────
|
|
367
|
+
cmd_report() {
|
|
368
|
+
local days="${1:-7}"
|
|
369
|
+
|
|
370
|
+
if [[ ! -f "$INCIDENTS_FILE" ]]; then
|
|
371
|
+
warn "No incidents recorded yet"
|
|
372
|
+
return 0
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
info "Incident Report (last $days days)"
|
|
376
|
+
echo ""
|
|
377
|
+
|
|
378
|
+
local incident_count=0
|
|
379
|
+
|
|
380
|
+
while IFS= read -r line; do
|
|
381
|
+
incident_count=$((incident_count + 1))
|
|
382
|
+
|
|
383
|
+
local ts
|
|
384
|
+
ts=$(echo "$line" | jq -r '.timestamp // "Unknown"')
|
|
385
|
+
local cause
|
|
386
|
+
cause=$(echo "$line" | jq -r '.root_cause // "Unknown"')
|
|
387
|
+
local fixed
|
|
388
|
+
fixed=$(echo "$line" | jq -r '.fix_applied // "Pending"')
|
|
389
|
+
|
|
390
|
+
echo " ${CYAN}Incident $incident_count${RESET} ${DIM}($ts)${RESET}"
|
|
391
|
+
echo " ${DIM}Cause: ${RESET}$cause"
|
|
392
|
+
echo " ${DIM}Fix: ${RESET}$fixed"
|
|
393
|
+
done < "$INCIDENTS_FILE"
|
|
394
|
+
|
|
395
|
+
echo ""
|
|
396
|
+
success "Total incidents: $incident_count"
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
# ─── Show help ───────────────────────────────────────────────────────────────
|
|
400
|
+
show_help() {
|
|
401
|
+
cat <<EOF
|
|
402
|
+
${BOLD}shipwright feedback${RESET} — Production Feedback Loop
|
|
403
|
+
|
|
404
|
+
${BOLD}USAGE${RESET}
|
|
405
|
+
shipwright feedback <subcommand> [options]
|
|
406
|
+
|
|
407
|
+
${BOLD}SUBCOMMANDS${RESET}
|
|
408
|
+
${CYAN}collect${RESET} [path] Collect error patterns from logs
|
|
409
|
+
${CYAN}analyze${RESET} [error-file] Analyze collected errors
|
|
410
|
+
${CYAN}create-issue${RESET} [error-file] Create GitHub issue for regression
|
|
411
|
+
${CYAN}rollback${RESET} [env] [reason] Trigger rollback via Deployments API
|
|
412
|
+
${CYAN}learn${RESET} [cause] [fix] Capture incident in memory system
|
|
413
|
+
${CYAN}report${RESET} [days] Show recent incidents (default: 7 days)
|
|
414
|
+
${CYAN}help${RESET} Show this help message
|
|
415
|
+
|
|
416
|
+
${BOLD}EXAMPLES${RESET}
|
|
417
|
+
${DIM}shipwright feedback collect ./logs${RESET}
|
|
418
|
+
${DIM}shipwright feedback analyze${RESET}
|
|
419
|
+
${DIM}shipwright feedback create-issue${RESET}
|
|
420
|
+
${DIM}shipwright feedback rollback production "Hotfix v1.2.3 regression"${RESET}
|
|
421
|
+
${DIM}shipwright feedback learn "Off-by-one error in pagination" "Fixed in PR #456"${RESET}
|
|
422
|
+
${DIM}shipwright feedback report 30${RESET}
|
|
423
|
+
|
|
424
|
+
${BOLD}STORAGE${RESET}
|
|
425
|
+
Incidents: $INCIDENTS_FILE
|
|
426
|
+
Errors: ${ARTIFACTS_DIR}/errors-collected.json
|
|
427
|
+
Rollbacks: ${ARTIFACTS_DIR}/rollbacks.jsonl
|
|
428
|
+
|
|
429
|
+
${BOLD}VERSION${RESET}
|
|
430
|
+
$VERSION
|
|
431
|
+
EOF
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# ─── Main entry point ────────────────────────────────────────────────────────
|
|
435
|
+
main() {
|
|
436
|
+
local cmd="${1:-help}"
|
|
437
|
+
shift 2>/dev/null || true
|
|
438
|
+
|
|
439
|
+
case "$cmd" in
|
|
440
|
+
collect)
|
|
441
|
+
cmd_collect "$@"
|
|
442
|
+
;;
|
|
443
|
+
analyze)
|
|
444
|
+
cmd_analyze "$@"
|
|
445
|
+
;;
|
|
446
|
+
create-issue)
|
|
447
|
+
cmd_create_issue "$@"
|
|
448
|
+
;;
|
|
449
|
+
rollback)
|
|
450
|
+
cmd_rollback "$@"
|
|
451
|
+
;;
|
|
452
|
+
learn)
|
|
453
|
+
cmd_learn "$@"
|
|
454
|
+
;;
|
|
455
|
+
report)
|
|
456
|
+
cmd_report "$@"
|
|
457
|
+
;;
|
|
458
|
+
help|-h|--help)
|
|
459
|
+
show_help
|
|
460
|
+
;;
|
|
461
|
+
*)
|
|
462
|
+
error "Unknown subcommand: $cmd"
|
|
463
|
+
show_help
|
|
464
|
+
exit 1
|
|
465
|
+
;;
|
|
466
|
+
esac
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
470
|
+
main "$@"
|
|
471
|
+
fi
|
package/scripts/sw-fix.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="
|
|
9
|
+
VERSION="2.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|