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
|
@@ -137,6 +137,178 @@ jira_api() {
|
|
|
137
137
|
curl "${args[@]}" "${JIRA_BASE_URL}/rest/api/3/${endpoint}" 2>&1
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
# ─── Discovery & CRUD Interface ───────────────────────────────────────────
|
|
141
|
+
# Implements provider interface for daemon discovery and pipeline CRUD
|
|
142
|
+
|
|
143
|
+
provider_discover_issues() {
|
|
144
|
+
local label="$1"
|
|
145
|
+
local state="${2:-open}"
|
|
146
|
+
local limit="${3:-50}"
|
|
147
|
+
|
|
148
|
+
provider_load_config
|
|
149
|
+
|
|
150
|
+
# Build JQL query
|
|
151
|
+
local jql="project = \"${JIRA_PROJECT_KEY}\""
|
|
152
|
+
|
|
153
|
+
if [[ -n "$label" ]]; then
|
|
154
|
+
jql="${jql} AND labels = \"${label}\""
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# Map state parameter to Jira status
|
|
158
|
+
case "$state" in
|
|
159
|
+
open)
|
|
160
|
+
jql="${jql} AND status IN (${JIRA_TRANSITION_IN_PROGRESS:-\"In Progress\"},${JIRA_TRANSITION_IN_REVIEW:-\"In Review\"})"
|
|
161
|
+
;;
|
|
162
|
+
closed)
|
|
163
|
+
jql="${jql} AND status = ${JIRA_TRANSITION_DONE:-\"Done\"}"
|
|
164
|
+
;;
|
|
165
|
+
*)
|
|
166
|
+
# Custom status provided
|
|
167
|
+
jql="${jql} AND status = \"${state}\""
|
|
168
|
+
;;
|
|
169
|
+
esac
|
|
170
|
+
|
|
171
|
+
jql="${jql} ORDER BY created DESC"
|
|
172
|
+
|
|
173
|
+
# Fetch issues
|
|
174
|
+
local response
|
|
175
|
+
response=$(jira_api "GET" "search?jql=$(printf '%s' "$jql" | jq -sRr @uri)&maxResults=${limit}&fields=key,summary,labels,status" 2>/dev/null) || {
|
|
176
|
+
echo "[]"
|
|
177
|
+
return 0
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Normalize to {id, title, labels[], state}
|
|
181
|
+
echo "$response" | jq '[.issues[]? | {id: .key, title: .fields.summary, labels: [.fields.labels[]?.name // empty], state: .fields.status.name}]' 2>/dev/null || echo "[]"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
provider_get_issue() {
|
|
185
|
+
local issue_id="$1"
|
|
186
|
+
|
|
187
|
+
[[ -z "$issue_id" ]] && return 1
|
|
188
|
+
|
|
189
|
+
provider_load_config
|
|
190
|
+
|
|
191
|
+
local response
|
|
192
|
+
response=$(jira_api "GET" "issue/${issue_id}?fields=key,summary,description,labels,status" 2>/dev/null) || {
|
|
193
|
+
return 1
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Normalize output
|
|
197
|
+
echo "$response" | jq '{id: .key, title: .fields.summary, body: .fields.description, labels: [.fields.labels[]?.name // empty], state: .fields.status.name}' 2>/dev/null || return 1
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
provider_get_issue_body() {
|
|
201
|
+
local issue_id="$1"
|
|
202
|
+
|
|
203
|
+
[[ -z "$issue_id" ]] && return 1
|
|
204
|
+
|
|
205
|
+
provider_load_config
|
|
206
|
+
|
|
207
|
+
local response
|
|
208
|
+
response=$(jira_api "GET" "issue/${issue_id}?fields=description" 2>/dev/null) || {
|
|
209
|
+
return 1
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
echo "$response" | jq -r '.fields.description // ""' 2>/dev/null || return 1
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
provider_add_label() {
|
|
216
|
+
local issue_id="$1"
|
|
217
|
+
local label="$2"
|
|
218
|
+
|
|
219
|
+
[[ -z "$issue_id" || -z "$label" ]] && return 1
|
|
220
|
+
|
|
221
|
+
provider_load_config
|
|
222
|
+
|
|
223
|
+
# Get current labels
|
|
224
|
+
local current_labels
|
|
225
|
+
current_labels=$(jira_api "GET" "issue/${issue_id}?fields=labels" 2>/dev/null | jq '.fields.labels[]?.name' 2>/dev/null || echo "[]")
|
|
226
|
+
|
|
227
|
+
# Add new label
|
|
228
|
+
local payload
|
|
229
|
+
payload=$(jq -n --arg label "$label" --argjson labels "$current_labels" '{fields: {labels: ($labels | map(select(. != $label)) + [$label])}}' 2>/dev/null)
|
|
230
|
+
|
|
231
|
+
jira_api "PUT" "issue/${issue_id}" "$payload" 2>/dev/null || return 1
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
provider_remove_label() {
|
|
235
|
+
local issue_id="$1"
|
|
236
|
+
local label="$2"
|
|
237
|
+
|
|
238
|
+
[[ -z "$issue_id" || -z "$label" ]] && return 1
|
|
239
|
+
|
|
240
|
+
provider_load_config
|
|
241
|
+
|
|
242
|
+
# Get current labels and remove the specified one
|
|
243
|
+
local payload
|
|
244
|
+
payload=$(jq -n --arg label "$label" '{fields: {labels: [{name: ""}]}}')
|
|
245
|
+
|
|
246
|
+
jira_api "PUT" "issue/${issue_id}" "$payload" 2>/dev/null || return 1
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
provider_comment() {
|
|
250
|
+
local issue_id="$1"
|
|
251
|
+
local body="$2"
|
|
252
|
+
|
|
253
|
+
[[ -z "$issue_id" || -z "$body" ]] && return 1
|
|
254
|
+
|
|
255
|
+
provider_load_config
|
|
256
|
+
jira_add_comment "$issue_id" "$body"
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
provider_close_issue() {
|
|
260
|
+
local issue_id="$1"
|
|
261
|
+
|
|
262
|
+
[[ -z "$issue_id" ]] && return 1
|
|
263
|
+
|
|
264
|
+
provider_load_config
|
|
265
|
+
jira_transition "$issue_id" "$JIRA_TRANSITION_DONE"
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
provider_create_issue() {
|
|
269
|
+
local title="$1"
|
|
270
|
+
local body="$2"
|
|
271
|
+
local labels="${3:-}"
|
|
272
|
+
|
|
273
|
+
[[ -z "$title" ]] && return 1
|
|
274
|
+
|
|
275
|
+
provider_load_config
|
|
276
|
+
|
|
277
|
+
# Build payload
|
|
278
|
+
local payload
|
|
279
|
+
payload=$(jq -n --arg summary "$title" --arg description "$body" --arg project "$JIRA_PROJECT_KEY" \
|
|
280
|
+
'{
|
|
281
|
+
fields: {
|
|
282
|
+
project: {key: $project},
|
|
283
|
+
summary: $summary,
|
|
284
|
+
description: $description,
|
|
285
|
+
issuetype: {name: "Task"}
|
|
286
|
+
}
|
|
287
|
+
}')
|
|
288
|
+
|
|
289
|
+
# Add labels if provided
|
|
290
|
+
if [[ -n "$labels" ]]; then
|
|
291
|
+
local label_array
|
|
292
|
+
label_array=$(echo "$labels" | jq -R 'split("[, ]"; "x") | map(select(length > 0))' 2>/dev/null || echo '[]')
|
|
293
|
+
payload=$(echo "$payload" | jq --argjson labels "$label_array" '.fields.labels = $labels' 2>/dev/null)
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
local response
|
|
297
|
+
response=$(jira_api "POST" "issue" "$payload" 2>/dev/null) || {
|
|
298
|
+
return 1
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
# Extract issue key from response
|
|
302
|
+
local issue_key
|
|
303
|
+
issue_key=$(echo "$response" | jq -r '.key // empty' 2>/dev/null)
|
|
304
|
+
|
|
305
|
+
if [[ -z "$issue_key" ]]; then
|
|
306
|
+
return 1
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
echo "{\"id\": \"$issue_key\", \"title\": \"$title\"}"
|
|
310
|
+
}
|
|
311
|
+
|
|
140
312
|
# ─── Find Jira Issue Key from GitHub Issue Body ───────────────────────────
|
|
141
313
|
|
|
142
314
|
find_jira_key() {
|
|
@@ -216,6 +216,257 @@ linear_attach_pr() {
|
|
|
216
216
|
linear_add_comment "$issue_id" "$body"
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
# ─── Discovery & CRUD Interface ───────────────────────────────────────────
|
|
220
|
+
# Implements provider interface for daemon discovery and pipeline CRUD
|
|
221
|
+
|
|
222
|
+
provider_discover_issues() {
|
|
223
|
+
local label="$1"
|
|
224
|
+
local state="${2:-open}"
|
|
225
|
+
local limit="${3:-50}"
|
|
226
|
+
|
|
227
|
+
provider_load_config
|
|
228
|
+
|
|
229
|
+
# Build Linear query for issues
|
|
230
|
+
local query='query($teamId: String!, $first: Int, $filter: IssueFilter) {
|
|
231
|
+
team(id: $teamId) {
|
|
232
|
+
issues(first: $first, filter: $filter) {
|
|
233
|
+
nodes {
|
|
234
|
+
id identifier title labels {nodes {name}}
|
|
235
|
+
state {id name type}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}'
|
|
240
|
+
|
|
241
|
+
# Build filter for state
|
|
242
|
+
local state_filter=""
|
|
243
|
+
case "$state" in
|
|
244
|
+
open)
|
|
245
|
+
# Open = unstarted or started
|
|
246
|
+
state_filter='and: [or: [{state: {type: {eq: "unstarted"}}}, {state: {type: {eq: "started"}}}]]'
|
|
247
|
+
;;
|
|
248
|
+
closed)
|
|
249
|
+
state_filter='and: [{state: {type: {eq: "completed"}}}]'
|
|
250
|
+
;;
|
|
251
|
+
*)
|
|
252
|
+
# Custom state provided
|
|
253
|
+
state_filter="and: [{state: {type: {eq: \"${state}\"}}}]"
|
|
254
|
+
;;
|
|
255
|
+
esac
|
|
256
|
+
|
|
257
|
+
# Add label filter if provided
|
|
258
|
+
if [[ -n "$label" ]]; then
|
|
259
|
+
state_filter="${state_filter}, {labels: {some: {name: {eq: \"${label}\"}}}}"
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
local filter
|
|
263
|
+
filter="{${state_filter}}"
|
|
264
|
+
|
|
265
|
+
local vars
|
|
266
|
+
vars=$(jq -n --arg teamId "$LINEAR_TEAM_ID" --arg filter "$filter" --arg limit "$limit" \
|
|
267
|
+
"{teamId: \$teamId, first: (\$limit | tonumber), filter: $filter}" 2>/dev/null || \
|
|
268
|
+
jq -n --arg teamId "$LINEAR_TEAM_ID" --arg limit "$limit" \
|
|
269
|
+
'{teamId: $teamId, first: ($limit | tonumber)}')
|
|
270
|
+
|
|
271
|
+
local response
|
|
272
|
+
response=$(linear_graphql "$query" "$vars" 2>/dev/null) || {
|
|
273
|
+
echo "[]"
|
|
274
|
+
return 0
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Normalize to {id, title, labels[], state}
|
|
278
|
+
echo "$response" | jq '[.data.team.issues.nodes[]? | {id: .id, title: .title, labels: [.labels.nodes[]?.name // empty], state: .state.name}]' 2>/dev/null || echo "[]"
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
provider_get_issue() {
|
|
282
|
+
local issue_id="$1"
|
|
283
|
+
|
|
284
|
+
[[ -z "$issue_id" ]] && return 1
|
|
285
|
+
|
|
286
|
+
provider_load_config
|
|
287
|
+
|
|
288
|
+
local query='query($id: String!) {
|
|
289
|
+
issue(id: $id) {
|
|
290
|
+
id title description labels {nodes {name}}
|
|
291
|
+
state {id name}
|
|
292
|
+
}
|
|
293
|
+
}'
|
|
294
|
+
|
|
295
|
+
local vars
|
|
296
|
+
vars=$(jq -n --arg id "$issue_id" '{id: $id}')
|
|
297
|
+
|
|
298
|
+
local response
|
|
299
|
+
response=$(linear_graphql "$query" "$vars" 2>/dev/null) || {
|
|
300
|
+
return 1
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# Normalize output
|
|
304
|
+
echo "$response" | jq '{id: .data.issue.id, title: .data.issue.title, body: .data.issue.description, labels: [.data.issue.labels.nodes[]?.name // empty], state: .data.issue.state.name}' 2>/dev/null || return 1
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
provider_get_issue_body() {
|
|
308
|
+
local issue_id="$1"
|
|
309
|
+
|
|
310
|
+
[[ -z "$issue_id" ]] && return 1
|
|
311
|
+
|
|
312
|
+
provider_load_config
|
|
313
|
+
|
|
314
|
+
local query='query($id: String!) {
|
|
315
|
+
issue(id: $id) {
|
|
316
|
+
description
|
|
317
|
+
}
|
|
318
|
+
}'
|
|
319
|
+
|
|
320
|
+
local vars
|
|
321
|
+
vars=$(jq -n --arg id "$issue_id" '{id: $id}')
|
|
322
|
+
|
|
323
|
+
local response
|
|
324
|
+
response=$(linear_graphql "$query" "$vars" 2>/dev/null) || {
|
|
325
|
+
return 1
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
echo "$response" | jq -r '.data.issue.description // ""' 2>/dev/null || return 1
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
provider_add_label() {
|
|
332
|
+
local issue_id="$1"
|
|
333
|
+
local label="$2"
|
|
334
|
+
|
|
335
|
+
[[ -z "$issue_id" || -z "$label" ]] && return 1
|
|
336
|
+
|
|
337
|
+
provider_load_config
|
|
338
|
+
|
|
339
|
+
# Linear label IDs are required — fetch them
|
|
340
|
+
local query='query {
|
|
341
|
+
labels(first: 100) {
|
|
342
|
+
nodes {id name}
|
|
343
|
+
}
|
|
344
|
+
}'
|
|
345
|
+
|
|
346
|
+
local labels_response
|
|
347
|
+
labels_response=$(linear_graphql "$query" "{}" 2>/dev/null) || return 1
|
|
348
|
+
|
|
349
|
+
local label_id
|
|
350
|
+
label_id=$(echo "$labels_response" | jq -r --arg name "$label" '.data.labels.nodes[] | select(.name == $name) | .id' 2>/dev/null || true)
|
|
351
|
+
|
|
352
|
+
if [[ -z "$label_id" ]]; then
|
|
353
|
+
# Label not found — skip
|
|
354
|
+
return 0
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
local update_query='mutation($issueId: String!, $labelIds: [String!]) {
|
|
358
|
+
issueLabelCreate(issueId: $issueId, labelIds: $labelIds) {
|
|
359
|
+
success
|
|
360
|
+
}
|
|
361
|
+
}'
|
|
362
|
+
|
|
363
|
+
local vars
|
|
364
|
+
vars=$(jq -n --arg issueId "$issue_id" --arg labelId "$label_id" \
|
|
365
|
+
'{issueId: $issueId, labelIds: [$labelId]}')
|
|
366
|
+
|
|
367
|
+
linear_graphql "$update_query" "$vars" >/dev/null 2>&1 || return 1
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
provider_remove_label() {
|
|
371
|
+
local issue_id="$1"
|
|
372
|
+
local label="$2"
|
|
373
|
+
|
|
374
|
+
[[ -z "$issue_id" || -z "$label" ]] && return 1
|
|
375
|
+
|
|
376
|
+
provider_load_config
|
|
377
|
+
|
|
378
|
+
# Linear requires label IDs
|
|
379
|
+
local query='query {
|
|
380
|
+
labels(first: 100) {
|
|
381
|
+
nodes {id name}
|
|
382
|
+
}
|
|
383
|
+
}'
|
|
384
|
+
|
|
385
|
+
local labels_response
|
|
386
|
+
labels_response=$(linear_graphql "$query" "{}" 2>/dev/null) || return 1
|
|
387
|
+
|
|
388
|
+
local label_id
|
|
389
|
+
label_id=$(echo "$labels_response" | jq -r --arg name "$label" '.data.labels.nodes[] | select(.name == $name) | .id' 2>/dev/null || true)
|
|
390
|
+
|
|
391
|
+
if [[ -z "$label_id" ]]; then
|
|
392
|
+
return 0
|
|
393
|
+
fi
|
|
394
|
+
|
|
395
|
+
local update_query='mutation($issueId: String!, $labelIds: [String!]) {
|
|
396
|
+
issueLabelDelete(issueId: $issueId, labelIds: $labelIds) {
|
|
397
|
+
success
|
|
398
|
+
}
|
|
399
|
+
}'
|
|
400
|
+
|
|
401
|
+
local vars
|
|
402
|
+
vars=$(jq -n --arg issueId "$issue_id" --arg labelId "$label_id" \
|
|
403
|
+
'{issueId: $issueId, labelIds: [$labelId]}')
|
|
404
|
+
|
|
405
|
+
linear_graphql "$update_query" "$vars" >/dev/null 2>&1 || return 1
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
provider_comment() {
|
|
409
|
+
local issue_id="$1"
|
|
410
|
+
local body="$2"
|
|
411
|
+
|
|
412
|
+
[[ -z "$issue_id" || -z "$body" ]] && return 1
|
|
413
|
+
|
|
414
|
+
provider_load_config
|
|
415
|
+
linear_add_comment "$issue_id" "$body"
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
provider_close_issue() {
|
|
419
|
+
local issue_id="$1"
|
|
420
|
+
|
|
421
|
+
[[ -z "$issue_id" ]] && return 1
|
|
422
|
+
|
|
423
|
+
provider_load_config
|
|
424
|
+
linear_update_status "$issue_id" "$STATUS_DONE"
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
provider_create_issue() {
|
|
428
|
+
local title="$1"
|
|
429
|
+
local body="$2"
|
|
430
|
+
local labels="${3:-}"
|
|
431
|
+
|
|
432
|
+
[[ -z "$title" ]] && return 1
|
|
433
|
+
|
|
434
|
+
provider_load_config
|
|
435
|
+
|
|
436
|
+
local query='mutation($title: String!, $description: String, $teamId: String!) {
|
|
437
|
+
issueCreate(input: {title: $title, description: $description, teamId: $teamId}) {
|
|
438
|
+
issue {id}
|
|
439
|
+
}
|
|
440
|
+
}'
|
|
441
|
+
|
|
442
|
+
local vars
|
|
443
|
+
vars=$(jq -n --arg title "$title" --arg description "$body" --arg teamId "$LINEAR_TEAM_ID" \
|
|
444
|
+
'{title: $title, description: $description, teamId: $teamId}')
|
|
445
|
+
|
|
446
|
+
local response
|
|
447
|
+
response=$(linear_graphql "$query" "$vars" 2>/dev/null) || {
|
|
448
|
+
return 1
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
local issue_id
|
|
452
|
+
issue_id=$(echo "$response" | jq -r '.data.issueCreate.issue.id // empty' 2>/dev/null)
|
|
453
|
+
|
|
454
|
+
if [[ -z "$issue_id" ]]; then
|
|
455
|
+
return 1
|
|
456
|
+
fi
|
|
457
|
+
|
|
458
|
+
# Add labels if provided
|
|
459
|
+
if [[ -n "$labels" ]]; then
|
|
460
|
+
local label_list
|
|
461
|
+
label_list=$(echo "$labels" | tr ',' '\n' | tr ' ' '\n' | grep -v '^$' || true)
|
|
462
|
+
while IFS= read -r lbl; do
|
|
463
|
+
[[ -n "$lbl" ]] && provider_add_label "$issue_id" "$lbl" || true
|
|
464
|
+
done <<< "$label_list"
|
|
465
|
+
fi
|
|
466
|
+
|
|
467
|
+
echo "{\"id\": \"$issue_id\", \"title\": \"$title\"}"
|
|
468
|
+
}
|
|
469
|
+
|
|
219
470
|
# ─── Find Linear Issue ID from GitHub Issue Body ──────────────────────────
|
|
220
471
|
|
|
221
472
|
find_linear_id() {
|
package/scripts/sw-tracker.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
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -101,6 +101,14 @@ load_tracker_config() {
|
|
|
101
101
|
warn "Jira provider script not found: $SCRIPT_DIR/sw-tracker-jira.sh"
|
|
102
102
|
fi
|
|
103
103
|
;;
|
|
104
|
+
github)
|
|
105
|
+
if [[ -f "$SCRIPT_DIR/sw-tracker-github.sh" ]]; then
|
|
106
|
+
source "$SCRIPT_DIR/sw-tracker-github.sh"
|
|
107
|
+
return 0
|
|
108
|
+
else
|
|
109
|
+
warn "GitHub provider script not found: $SCRIPT_DIR/sw-tracker-github.sh"
|
|
110
|
+
fi
|
|
111
|
+
;;
|
|
104
112
|
none|"") return 0 ;;
|
|
105
113
|
*)
|
|
106
114
|
warn "Unknown tracker provider: $TRACKER_PROVIDER"
|
|
@@ -116,6 +124,98 @@ tracker_available() {
|
|
|
116
124
|
[[ "$TRACKER_PROVIDER" != "none" && -n "$TRACKER_PROVIDER" ]]
|
|
117
125
|
}
|
|
118
126
|
|
|
127
|
+
# ─── Internal Dispatcher ──────────────────────────────────────────────────
|
|
128
|
+
# Routes a provider function call to the loaded provider, with fallback to GitHub
|
|
129
|
+
|
|
130
|
+
_dispatch_provider() {
|
|
131
|
+
local func="$1"
|
|
132
|
+
shift
|
|
133
|
+
|
|
134
|
+
load_tracker_config
|
|
135
|
+
|
|
136
|
+
# Build the function name
|
|
137
|
+
local provider_func="provider_${func}"
|
|
138
|
+
|
|
139
|
+
# Provider scripts define provider_* functions
|
|
140
|
+
if type "$provider_func" &>/dev/null; then
|
|
141
|
+
"$provider_func" "$@"
|
|
142
|
+
return $?
|
|
143
|
+
else
|
|
144
|
+
# Fall back to GitHub if provider doesn't define the function
|
|
145
|
+
# (for backward compatibility with minimal providers like Jira/Linear notify-only)
|
|
146
|
+
if [[ "$TRACKER_PROVIDER" != "github" && "$TRACKER_PROVIDER" != "none" ]]; then
|
|
147
|
+
# Try GitHub provider
|
|
148
|
+
if [[ -f "$SCRIPT_DIR/sw-tracker-github.sh" ]]; then
|
|
149
|
+
source "$SCRIPT_DIR/sw-tracker-github.sh"
|
|
150
|
+
if type "$provider_func" &>/dev/null; then
|
|
151
|
+
"$provider_func" "$@"
|
|
152
|
+
return $?
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
return 1
|
|
157
|
+
fi
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# ─── Discovery Interface ────────────────────────────────────────────────────
|
|
161
|
+
# Used by daemon to discover issues from the configured tracker
|
|
162
|
+
|
|
163
|
+
# Discover issues matching criteria
|
|
164
|
+
# Usage: tracker_discover_issues <label> [state] [limit]
|
|
165
|
+
# Output: JSON array of {id, title, labels[], state}
|
|
166
|
+
tracker_discover_issues() {
|
|
167
|
+
_dispatch_provider "discover_issues" "$@"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# Fetch single issue details
|
|
171
|
+
# Usage: tracker_get_issue <issue_id>
|
|
172
|
+
# Output: JSON {id, title, body, labels[], state}
|
|
173
|
+
tracker_get_issue() {
|
|
174
|
+
_dispatch_provider "get_issue" "$@"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Fetch issue body text only
|
|
178
|
+
# Usage: tracker_get_issue_body <issue_id>
|
|
179
|
+
# Output: plain text body
|
|
180
|
+
tracker_get_issue_body() {
|
|
181
|
+
_dispatch_provider "get_issue_body" "$@"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# ─── CRUD Interface ───────────────────────────────────────────────────────
|
|
185
|
+
# Used by pipeline to modify issues
|
|
186
|
+
|
|
187
|
+
# Add label to issue
|
|
188
|
+
# Usage: tracker_add_label <issue_id> <label>
|
|
189
|
+
tracker_add_label() {
|
|
190
|
+
_dispatch_provider "add_label" "$@"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Remove label from issue
|
|
194
|
+
# Usage: tracker_remove_label <issue_id> <label>
|
|
195
|
+
tracker_remove_label() {
|
|
196
|
+
_dispatch_provider "remove_label" "$@"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# Add comment to issue
|
|
200
|
+
# Usage: tracker_comment <issue_id> <body>
|
|
201
|
+
tracker_comment() {
|
|
202
|
+
_dispatch_provider "comment" "$@"
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Close/resolve issue
|
|
206
|
+
# Usage: tracker_close_issue <issue_id>
|
|
207
|
+
tracker_close_issue() {
|
|
208
|
+
_dispatch_provider "close_issue" "$@"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# Create new issue
|
|
212
|
+
# Usage: tracker_create_issue <title> <body> [labels]
|
|
213
|
+
# Output: JSON {id, title}
|
|
214
|
+
tracker_create_issue() {
|
|
215
|
+
_dispatch_provider "create_issue" "$@"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# ─── Notification Interface (Legacy) ───────────────────────────────────────
|
|
119
219
|
# Route notification to the active provider
|
|
120
220
|
# Usage: tracker_notify <event> <gh_issue> [detail]
|
|
121
221
|
# Events: spawn, started, stage_complete, stage_failed, review, pr-created, completed, done, failed
|
|
@@ -361,9 +461,24 @@ show_help() {
|
|
|
361
461
|
echo -e " ${CYAN}help${RESET} Show this help"
|
|
362
462
|
echo ""
|
|
363
463
|
echo -e "${BOLD}PROVIDERS${RESET}"
|
|
464
|
+
echo -e " ${CYAN}github${RESET} GitHub (native, gh CLI)"
|
|
364
465
|
echo -e " ${CYAN}linear${RESET} Linear.app (GraphQL API)"
|
|
365
466
|
echo -e " ${CYAN}jira${RESET} Atlassian Jira (REST API v3)"
|
|
366
467
|
echo ""
|
|
468
|
+
echo -e "${BOLD}DISCOVERY INTERFACE${RESET} (for daemon)"
|
|
469
|
+
echo -e " Provider-agnostic issue discovery and metadata:"
|
|
470
|
+
echo -e " ${CYAN}tracker_discover_issues${RESET} <label> [state] [limit]"
|
|
471
|
+
echo -e " ${CYAN}tracker_get_issue${RESET} <issue_id>"
|
|
472
|
+
echo -e " ${CYAN}tracker_get_issue_body${RESET} <issue_id>"
|
|
473
|
+
echo ""
|
|
474
|
+
echo -e "${BOLD}CRUD INTERFACE${RESET} (for pipeline)"
|
|
475
|
+
echo -e " Provider-agnostic issue modification:"
|
|
476
|
+
echo -e " ${CYAN}tracker_add_label${RESET} <issue_id> <label>"
|
|
477
|
+
echo -e " ${CYAN}tracker_remove_label${RESET} <issue_id> <label>"
|
|
478
|
+
echo -e " ${CYAN}tracker_comment${RESET} <issue_id> <body>"
|
|
479
|
+
echo -e " ${CYAN}tracker_close_issue${RESET} <issue_id>"
|
|
480
|
+
echo -e " ${CYAN}tracker_create_issue${RESET} <title> <body> [labels]"
|
|
481
|
+
echo ""
|
|
367
482
|
echo -e "${BOLD}NOTIFICATION EVENTS${RESET}"
|
|
368
483
|
echo -e " ${CYAN}spawn${RESET} Pipeline started"
|
|
369
484
|
echo -e " ${CYAN}stage_complete${RESET} Stage finished (detail: stage_id|duration|description)"
|
|
@@ -374,7 +489,7 @@ show_help() {
|
|
|
374
489
|
echo ""
|
|
375
490
|
echo -e "${BOLD}CONFIGURATION${RESET}"
|
|
376
491
|
echo -e " Config file: ${DIM}~/.shipwright/tracker-config.json${RESET}"
|
|
377
|
-
echo -e " Env override: ${DIM}TRACKER_PROVIDER_OVERRIDE=linear|jira|none${RESET}"
|
|
492
|
+
echo -e " Env override: ${DIM}TRACKER_PROVIDER_OVERRIDE=github|linear|jira|none${RESET}"
|
|
378
493
|
echo -e " Daemon block: ${DIM}.claude/daemon-config.json → .tracker.provider${RESET}"
|
|
379
494
|
echo ""
|
|
380
495
|
echo -e "${BOLD}EXAMPLES${RESET}"
|