shipwright-cli 1.7.1 → 1.10.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 (115) hide show
  1. package/.claude/agents/code-reviewer.md +90 -0
  2. package/.claude/agents/devops-engineer.md +142 -0
  3. package/.claude/agents/pipeline-agent.md +80 -0
  4. package/.claude/agents/shell-script-specialist.md +150 -0
  5. package/.claude/agents/test-specialist.md +196 -0
  6. package/.claude/hooks/post-tool-use.sh +45 -0
  7. package/.claude/hooks/pre-tool-use.sh +25 -0
  8. package/.claude/hooks/session-started.sh +37 -0
  9. package/README.md +212 -814
  10. package/claude-code/CLAUDE.md.shipwright +54 -0
  11. package/claude-code/hooks/notify-idle.sh +2 -2
  12. package/claude-code/hooks/session-start.sh +24 -0
  13. package/claude-code/hooks/task-completed.sh +6 -2
  14. package/claude-code/settings.json.template +12 -0
  15. package/dashboard/public/app.js +4422 -0
  16. package/dashboard/public/index.html +816 -0
  17. package/dashboard/public/styles.css +4755 -0
  18. package/dashboard/server.ts +4315 -0
  19. package/docs/KNOWN-ISSUES.md +18 -10
  20. package/docs/TIPS.md +38 -26
  21. package/docs/patterns/README.md +33 -23
  22. package/package.json +9 -5
  23. package/scripts/adapters/iterm2-adapter.sh +1 -1
  24. package/scripts/adapters/tmux-adapter.sh +52 -23
  25. package/scripts/adapters/wezterm-adapter.sh +26 -14
  26. package/scripts/lib/compat.sh +200 -0
  27. package/scripts/lib/helpers.sh +72 -0
  28. package/scripts/postinstall.mjs +72 -13
  29. package/scripts/{cct → sw} +118 -22
  30. package/scripts/sw-adversarial.sh +274 -0
  31. package/scripts/sw-architecture-enforcer.sh +330 -0
  32. package/scripts/sw-checkpoint.sh +468 -0
  33. package/scripts/sw-cleanup.sh +359 -0
  34. package/scripts/sw-connect.sh +619 -0
  35. package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
  36. package/scripts/sw-daemon.sh +5574 -0
  37. package/scripts/sw-dashboard.sh +477 -0
  38. package/scripts/sw-developer-simulation.sh +252 -0
  39. package/scripts/sw-docs.sh +635 -0
  40. package/scripts/sw-doctor.sh +907 -0
  41. package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
  42. package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
  43. package/scripts/sw-github-checks.sh +521 -0
  44. package/scripts/sw-github-deploy.sh +533 -0
  45. package/scripts/sw-github-graphql.sh +972 -0
  46. package/scripts/sw-heartbeat.sh +293 -0
  47. package/scripts/{cct-init.sh → sw-init.sh} +144 -11
  48. package/scripts/sw-intelligence.sh +1196 -0
  49. package/scripts/sw-jira.sh +643 -0
  50. package/scripts/sw-launchd.sh +364 -0
  51. package/scripts/sw-linear.sh +648 -0
  52. package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
  53. package/scripts/sw-loop.sh +2217 -0
  54. package/scripts/{cct-memory.sh → sw-memory.sh} +514 -36
  55. package/scripts/sw-patrol-meta.sh +417 -0
  56. package/scripts/sw-pipeline-composer.sh +455 -0
  57. package/scripts/sw-pipeline-vitals.sh +1096 -0
  58. package/scripts/sw-pipeline.sh +7593 -0
  59. package/scripts/sw-predictive.sh +820 -0
  60. package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
  61. package/scripts/{cct-ps.sh → sw-ps.sh} +9 -6
  62. package/scripts/{cct-reaper.sh → sw-reaper.sh} +10 -6
  63. package/scripts/sw-remote.sh +687 -0
  64. package/scripts/sw-self-optimize.sh +1048 -0
  65. package/scripts/sw-session.sh +541 -0
  66. package/scripts/sw-setup.sh +234 -0
  67. package/scripts/sw-status.sh +796 -0
  68. package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
  69. package/scripts/sw-tmux.sh +591 -0
  70. package/scripts/sw-tracker-jira.sh +277 -0
  71. package/scripts/sw-tracker-linear.sh +292 -0
  72. package/scripts/sw-tracker.sh +409 -0
  73. package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
  74. package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
  75. package/templates/pipelines/autonomous.json +35 -6
  76. package/templates/pipelines/cost-aware.json +21 -0
  77. package/templates/pipelines/deployed.json +40 -6
  78. package/templates/pipelines/enterprise.json +16 -2
  79. package/templates/pipelines/fast.json +19 -0
  80. package/templates/pipelines/full.json +28 -2
  81. package/templates/pipelines/hotfix.json +19 -0
  82. package/templates/pipelines/standard.json +31 -0
  83. package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
  84. package/tmux/templates/accessibility.json +34 -0
  85. package/tmux/templates/api-design.json +35 -0
  86. package/tmux/templates/architecture.json +1 -0
  87. package/tmux/templates/bug-fix.json +9 -0
  88. package/tmux/templates/code-review.json +1 -0
  89. package/tmux/templates/compliance.json +36 -0
  90. package/tmux/templates/data-pipeline.json +36 -0
  91. package/tmux/templates/debt-paydown.json +34 -0
  92. package/tmux/templates/devops.json +1 -0
  93. package/tmux/templates/documentation.json +1 -0
  94. package/tmux/templates/exploration.json +1 -0
  95. package/tmux/templates/feature-dev.json +1 -0
  96. package/tmux/templates/full-stack.json +8 -0
  97. package/tmux/templates/i18n.json +34 -0
  98. package/tmux/templates/incident-response.json +36 -0
  99. package/tmux/templates/migration.json +1 -0
  100. package/tmux/templates/observability.json +35 -0
  101. package/tmux/templates/onboarding.json +33 -0
  102. package/tmux/templates/performance.json +35 -0
  103. package/tmux/templates/refactor.json +1 -0
  104. package/tmux/templates/release.json +35 -0
  105. package/tmux/templates/security-audit.json +8 -0
  106. package/tmux/templates/spike.json +34 -0
  107. package/tmux/templates/testing.json +1 -0
  108. package/tmux/tmux.conf +98 -9
  109. package/scripts/cct-cleanup.sh +0 -172
  110. package/scripts/cct-daemon.sh +0 -3189
  111. package/scripts/cct-doctor.sh +0 -414
  112. package/scripts/cct-loop.sh +0 -1332
  113. package/scripts/cct-pipeline.sh +0 -3844
  114. package/scripts/cct-session.sh +0 -284
  115. package/scripts/cct-status.sh +0 -169
@@ -0,0 +1,521 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright github-checks — Native GitHub Checks API Integration ║
4
+ # ║ Check runs per stage · Annotations · PR timeline integration ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
+
9
+ VERSION="1.10.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)\",\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
56
+ }
57
+
58
+ # ─── Artifacts Directory ─────────────────────────────────────────────────────
59
+ ARTIFACTS_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
60
+
61
+ # ─── Session Cache for API Availability ───────────────────────────────────────
62
+ _GH_CHECKS_AVAILABLE=""
63
+
64
+ # ─── Auto-detect owner/repo from git remote ──────────────────────────────────
65
+ _gh_detect_repo() {
66
+ local remote_url
67
+ remote_url=$(git -C "$REPO_DIR" remote get-url origin 2>/dev/null || true)
68
+ if [[ -z "$remote_url" ]]; then
69
+ echo ""
70
+ return 1
71
+ fi
72
+ # Handle SSH (git@github.com:owner/repo.git) and HTTPS (https://github.com/owner/repo.git)
73
+ local owner_repo
74
+ owner_repo=$(echo "$remote_url" | sed -E 's#^(https?://github\.com/|git@github\.com:)##; s#\.git$##')
75
+ echo "$owner_repo"
76
+ }
77
+
78
+ # ═══════════════════════════════════════════════════════════════════════════════
79
+ # CHECKS API FUNCTIONS
80
+ # ═══════════════════════════════════════════════════════════════════════════════
81
+
82
+ # ─── Check if Checks API is accessible ────────────────────────────────────────
83
+ _gh_checks_available() {
84
+ # Return cached result if available
85
+ if [[ "$_GH_CHECKS_AVAILABLE" == "yes" ]]; then
86
+ return 0
87
+ elif [[ "$_GH_CHECKS_AVAILABLE" == "no" ]]; then
88
+ return 1
89
+ fi
90
+
91
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
92
+ _GH_CHECKS_AVAILABLE="no"
93
+ return 1
94
+ fi
95
+
96
+ local owner="${1:-}"
97
+ local repo="${2:-}"
98
+
99
+ if [[ -z "$owner" || -z "$repo" ]]; then
100
+ local detected
101
+ detected=$(_gh_detect_repo || true)
102
+ if [[ -n "$detected" ]]; then
103
+ owner="${detected%%/*}"
104
+ repo="${detected##*/}"
105
+ fi
106
+ fi
107
+
108
+ if [[ -z "$owner" || -z "$repo" ]]; then
109
+ _GH_CHECKS_AVAILABLE="no"
110
+ return 1
111
+ fi
112
+
113
+ local result=0
114
+ gh api "repos/${owner}/${repo}/commits/HEAD/check-runs?per_page=1" --silent 2>/dev/null || result=$?
115
+
116
+ if [[ "$result" -eq 0 ]]; then
117
+ _GH_CHECKS_AVAILABLE="yes"
118
+ return 0
119
+ else
120
+ _GH_CHECKS_AVAILABLE="no"
121
+ return 1
122
+ fi
123
+ }
124
+
125
+ # ─── Create a check run ──────────────────────────────────────────────────────
126
+ gh_checks_create_run() {
127
+ local owner="${1:-}"
128
+ local repo="${2:-}"
129
+ local head_sha="${3:-}"
130
+ local name="${4:-}"
131
+ local status="${5:-in_progress}"
132
+ local details_url="${6:-}"
133
+
134
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
135
+ return 0
136
+ fi
137
+
138
+ if [[ -z "$owner" || -z "$repo" || -z "$head_sha" || -z "$name" ]]; then
139
+ error "Usage: gh_checks_create_run <owner> <repo> <sha> <name> [status] [details_url]"
140
+ return 1
141
+ fi
142
+
143
+ local body
144
+ body=$(jq -n \
145
+ --arg sha "$head_sha" \
146
+ --arg name "$name" \
147
+ --arg status "$status" \
148
+ --arg started_at "$(now_iso)" \
149
+ --arg details_url "$details_url" \
150
+ '{
151
+ head_sha: $sha,
152
+ name: $name,
153
+ status: $status,
154
+ started_at: $started_at
155
+ } + (if $details_url != "" then {details_url: $details_url} else {} end)')
156
+
157
+ local response=""
158
+ local result=0
159
+ response=$(gh api "repos/${owner}/${repo}/check-runs" \
160
+ --method POST \
161
+ --input - <<< "$body" 2>/dev/null) || result=$?
162
+
163
+ if [[ "$result" -ne 0 ]]; then
164
+ warn "Failed to create check run '${name}' (API returned ${result})" >&2
165
+ echo ""
166
+ return 0
167
+ fi
168
+
169
+ local run_id
170
+ run_id=$(echo "$response" | jq -r '.id // empty' 2>/dev/null || true)
171
+
172
+ if [[ -n "$run_id" && "$run_id" != "null" ]]; then
173
+ emit_event "checks.create" "run_id=$run_id" "name=$name" "status=$status"
174
+ echo "$run_id"
175
+ else
176
+ warn "Check run created but no ID returned" >&2
177
+ echo ""
178
+ fi
179
+ }
180
+
181
+ # ─── Update a check run ──────────────────────────────────────────────────────
182
+ gh_checks_update_run() {
183
+ local owner="${1:-}"
184
+ local repo="${2:-}"
185
+ local run_id="${3:-}"
186
+ local status="${4:-}"
187
+ local conclusion="${5:-}"
188
+ local output_title="${6:-}"
189
+ local output_summary="${7:-}"
190
+ local output_text="${8:-}"
191
+
192
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
193
+ return 0
194
+ fi
195
+
196
+ # Skip silently if run_id is empty
197
+ if [[ -z "$run_id" ]]; then
198
+ return 0
199
+ fi
200
+
201
+ if [[ -z "$owner" || -z "$repo" ]]; then
202
+ error "Usage: gh_checks_update_run <owner> <repo> <run_id> <status> [conclusion] [title] [summary] [text]"
203
+ return 1
204
+ fi
205
+
206
+ local body
207
+ body=$(jq -n \
208
+ --arg status "$status" \
209
+ --arg conclusion "$conclusion" \
210
+ --arg completed_at "$(now_iso)" \
211
+ --arg title "$output_title" \
212
+ --arg summary "$output_summary" \
213
+ --arg text "$output_text" \
214
+ '{status: $status}
215
+ + (if $conclusion != "" then {conclusion: $conclusion, completed_at: $completed_at} else {} end)
216
+ + (if $title != "" then {output: (
217
+ {title: $title, summary: (if $summary != "" then $summary else "No summary" end)}
218
+ + (if $text != "" then {text: $text} else {} end)
219
+ )} else {} end)')
220
+
221
+ local result=0
222
+ gh api "repos/${owner}/${repo}/check-runs/${run_id}" \
223
+ --method PATCH \
224
+ --input - <<< "$body" --silent 2>/dev/null || result=$?
225
+
226
+ if [[ "$result" -ne 0 ]]; then
227
+ warn "Failed to update check run ${run_id}"
228
+ else
229
+ emit_event "checks.update" "run_id=$run_id" "status=$status" "conclusion=$conclusion"
230
+ fi
231
+ }
232
+
233
+ # ─── Add annotations to a check run ──────────────────────────────────────────
234
+ gh_checks_annotate() {
235
+ local owner="${1:-}"
236
+ local repo="${2:-}"
237
+ local run_id="${3:-}"
238
+ local annotations_json="${4:-}"
239
+
240
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
241
+ return 0
242
+ fi
243
+
244
+ if [[ -z "$run_id" ]]; then
245
+ return 0
246
+ fi
247
+
248
+ if [[ -z "$owner" || -z "$repo" || -z "$annotations_json" ]]; then
249
+ error "Usage: gh_checks_annotate <owner> <repo> <run_id> <annotations_json>"
250
+ return 1
251
+ fi
252
+
253
+ # GitHub limits: max 50 annotations per request
254
+ local total
255
+ total=$(echo "$annotations_json" | jq 'length' 2>/dev/null || echo "0")
256
+
257
+ local offset=0
258
+ while [[ "$offset" -lt "$total" ]]; do
259
+ local batch
260
+ batch=$(echo "$annotations_json" | jq --argjson s "$offset" --argjson e 50 '.[$s:$s+$e]' 2>/dev/null)
261
+
262
+ local body
263
+ body=$(jq -n \
264
+ --argjson annotations "$batch" \
265
+ '{
266
+ output: {
267
+ title: "Shipwright Annotations",
268
+ summary: "Pipeline analysis annotations",
269
+ annotations: $annotations
270
+ }
271
+ }')
272
+
273
+ local result=0
274
+ gh api "repos/${owner}/${repo}/check-runs/${run_id}" \
275
+ --method PATCH \
276
+ --input - <<< "$body" --silent 2>/dev/null || result=$?
277
+
278
+ if [[ "$result" -ne 0 ]]; then
279
+ warn "Failed to add annotations batch at offset ${offset}"
280
+ fi
281
+
282
+ offset=$((offset + 50))
283
+ done
284
+
285
+ emit_event "checks.annotate" "run_id=$run_id" "count=$total"
286
+ }
287
+
288
+ # ─── List check runs for a commit ─────────────────────────────────────────────
289
+ gh_checks_list_runs() {
290
+ local owner="${1:-}"
291
+ local repo="${2:-}"
292
+ local head_sha="${3:-}"
293
+
294
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
295
+ echo "[]"
296
+ return 0
297
+ fi
298
+
299
+ if [[ -z "$owner" || -z "$repo" || -z "$head_sha" ]]; then
300
+ error "Usage: gh_checks_list_runs <owner> <repo> <sha>"
301
+ return 1
302
+ fi
303
+
304
+ local response=""
305
+ local result=0
306
+ response=$(gh api "repos/${owner}/${repo}/commits/${head_sha}/check-runs" 2>/dev/null) || result=$?
307
+
308
+ if [[ "$result" -ne 0 ]]; then
309
+ warn "Failed to list check runs for ${head_sha}" >&2
310
+ echo "[]"
311
+ return 0
312
+ fi
313
+
314
+ echo "$response" | jq '[.check_runs[] | {id, name, status, conclusion, started_at, completed_at}]' 2>/dev/null || echo "[]"
315
+ }
316
+
317
+ # ─── Complete a check run (convenience) ───────────────────────────────────────
318
+ gh_checks_complete() {
319
+ local owner="${1:-}"
320
+ local repo="${2:-}"
321
+ local run_id="${3:-}"
322
+ local conclusion="${4:-success}"
323
+ local summary="${5:-}"
324
+
325
+ if [[ -z "$run_id" ]]; then
326
+ return 0
327
+ fi
328
+
329
+ gh_checks_update_run "$owner" "$repo" "$run_id" "completed" "$conclusion" \
330
+ "Shipwright: ${conclusion}" "${summary:-Stage completed with ${conclusion}}" ""
331
+
332
+ emit_event "checks.complete" "run_id=$run_id" "conclusion=$conclusion"
333
+ }
334
+
335
+ # ─── Create check runs for all pipeline stages ───────────────────────────────
336
+ gh_checks_pipeline_start() {
337
+ local owner="${1:-}"
338
+ local repo="${2:-}"
339
+ local head_sha="${3:-}"
340
+ local stages_json="${4:-}"
341
+
342
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
343
+ echo "{}"
344
+ return 0
345
+ fi
346
+
347
+ if [[ -z "$owner" || -z "$repo" || -z "$head_sha" || -z "$stages_json" ]]; then
348
+ error "Usage: gh_checks_pipeline_start <owner> <repo> <sha> <stages_json>"
349
+ return 1
350
+ fi
351
+
352
+ mkdir -p "$ARTIFACTS_DIR"
353
+
354
+ local run_ids="{}"
355
+ local stage=""
356
+ while IFS= read -r stage; do
357
+ [[ -z "$stage" ]] && continue
358
+
359
+ local run_id
360
+ run_id=$(gh_checks_create_run "$owner" "$repo" "$head_sha" "shipwright/${stage}" "queued" "")
361
+
362
+ if [[ -n "$run_id" ]]; then
363
+ run_ids=$(echo "$run_ids" | jq --arg stage "$stage" --arg id "$run_id" '. + {($stage): $id}')
364
+ fi
365
+ done < <(echo "$stages_json" | jq -r '.[]' 2>/dev/null)
366
+
367
+ # Store run IDs atomically
368
+ local tmp_file
369
+ tmp_file=$(mktemp "${ARTIFACTS_DIR}/check-run-ids.XXXXXX")
370
+ echo "$run_ids" > "$tmp_file"
371
+ mv "$tmp_file" "${ARTIFACTS_DIR}/check-run-ids.json"
372
+
373
+ emit_event "checks.pipeline_start" "stages=$(echo "$stages_json" | jq -r 'length')"
374
+ echo "$run_ids"
375
+ }
376
+
377
+ # ─── Update a pipeline stage check run ────────────────────────────────────────
378
+ gh_checks_stage_update() {
379
+ local stage_name="${1:-}"
380
+ local status="${2:-}"
381
+ local conclusion="${3:-}"
382
+ local summary="${4:-}"
383
+
384
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
385
+ return 0
386
+ fi
387
+
388
+ local ids_file="${ARTIFACTS_DIR}/check-run-ids.json"
389
+ if [[ ! -f "$ids_file" ]]; then
390
+ warn "No check-run-ids.json found — skipping stage update"
391
+ return 0
392
+ fi
393
+
394
+ local run_id
395
+ run_id=$(jq -r --arg s "$stage_name" '.[$s] // empty' "$ids_file" 2>/dev/null || true)
396
+
397
+ if [[ -z "$run_id" || "$run_id" == "null" ]]; then
398
+ warn "No check run ID found for stage '${stage_name}'"
399
+ return 0
400
+ fi
401
+
402
+ # Detect owner/repo
403
+ local detected
404
+ detected=$(_gh_detect_repo || true)
405
+ if [[ -z "$detected" ]]; then
406
+ warn "Could not detect owner/repo — skipping stage update"
407
+ return 0
408
+ fi
409
+
410
+ local owner="${detected%%/*}"
411
+ local repo="${detected##*/}"
412
+
413
+ gh_checks_update_run "$owner" "$repo" "$run_id" "$status" "$conclusion" \
414
+ "Shipwright: ${stage_name}" "${summary:-Stage ${stage_name}: ${status}}" ""
415
+ }
416
+
417
+ # ═══════════════════════════════════════════════════════════════════════════════
418
+ # CLI INTERFACE
419
+ # ═══════════════════════════════════════════════════════════════════════════════
420
+
421
+ show_help() {
422
+ echo ""
423
+ echo -e "${CYAN}${BOLD}shipwright github-checks${RESET} — Native GitHub Checks API Integration"
424
+ echo ""
425
+ echo -e "${BOLD}USAGE${RESET}"
426
+ echo -e " shipwright checks <command> [options]"
427
+ echo ""
428
+ echo -e "${BOLD}COMMANDS${RESET}"
429
+ echo -e " ${CYAN}list${RESET} <sha> List check runs for a commit"
430
+ echo -e " ${CYAN}create${RESET} <sha> <name> Create a new check run"
431
+ echo -e " ${CYAN}help${RESET} Show this help"
432
+ echo ""
433
+ echo -e "${BOLD}FUNCTIONS (for sourcing)${RESET}"
434
+ echo -e " ${DIM}gh_checks_create_run Create a check run${RESET}"
435
+ echo -e " ${DIM}gh_checks_update_run Update check run status/conclusion${RESET}"
436
+ echo -e " ${DIM}gh_checks_annotate Add annotations to a check run${RESET}"
437
+ echo -e " ${DIM}gh_checks_list_runs List check runs for a commit${RESET}"
438
+ echo -e " ${DIM}gh_checks_complete Mark a check run as completed${RESET}"
439
+ echo -e " ${DIM}gh_checks_pipeline_start Create runs for all pipeline stages${RESET}"
440
+ echo -e " ${DIM}gh_checks_stage_update Update a stage's check run${RESET}"
441
+ echo ""
442
+ echo -e "${DIM}Version ${VERSION}${RESET}"
443
+ }
444
+
445
+ _checks_list_cli() {
446
+ local sha="${1:-HEAD}"
447
+
448
+ local detected
449
+ detected=$(_gh_detect_repo || true)
450
+ if [[ -z "$detected" ]]; then
451
+ error "Could not detect owner/repo from git remote"
452
+ exit 1
453
+ fi
454
+
455
+ local owner="${detected%%/*}"
456
+ local repo="${detected##*/}"
457
+
458
+ info "Listing check runs for ${sha}..."
459
+ local runs
460
+ runs=$(gh_checks_list_runs "$owner" "$repo" "$sha")
461
+
462
+ local count
463
+ count=$(echo "$runs" | jq 'length' 2>/dev/null || echo "0")
464
+
465
+ if [[ "$count" -eq 0 ]]; then
466
+ info "No check runs found"
467
+ return 0
468
+ fi
469
+
470
+ echo ""
471
+ echo -e "${BOLD}Check Runs (${count})${RESET}"
472
+ echo ""
473
+
474
+ echo "$runs" | jq -r '.[] | " \(.name)\t\(.status)\t\(.conclusion // "-")"' 2>/dev/null || true
475
+ echo ""
476
+ }
477
+
478
+ _checks_create_cli() {
479
+ local sha="${1:-}"
480
+ local name="${2:-}"
481
+
482
+ if [[ -z "$sha" || -z "$name" ]]; then
483
+ error "Usage: shipwright checks create <sha> <name>"
484
+ exit 1
485
+ fi
486
+
487
+ local detected
488
+ detected=$(_gh_detect_repo || true)
489
+ if [[ -z "$detected" ]]; then
490
+ error "Could not detect owner/repo from git remote"
491
+ exit 1
492
+ fi
493
+
494
+ local owner="${detected%%/*}"
495
+ local repo="${detected##*/}"
496
+
497
+ info "Creating check run '${name}'..."
498
+ local run_id
499
+ run_id=$(gh_checks_create_run "$owner" "$repo" "$sha" "$name" "in_progress")
500
+
501
+ if [[ -n "$run_id" ]]; then
502
+ success "Created check run: ${run_id}"
503
+ else
504
+ error "Failed to create check run"
505
+ exit 1
506
+ fi
507
+ }
508
+
509
+ main() {
510
+ case "${1:-help}" in
511
+ list) shift; _checks_list_cli "$@" ;;
512
+ create) shift; _checks_create_cli "$@" ;;
513
+ help|--help|-h) show_help ;;
514
+ *) error "Unknown command: $1"; show_help; exit 1 ;;
515
+ esac
516
+ }
517
+
518
+ # Only run main if executed directly (not sourced)
519
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
520
+ main "$@"
521
+ fi