forgedock 1.1.7 → 1.1.9
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 +10 -33
- package/bin/cinema.mjs +23 -5
- package/bin/engine/invariants.mjs +361 -0
- package/bin/engine-cli.mjs +137 -11
- package/bin/env-detect.mjs +249 -0
- package/bin/forge-utils.mjs +364 -4
- package/bin/forgedock.mjs +1064 -135
- package/bin/hooks/pre-tool-use.mjs +182 -0
- package/bin/hooks/session-start.mjs +68 -3
- package/bin/init-detect.mjs +101 -12
- package/bin/journey.mjs +917 -16
- package/bin/labels.json +10 -0
- package/bin/recall.mjs +557 -0
- package/bin/registry.mjs +91 -3
- package/bin/runner.mjs +38 -2
- package/bin/tui.mjs +49 -3
- package/commands/adopt.md +1 -1
- package/commands/analytics.md +8 -8
- package/commands/audit.md +1 -1
- package/commands/autopilot.md +958 -77
- package/commands/ci-audit.md +1 -1
- package/commands/cleanup.md +139 -55
- package/commands/compat-audit.md +1 -1
- package/commands/deploy-info.md +1 -1
- package/commands/deploy-pr.md +1 -1
- package/commands/diagnose.md +1 -1
- package/commands/explain.md +1 -1
- package/commands/fix-ci.md +1 -1
- package/commands/forgedock-init.md +1 -1
- package/commands/incident-response.md +3 -3
- package/commands/issue.md +1 -1
- package/commands/optimize.md +586 -1
- package/commands/orchestrate/config.md +13 -3
- package/commands/orchestrate/phase-1-resolve.md +36 -26
- package/commands/orchestrate/phase-2-triage.md +1 -1
- package/commands/orchestrate/phase-2.5-synthesis.md +1 -1
- package/commands/orchestrate/phase-3-dependency.md +587 -48
- package/commands/orchestrate/phase-4-execution.md +1031 -57
- package/commands/orchestrate/phase-5-cleanup.md +1 -1
- package/commands/orchestrate/phase-6-report.md +248 -11
- package/commands/orchestrate/safety.md +2 -2
- package/commands/pipeline-health/phase-2-metrics.md +305 -1
- package/commands/pipeline-health/phase-4-proposals.md +127 -0
- package/commands/qa-sweep.md +1 -1
- package/commands/quality-gate.md +418 -1
- package/commands/recover-orphans.md +5 -5
- package/commands/replay.md +1 -1
- package/commands/review-pr-agents/api.md +2 -2
- package/commands/review-pr-agents/auth.md +2 -2
- package/commands/review-pr-agents/billing.md +2 -2
- package/commands/review-pr-agents/concurrency.md +2 -2
- package/commands/review-pr-agents/database.md +2 -2
- package/commands/review-pr-agents/frontend.md +2 -2
- package/commands/review-pr-agents/infra.md +2 -2
- package/commands/review-pr-agents/protocols.md +1 -1
- package/commands/review-pr-agents/scraper.md +2 -2
- package/commands/review-pr-agents/security.md +2 -2
- package/commands/review-pr-staging.md +41 -3
- package/commands/review-pr.md +462 -19
- package/commands/rollback.md +1 -1
- package/commands/scope.md +1 -1
- package/commands/security-audit.md +1 -1
- package/commands/spec-doctor.md +429 -0
- package/commands/test-gate.md +1 -1
- package/commands/validate.md +53 -1
- package/commands/work-on/build/architect.md +78 -1
- package/commands/work-on/build/context.md +360 -22
- package/commands/work-on/build/implement.md +3 -1
- package/commands/work-on/build/validate.md +29 -1
- package/commands/work-on/build.md +75 -6
- package/commands/work-on/close.md +624 -10
- package/commands/work-on/decompose.md +1 -1
- package/commands/work-on/investigate.md +235 -5
- package/commands/work-on/remediate.md +331 -0
- package/commands/work-on/review.md +1 -1
- package/commands/work-on-monolithic.md +1 -1
- package/commands/work-on.md +205 -66
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +1 -1
- package/docs/CONFIG.md +186 -0
- package/docs/FORGE-PROTOCOL.md +101 -0
- package/docs/site/command-reference.md +31 -1
- package/docs/site/getting-started.md +9 -34
- package/docs/site/index.md +1 -1
- package/docs/spec/forge-protocol-v1.md +97 -5
- package/docs/spec/label-state-machine.md +6 -1
- package/package.json +3 -2
- package/packages/protocol/src/cli.js +374 -84
- package/packages/protocol/src/types.js +75 -1
- package/scripts/build-knowledge-index.mjs +1761 -0
- package/scripts/calibration.mjs +996 -0
- package/scripts/check-command-docs-drift.sh +4 -2
- package/scripts/danger-zones.mjs +789 -0
- package/scripts/doctor-pipeline-state.sh +88 -2
- package/scripts/select-fix-targets.sh +234 -0
- package/scripts/transition-label.sh +245 -6
- package/scripts/refresh-bot-token.sh +0 -97
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
# are silently disabled and warrants investigating the underlying `gh` failure.
|
|
30
30
|
#
|
|
31
31
|
# Exit codes:
|
|
32
|
-
# 0 = no findings (pipeline healthy)
|
|
32
|
+
# 0 = no findings (pipeline healthy — all checks ran and passed)
|
|
33
33
|
# 1 = findings present
|
|
34
34
|
# 2 = error (missing deps, bad args)
|
|
35
|
+
# 3 = inconclusive — checks were skipped due to gh API failures (total=0 but checks_skipped>0);
|
|
36
|
+
# pipeline health cannot be confirmed; treat as UNKNOWN, not healthy
|
|
35
37
|
#
|
|
36
38
|
# Depends on: gh (GitHub CLI), jq, date
|
|
37
39
|
#
|
|
@@ -43,6 +45,8 @@
|
|
|
43
45
|
# I5 Issues in workflow:in-review with no open PR
|
|
44
46
|
# I6 PRs open without a FORGE:CONTRACT or FORGE:BUILDER annotation
|
|
45
47
|
# I7 Orphaned worktree branches (local branches fix/* with no open PR)
|
|
48
|
+
# I8 FORGE-referenced gists with public visibility (KNOWLEDGE_GIST, MILESTONE_INDEX,
|
|
49
|
+
# PRIOR_GIST annotations in recent issue comments)
|
|
46
50
|
#
|
|
47
51
|
# /pipeline-health should consume this script's JSON output rather than
|
|
48
52
|
# re-discovering state from scratch.
|
|
@@ -401,6 +405,79 @@ while IFS= read -r row; do
|
|
|
401
405
|
fi
|
|
402
406
|
done < <(echo "$OPEN_PRS" | jq -c '.[]')
|
|
403
407
|
|
|
408
|
+
# ---------------------------------------------------------------------------
|
|
409
|
+
# I8: FORGE-referenced gists with public visibility
|
|
410
|
+
#
|
|
411
|
+
# Scan the last 100 issues (open + closed) for comments containing
|
|
412
|
+
# FORGE:KNOWLEDGE_GIST, FORGE:MILESTONE_INDEX, or FORGE:PRIOR_GIST annotations.
|
|
413
|
+
# Extract the gist ID from each URL and query gh api gists/{id} --jq .public.
|
|
414
|
+
# A "true" result means a pipeline memory gist is world-readable — CRITICAL finding.
|
|
415
|
+
#
|
|
416
|
+
# Fail-open on gh API errors: a network blip or rate-limit must NOT be reported
|
|
417
|
+
# as a confirmed public gist. Uses add_skip (same pattern as I4/I5) so persistent
|
|
418
|
+
# API degradation surfaces in --json degraded[] without inflating CRITICAL count.
|
|
419
|
+
# ---------------------------------------------------------------------------
|
|
420
|
+
RECENT_ISSUES=$(gh issue list $GH_FLAG \
|
|
421
|
+
--state all \
|
|
422
|
+
--limit 100 \
|
|
423
|
+
--json number \
|
|
424
|
+
--jq '.[].number' 2>"$GH_STDERR_TMP" || true)
|
|
425
|
+
|
|
426
|
+
I8_FETCH_ERR=$(cat "$GH_STDERR_TMP")
|
|
427
|
+
if [ -z "$RECENT_ISSUES" ] && [ -n "$I8_FETCH_ERR" ]; then
|
|
428
|
+
echo "WARNING: gh issue list failed for I8 gist-visibility scan — check inconclusive, skipping: $I8_FETCH_ERR" >&2
|
|
429
|
+
add_skip "I8" "all" "gh issue list failed: $(bounded_reason "$I8_FETCH_ERR")"
|
|
430
|
+
fi
|
|
431
|
+
|
|
432
|
+
for issue_num in $RECENT_ISSUES; do
|
|
433
|
+
# Fetch comments and extract FORGE gist annotation URLs
|
|
434
|
+
if ! ISSUE_COMMENTS=$(gh issue view "$issue_num" $GH_FLAG \
|
|
435
|
+
--json comments \
|
|
436
|
+
--jq '[.comments[].body] | join("\n")' 2>"$GH_STDERR_TMP"); then
|
|
437
|
+
echo "WARNING: gh issue view failed for #$issue_num (I8) — skipping: $(cat "$GH_STDERR_TMP")" >&2
|
|
438
|
+
add_skip "I8" "$issue_num" "gh issue view failed: $(bounded_reason "$(cat "$GH_STDERR_TMP")")"
|
|
439
|
+
continue
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
# Extract gist URLs from all three FORGE annotation types.
|
|
443
|
+
# The character class [^\s >]+ matches any non-whitespace, non-space, non-> char,
|
|
444
|
+
# which covers standard usernames, bot accounts (e.g. rapiercraft-forge[bot]),
|
|
445
|
+
# and gist hashes — all valid in a gist URL before the closing ' -->'.
|
|
446
|
+
GIST_URLS=$(echo "$ISSUE_COMMENTS" \
|
|
447
|
+
| grep -oE '<!-- FORGE:(KNOWLEDGE_GIST|PRIOR_GIST): https://gist\.github\.com/[^ >]+ -->' \
|
|
448
|
+
| grep -oE 'https://gist\.github\.com/[^ >]+' || true)
|
|
449
|
+
# FORGE:MILESTONE_INDEX may appear in milestone description, not comments, but
|
|
450
|
+
# some pipelines also post it as a comment — include it in the scan.
|
|
451
|
+
MILESTONE_GIST_URLS=$(echo "$ISSUE_COMMENTS" \
|
|
452
|
+
| grep -oE '<!-- FORGE:MILESTONE_INDEX: https://gist\.github\.com/[^ >]+ -->' \
|
|
453
|
+
| grep -oE 'https://gist\.github\.com/[^ >]+' || true)
|
|
454
|
+
ALL_GIST_URLS=$(printf '%s\n%s\n' "$GIST_URLS" "$MILESTONE_GIST_URLS" | grep -v '^$' | sort -u || true)
|
|
455
|
+
|
|
456
|
+
for gist_url in $ALL_GIST_URLS; do
|
|
457
|
+
# Extract gist ID: last path component after the username segment
|
|
458
|
+
gist_id=$(echo "$gist_url" | sed 's|.*/||')
|
|
459
|
+
if [ -z "$gist_id" ]; then
|
|
460
|
+
continue
|
|
461
|
+
fi
|
|
462
|
+
|
|
463
|
+
# Query gist visibility — fail-open on API error (rate limit, auth, network)
|
|
464
|
+
if ! IS_PUBLIC=$(gh api "gists/${gist_id}" --jq '.public' 2>"$GH_STDERR_TMP"); then
|
|
465
|
+
echo "WARNING: gh api gists/${gist_id} failed (I8) — skipping: $(cat "$GH_STDERR_TMP")" >&2
|
|
466
|
+
add_skip "I8" "$issue_num" "gh api gists/${gist_id} failed: $(bounded_reason "$(cat "$GH_STDERR_TMP")")"
|
|
467
|
+
continue
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
if [ "$IS_PUBLIC" = "true" ]; then
|
|
471
|
+
add_finding \
|
|
472
|
+
"public_forge_gist" "critical" \
|
|
473
|
+
"$issue_num" "gist_visibility" "0" \
|
|
474
|
+
"FORGE:KNOWLEDGE_GIST/FORGE:MILESTONE_INDEX/FORGE:PRIOR_GIST" \
|
|
475
|
+
"gh api gists/${gist_id} --jq '.public'" \
|
|
476
|
+
"Issue #${issue_num} references a public FORGE gist: ${gist_url} — gist ID: ${gist_id}. Pipeline memory gists (FORGE:KNOWLEDGE_GIST, FORGE:MILESTONE_INDEX, FORGE:PRIOR_GIST) MUST be secret. Public gists expose root causes, file paths, and security findings to the open internet. Invariant I8 broken. Migration: gh gist view ${gist_id} > /tmp/gist_migrate.md && gh gist delete ${gist_id} --yes && gh gist create /tmp/gist_migrate.md --desc '<original description>' (no --public). Then update the annotation URL on issue #${issue_num}."
|
|
477
|
+
fi
|
|
478
|
+
done
|
|
479
|
+
done
|
|
480
|
+
|
|
404
481
|
# ---------------------------------------------------------------------------
|
|
405
482
|
# Summary
|
|
406
483
|
# ---------------------------------------------------------------------------
|
|
@@ -429,6 +506,12 @@ if $JSON_MODE; then
|
|
|
429
506
|
--argjson findings "$FINDINGS_JSON" \
|
|
430
507
|
--argjson summary "$SUMMARY_JSON" \
|
|
431
508
|
'{findings:$findings,summary:$summary}'
|
|
509
|
+
# WIRE:PROVEN — reachable when gh API fails during a doctor run with no prior findings:
|
|
510
|
+
# all I1/I2/I3/I6 checks fail and add_skip() is called; CHECKS_SKIPPED > 0; TOTAL stays 0.
|
|
511
|
+
# Verified by the scenario in #1590: total gh outage → all list checks skipped → previously exit 0.
|
|
512
|
+
if [ "$TOTAL" -eq 0 ] && [ "$CHECKS_SKIPPED" -gt 0 ]; then
|
|
513
|
+
exit 3 # INCONCLUSIVE — checks skipped due to gh failures; cannot confirm healthy
|
|
514
|
+
fi
|
|
432
515
|
[ "$TOTAL" -eq 0 ] && exit 0 || exit 1
|
|
433
516
|
fi
|
|
434
517
|
|
|
@@ -443,11 +526,14 @@ if [ "$CHECKS_SKIPPED" -gt 0 ]; then
|
|
|
443
526
|
fi
|
|
444
527
|
|
|
445
528
|
if [ "$TOTAL" -eq 0 ]; then
|
|
529
|
+
# WIRE:PROVEN — same trigger as JSON mode branch above: TOTAL=0 and CHECKS_SKIPPED>0.
|
|
530
|
+
# This branch executes when the outer 'if [ "$TOTAL" -eq 0 ]' block is entered
|
|
531
|
+
# and CHECKS_SKIPPED > 0, i.e., at least one gh API call failed during the run.
|
|
446
532
|
if [ "$CHECKS_SKIPPED" -gt 0 ]; then
|
|
447
533
|
echo " No stalls detected in checks that ran, but $CHECKS_SKIPPED check(s) were skipped"
|
|
448
534
|
echo " due to gh API failures — pipeline health is INCONCLUSIVE, not confirmed healthy."
|
|
449
535
|
echo ""
|
|
450
|
-
exit
|
|
536
|
+
exit 3 # INCONCLUSIVE — cannot confirm healthy when checks were skipped
|
|
451
537
|
fi
|
|
452
538
|
echo " No stalls detected. Pipeline is healthy."
|
|
453
539
|
echo ""
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# select-fix-targets.sh — Deterministic priority-ranked issue selection for autopilot
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# select-fix-targets.sh [-R <owner/repo>] [--limit <n>] [--fixture-test]
|
|
6
|
+
#
|
|
7
|
+
# -R <owner/repo> : GitHub repository (optional, defaults to current repo)
|
|
8
|
+
# --limit <n> : Maximum number of issues to output (optional, default: 10)
|
|
9
|
+
# --fixture-test : Run against synthetic fixture data instead of GitHub API.
|
|
10
|
+
# Exits 0 if output matches expected ranking, 1 otherwise.
|
|
11
|
+
# Use this for wire-through proof per #1731.
|
|
12
|
+
#
|
|
13
|
+
# Output:
|
|
14
|
+
# One issue number per line, in priority order (highest priority first).
|
|
15
|
+
# Suitable for: for ISSUE_NUM in $(select-fix-targets.sh); do ... done
|
|
16
|
+
#
|
|
17
|
+
# Ranking algorithm (deterministic — no LLM required):
|
|
18
|
+
# 1. P0 issues first (priority:P0 label)
|
|
19
|
+
# 2. P1 issues second (priority:P1 label)
|
|
20
|
+
# 3. P2 issues third (priority:P2 label)
|
|
21
|
+
# 4. Unlabeled issues (no priority:P* label) are EXCLUDED — never selected autonomously
|
|
22
|
+
#
|
|
23
|
+
# Tie-breaker within each priority band: oldest createdAt wins.
|
|
24
|
+
# This ensures fresh self-filed findings (from /autopilot recon) never outrank
|
|
25
|
+
# older human-filed issues at the same priority level. See: #1752.
|
|
26
|
+
#
|
|
27
|
+
# Exclusions (issues never selected, regardless of priority):
|
|
28
|
+
# - Issues with needs-human label
|
|
29
|
+
# - Issues with a milestone (feature-lane issues — autopilot handles these in Phase 3)
|
|
30
|
+
# - Issues with terminal workflow labels (workflow:merged, workflow:invalid, workflow:decomposed)
|
|
31
|
+
# - Issues with active workflow labels (workflow:building, workflow:in-review) — already in flight
|
|
32
|
+
#
|
|
33
|
+
# Exit codes: 0 = success (or fixture test passed), 1 = error (or fixture test failed)
|
|
34
|
+
#
|
|
35
|
+
# Added: forge#1752
|
|
36
|
+
|
|
37
|
+
set -euo pipefail
|
|
38
|
+
|
|
39
|
+
GH_REPO_ARGS=()
|
|
40
|
+
LIMIT=10
|
|
41
|
+
FIXTURE_TEST=false
|
|
42
|
+
|
|
43
|
+
# Parse arguments
|
|
44
|
+
while [ $# -gt 0 ]; do
|
|
45
|
+
case "$1" in
|
|
46
|
+
-R)
|
|
47
|
+
if [ $# -lt 2 ]; then
|
|
48
|
+
echo "ERROR: -R requires a value <owner/repo>" >&2
|
|
49
|
+
echo "Usage: select-fix-targets.sh [-R <owner/repo>] [--limit <n>] [--fixture-test]" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
if ! [[ "$2" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
|
|
53
|
+
echo "ERROR: -R value must be owner/repo format, got: $2" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
GH_REPO_ARGS=(-R "$2")
|
|
57
|
+
shift 2
|
|
58
|
+
;;
|
|
59
|
+
--limit)
|
|
60
|
+
if [ $# -lt 2 ] || ! [[ "$2" =~ ^[0-9]+$ ]]; then
|
|
61
|
+
echo "ERROR: --limit requires a positive integer" >&2
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
LIMIT="$2"
|
|
65
|
+
shift 2
|
|
66
|
+
;;
|
|
67
|
+
--fixture-test)
|
|
68
|
+
FIXTURE_TEST=true
|
|
69
|
+
shift
|
|
70
|
+
;;
|
|
71
|
+
*)
|
|
72
|
+
echo "ERROR: unknown argument: $1" >&2
|
|
73
|
+
echo "Usage: select-fix-targets.sh [-R <owner/repo>] [--limit <n>] [--fixture-test]" >&2
|
|
74
|
+
exit 1
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# Fixture test mode (wire-through proof per #1731)
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# Runs the ranking logic against synthetic JSON that exercises every edge case:
|
|
83
|
+
# - P0 issue (should be first)
|
|
84
|
+
# - P1 issue (should be second)
|
|
85
|
+
# - Unlabeled issue (should be excluded)
|
|
86
|
+
# - Fresh P2 self-filed finding (should be last among selected, after P0 and P1)
|
|
87
|
+
# - Older P2 issue (should appear before fresh P2 within the P2 band)
|
|
88
|
+
# Expected output order: 100 (P0), 101 (P1, older), 103 (P2, older), 104 (P2, newer)
|
|
89
|
+
# Issues 102 (unlabeled) and 105 (needs-human) must be absent from output.
|
|
90
|
+
#
|
|
91
|
+
# This test does NOT make GitHub API calls — it validates the jq pipeline directly.
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
if [ "$FIXTURE_TEST" = "true" ]; then
|
|
94
|
+
FIXTURE_JSON='[
|
|
95
|
+
{
|
|
96
|
+
"number": 100,
|
|
97
|
+
"createdAt": "2026-01-01T00:00:00Z",
|
|
98
|
+
"milestone": null,
|
|
99
|
+
"labels": [{"name": "priority:P0"}, {"name": "bug"}]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"number": 101,
|
|
103
|
+
"createdAt": "2026-01-02T00:00:00Z",
|
|
104
|
+
"milestone": null,
|
|
105
|
+
"labels": [{"name": "priority:P1"}]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"number": 102,
|
|
109
|
+
"createdAt": "2026-01-03T00:00:00Z",
|
|
110
|
+
"milestone": null,
|
|
111
|
+
"labels": [{"name": "bug"}]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"number": 103,
|
|
115
|
+
"createdAt": "2026-03-01T00:00:00Z",
|
|
116
|
+
"milestone": null,
|
|
117
|
+
"labels": [{"name": "priority:P2"}]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"number": 104,
|
|
121
|
+
"createdAt": "2026-07-08T01:30:00Z",
|
|
122
|
+
"milestone": null,
|
|
123
|
+
"labels": [{"name": "priority:P2"}, {"name": "bug"}]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"number": 105,
|
|
127
|
+
"createdAt": "2026-01-01T00:00:00Z",
|
|
128
|
+
"milestone": null,
|
|
129
|
+
"labels": [{"name": "priority:P0"}, {"name": "needs-human"}]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"number": 106,
|
|
133
|
+
"createdAt": "2026-01-01T00:00:00Z",
|
|
134
|
+
"milestone": {"title": "v2.0"},
|
|
135
|
+
"labels": [{"name": "priority:P0"}]
|
|
136
|
+
}
|
|
137
|
+
]'
|
|
138
|
+
|
|
139
|
+
# Expected order: 100, 101, 103, 104
|
|
140
|
+
# Excluded: 102 (no priority label), 105 (needs-human), 106 (has milestone)
|
|
141
|
+
EXPECTED="100
|
|
142
|
+
101
|
|
143
|
+
103
|
|
144
|
+
104"
|
|
145
|
+
|
|
146
|
+
ACTUAL=$(echo "$FIXTURE_JSON" | jq -r '
|
|
147
|
+
# Step 1: Filter out exclusions
|
|
148
|
+
[.[] | select(
|
|
149
|
+
.milestone == null and
|
|
150
|
+
(.labels | map(.name) | any(. == "needs-human" or
|
|
151
|
+
. == "workflow:merged" or . == "workflow:invalid" or
|
|
152
|
+
. == "workflow:decomposed" or . == "workflow:building" or
|
|
153
|
+
. == "workflow:in-review") | not) and
|
|
154
|
+
# Only include issues with an explicit priority:P* label (unlabeled = excluded)
|
|
155
|
+
(.labels | map(.name) | any(startswith("priority:P")))
|
|
156
|
+
)] |
|
|
157
|
+
# Step 2: Assign numeric priority rank (lower = higher priority)
|
|
158
|
+
# P0 → 0, P1 → 1, P2 → 2, any other priority:P* → 9 (future-proof)
|
|
159
|
+
map(. + {
|
|
160
|
+
"_rank": (
|
|
161
|
+
.labels | map(.name) |
|
|
162
|
+
if any(. == "priority:P0") then 0
|
|
163
|
+
elif any(. == "priority:P1") then 1
|
|
164
|
+
elif any(. == "priority:P2") then 2
|
|
165
|
+
else 9
|
|
166
|
+
end
|
|
167
|
+
)
|
|
168
|
+
}) |
|
|
169
|
+
# Step 3: Sort by rank ASC, then by createdAt ASC (oldest wins within same band)
|
|
170
|
+
sort_by([._rank, .createdAt]) |
|
|
171
|
+
# Step 4: Output issue numbers
|
|
172
|
+
.[].number
|
|
173
|
+
')
|
|
174
|
+
|
|
175
|
+
if [ "$ACTUAL" = "$EXPECTED" ]; then
|
|
176
|
+
echo "FIXTURE TEST PASSED — ranking: $(echo "$ACTUAL" | tr '\n' ' ')"
|
|
177
|
+
echo "Excluded correctly: 102 (unlabeled), 105 (needs-human), 106 (has milestone)"
|
|
178
|
+
exit 0
|
|
179
|
+
else
|
|
180
|
+
echo "FIXTURE TEST FAILED" >&2
|
|
181
|
+
echo "Expected order: $(echo "$EXPECTED" | tr '\n' ' ')" >&2
|
|
182
|
+
echo "Actual order: $(echo "$ACTUAL" | tr '\n' ' ')" >&2
|
|
183
|
+
echo "" >&2
|
|
184
|
+
echo "Diff:" >&2
|
|
185
|
+
diff <(echo "$EXPECTED") <(echo "$ACTUAL") >&2
|
|
186
|
+
exit 1
|
|
187
|
+
fi
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
# Live mode: query GitHub and apply ranking
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
# Fetch open issues — get all candidates in one API call.
|
|
195
|
+
# JSON fields: number, createdAt, milestone (null or object), labels (array of name objects).
|
|
196
|
+
# Limit to 200 issues to match the rest of autopilot's queries; ranking is applied in jq.
|
|
197
|
+
ISSUES_JSON=$(gh issue list "${GH_REPO_ARGS[@]}" \
|
|
198
|
+
--state open \
|
|
199
|
+
--limit 200 \
|
|
200
|
+
--json number,createdAt,milestone,labels \
|
|
201
|
+
2>/dev/null || echo '[]')
|
|
202
|
+
|
|
203
|
+
if [ "$ISSUES_JSON" = "[]" ] || [ -z "$ISSUES_JSON" ]; then
|
|
204
|
+
echo "" # No issues — empty output is valid (caller loops over nothing)
|
|
205
|
+
exit 0
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
echo "$ISSUES_JSON" | jq -r --argjson limit "$LIMIT" '
|
|
209
|
+
# Step 1: Filter out exclusions (same logic as fixture test)
|
|
210
|
+
[.[] | select(
|
|
211
|
+
.milestone == null and
|
|
212
|
+
(.labels | map(.name) | any(. == "needs-human" or
|
|
213
|
+
. == "workflow:merged" or . == "workflow:invalid" or
|
|
214
|
+
. == "workflow:decomposed" or . == "workflow:building" or
|
|
215
|
+
. == "workflow:in-review") | not) and
|
|
216
|
+
# Only include issues with an explicit priority:P* label (unlabeled = excluded)
|
|
217
|
+
(.labels | map(.name) | any(startswith("priority:P")))
|
|
218
|
+
)] |
|
|
219
|
+
# Step 2: Assign numeric priority rank
|
|
220
|
+
map(. + {
|
|
221
|
+
"_rank": (
|
|
222
|
+
.labels | map(.name) |
|
|
223
|
+
if any(. == "priority:P0") then 0
|
|
224
|
+
elif any(. == "priority:P1") then 1
|
|
225
|
+
elif any(. == "priority:P2") then 2
|
|
226
|
+
else 9
|
|
227
|
+
end
|
|
228
|
+
)
|
|
229
|
+
}) |
|
|
230
|
+
# Step 3: Sort by rank ASC, then by createdAt ASC (oldest wins within same priority band)
|
|
231
|
+
sort_by([._rank, .createdAt]) |
|
|
232
|
+
# Step 4: Apply limit and output issue numbers
|
|
233
|
+
.[:$limit] | .[].number
|
|
234
|
+
'
|
|
@@ -2,37 +2,138 @@
|
|
|
2
2
|
# transition-label.sh — Workflow label state machine for ForgeDock pipeline
|
|
3
3
|
#
|
|
4
4
|
# Usage: transition-label.sh <ISSUE_NUMBER> <GH_FLAG...> <TARGET_STATE>
|
|
5
|
+
# OR: transition-label.sh --validate <VERDICT> <ISSUE_NUMBER> [GH_FLAG...]
|
|
5
6
|
#
|
|
6
7
|
# ISSUE_NUMBER GitHub issue number (e.g. 674)
|
|
7
8
|
# GH_FLAG Repository flag passed to gh (e.g. -R RapierCraftStudios/forgedock)
|
|
8
9
|
# May be multiple tokens — pass before TARGET_STATE
|
|
9
10
|
# TARGET_STATE One of: investigating, ready-to-build, building, in-review,
|
|
10
|
-
# merged, invalid, decomposed
|
|
11
|
+
# merged, invalid, decomposed, awaiting-merge
|
|
12
|
+
#
|
|
13
|
+
# --validate Sub-command mode: translate an investigation verdict into a
|
|
14
|
+
# finding-lifecycle label (needs-validation → validated/false-positive).
|
|
15
|
+
# VERDICT One of: CONFIRMED, NOT-CONFIRMED, INVALID, PARTIAL
|
|
16
|
+
# CONFIRMED → validated; all others → false-positive
|
|
11
17
|
#
|
|
12
18
|
# Examples:
|
|
13
19
|
# transition-label.sh 674 -R RapierCraftStudios/forgedock investigating
|
|
14
20
|
# transition-label.sh 674 -R owner/repo ready-to-build
|
|
21
|
+
# transition-label.sh --validate CONFIRMED 674 -R owner/repo
|
|
22
|
+
# transition-label.sh --validate NOT-CONFIRMED 674 -R owner/repo
|
|
15
23
|
#
|
|
16
|
-
# Behavior:
|
|
24
|
+
# Behavior (workflow mode):
|
|
17
25
|
# 1. Validates ISSUE_NUMBER and TARGET_STATE
|
|
18
26
|
# 2. Verifies the issue exists (exits 1 if not)
|
|
19
27
|
# 3. Adds workflow:{TARGET_STATE} label
|
|
20
28
|
# 4. Removes all other workflow:* labels atomically
|
|
21
29
|
#
|
|
30
|
+
# Behavior (--validate mode):
|
|
31
|
+
# 1. Validates VERDICT and ISSUE_NUMBER
|
|
32
|
+
# 2. Verifies issue exists and has needs-validation label (exits 0 silently if not)
|
|
33
|
+
# 3. Adds validated (CONFIRMED) or false-positive (all other verdicts) label
|
|
34
|
+
# 4. Removes needs-validation label
|
|
35
|
+
# 5. Idempotent: if already labeled validated/false-positive, exits 0 silently
|
|
36
|
+
#
|
|
22
37
|
# Exit codes: 0 = success, 1 = error (bad args, issue not found, gh failure)
|
|
23
38
|
|
|
24
39
|
set -euo pipefail
|
|
25
40
|
|
|
26
41
|
# ---------------------------------------------------------------------------
|
|
27
|
-
#
|
|
42
|
+
# --validate sub-command: finding lifecycle label transition
|
|
43
|
+
# Signature: --validate <VERDICT> <ISSUE_NUMBER> [GH_FLAG...]
|
|
44
|
+
# Separate from the workflow state machine — operates on needs-validation,
|
|
45
|
+
# validated, false-positive labels only; never touches workflow:* labels.
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
if [ "${1:-}" = "--validate" ]; then
|
|
48
|
+
shift
|
|
49
|
+
|
|
50
|
+
if [ "$#" -lt 2 ]; then
|
|
51
|
+
echo "ERROR: Usage: transition-label.sh --validate <VERDICT> <ISSUE_NUMBER> [GH_FLAG...]" >&2
|
|
52
|
+
echo " VERDICT: CONFIRMED | NOT-CONFIRMED | INVALID | PARTIAL" >&2
|
|
53
|
+
echo " Example: transition-label.sh --validate CONFIRMED 674 -R owner/repo" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
VERDICT="$1"
|
|
58
|
+
shift
|
|
59
|
+
ISSUE_NUMBER="$1"
|
|
60
|
+
shift
|
|
61
|
+
GH_ARGS=("$@")
|
|
62
|
+
|
|
63
|
+
# Validate verdict
|
|
64
|
+
case "$VERDICT" in
|
|
65
|
+
CONFIRMED|NOT-CONFIRMED|INVALID|PARTIAL) ;;
|
|
66
|
+
*)
|
|
67
|
+
echo "ERROR: Unknown verdict: '$VERDICT'" >&2
|
|
68
|
+
echo " Valid verdicts: CONFIRMED, NOT-CONFIRMED, INVALID, PARTIAL" >&2
|
|
69
|
+
exit 1
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
|
|
73
|
+
# Validate issue number
|
|
74
|
+
if ! [[ "$ISSUE_NUMBER" =~ ^[0-9]+$ ]]; then
|
|
75
|
+
echo "ERROR: ISSUE_NUMBER must be a positive integer, got: '$ISSUE_NUMBER'" >&2
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Verify issue exists
|
|
80
|
+
if ! gh issue view "$ISSUE_NUMBER" "${GH_ARGS[@]}" --json number >/dev/null 2>&1; then
|
|
81
|
+
echo "ERROR: Issue #$ISSUE_NUMBER not found (GH_FLAG: ${GH_ARGS[*]:-<none>})" >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Check if issue carries needs-validation (idempotent gate)
|
|
86
|
+
CURRENT_LABELS=$(gh issue view "$ISSUE_NUMBER" "${GH_ARGS[@]}" --json labels \
|
|
87
|
+
--jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
88
|
+
|
|
89
|
+
if ! echo "$CURRENT_LABELS" | grep -q "needs-validation"; then
|
|
90
|
+
echo "OK: Issue #$ISSUE_NUMBER does not have needs-validation — no action taken (idempotent)"
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Check if already resolved (idempotent)
|
|
95
|
+
if echo "$CURRENT_LABELS" | grep -qE "validated|false-positive"; then
|
|
96
|
+
echo "OK: Issue #$ISSUE_NUMBER already has a resolved verdict label — no action taken (idempotent)"
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Map verdict to label
|
|
101
|
+
if [ "$VERDICT" = "CONFIRMED" ]; then
|
|
102
|
+
TARGET_VERDICT_LABEL="validated"
|
|
103
|
+
echo "Verdict CONFIRMED → adding 'validated' to issue #$ISSUE_NUMBER..."
|
|
104
|
+
else
|
|
105
|
+
TARGET_VERDICT_LABEL="false-positive"
|
|
106
|
+
echo "Verdict $VERDICT → adding 'false-positive' to issue #$ISSUE_NUMBER..."
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Add verdict label
|
|
110
|
+
gh issue edit "$ISSUE_NUMBER" "${GH_ARGS[@]}" --add-label "$TARGET_VERDICT_LABEL"
|
|
111
|
+
|
|
112
|
+
# Remove needs-validation
|
|
113
|
+
gh issue edit "$ISSUE_NUMBER" "${GH_ARGS[@]}" --remove-label "needs-validation" 2>/dev/null || true
|
|
114
|
+
|
|
115
|
+
echo "OK: needs-validation → $TARGET_VERDICT_LABEL on issue #$ISSUE_NUMBER"
|
|
116
|
+
exit 0
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Argument parsing (workflow state machine mode)
|
|
28
121
|
# Signature: <ISSUE_NUMBER> [GH_FLAG...] <TARGET_STATE>
|
|
29
122
|
# We consume ISSUE_NUMBER as $1, TARGET_STATE as the last arg, everything
|
|
30
123
|
# in between is GH_FLAG (e.g. -R RapierCraftStudios/forgedock).
|
|
31
124
|
# ---------------------------------------------------------------------------
|
|
32
125
|
|
|
126
|
+
# Deferred exit code (forge#1929) — set to 1 by the persistent stale-label
|
|
127
|
+
# removal failure branch below instead of exiting immediately, so that the
|
|
128
|
+
# unconditional needs-human clear step (scoped to awaiting-merge) always
|
|
129
|
+
# gets a chance to run before the script actually exits. Initialized here,
|
|
130
|
+
# before any conditional logic, so `set -u` never sees it unbound.
|
|
131
|
+
TRANSITION_EXIT_CODE=0
|
|
132
|
+
|
|
33
133
|
if [ "$#" -lt 2 ]; then
|
|
34
134
|
echo "ERROR: Usage: transition-label.sh <ISSUE_NUMBER> [GH_FLAG...] <TARGET_STATE>" >&2
|
|
35
135
|
echo " Example: transition-label.sh 674 -R RapierCraftStudios/forgedock investigating" >&2
|
|
136
|
+
echo " OR: transition-label.sh --validate CONFIRMED 674 -R owner/repo" >&2
|
|
36
137
|
exit 1
|
|
37
138
|
fi
|
|
38
139
|
|
|
@@ -65,6 +166,7 @@ VALID_STATES=(
|
|
|
65
166
|
"merged"
|
|
66
167
|
"invalid"
|
|
67
168
|
"decomposed"
|
|
169
|
+
"awaiting-merge"
|
|
68
170
|
)
|
|
69
171
|
|
|
70
172
|
# ---------------------------------------------------------------------------
|
|
@@ -161,9 +263,146 @@ echo "Adding $EFFECTIVE_LABEL to issue #$ISSUE_NUMBER..."
|
|
|
161
263
|
gh issue edit "$ISSUE_NUMBER" "${GH_ARGS[@]}" --add-label "$EFFECTIVE_LABEL"
|
|
162
264
|
|
|
163
265
|
# ---------------------------------------------------------------------------
|
|
164
|
-
# Remove all other workflow:* labels (best-effort
|
|
266
|
+
# Remove all other workflow:* labels currently on the issue (best-effort)
|
|
267
|
+
#
|
|
268
|
+
# IMPORTANT: `gh issue edit --remove-label` is atomic across its whole
|
|
269
|
+
# comma-separated argument — if ANY label in the list is not a valid label
|
|
270
|
+
# on the repo (e.g. a newly added VALID_STATES entry like awaiting-merge
|
|
271
|
+
# whose repo-side `gh label create` / bootstrap hasn't run yet), the ENTIRE
|
|
272
|
+
# call fails with "not found" and — under `set -euo pipefail` without the
|
|
273
|
+
# `|| true` this used to silently swallow — no labels are removed at all,
|
|
274
|
+
# including ones that DO exist and SHOULD have been cleared (forge#1810
|
|
275
|
+
# follow-up: this exact bug was caught by dogfooding this script against
|
|
276
|
+
# the live repo before `workflow:awaiting-merge` had been bootstrapped).
|
|
277
|
+
#
|
|
278
|
+
# Fix: only ask to remove labels that are BOTH (a) in REMOVE_LABELS (valid
|
|
279
|
+
# states other than the target) AND (b) actually present on the issue right
|
|
280
|
+
# now. A label that was never applied to this issue can't be "not found" on
|
|
281
|
+
# the repo without failing the whole call, and a label the issue doesn't
|
|
282
|
+
# have doesn't need removing anyway — so intersecting against the issue's
|
|
283
|
+
# current labels sidesteps the all-or-nothing failure mode entirely instead
|
|
284
|
+
# of relying on `|| true` to mask it.
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
CURRENT_ISSUE_LABELS=$(gh issue view "$ISSUE_NUMBER" "${GH_ARGS[@]}" --json labels \
|
|
287
|
+
--jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
288
|
+
|
|
289
|
+
TO_REMOVE=""
|
|
290
|
+
IFS=',' read -ra REMOVE_CANDIDATES <<< "$REMOVE_LABELS"
|
|
291
|
+
for candidate in "${REMOVE_CANDIDATES[@]}"; do
|
|
292
|
+
case ",$CURRENT_ISSUE_LABELS," in
|
|
293
|
+
*",$candidate,"*)
|
|
294
|
+
TO_REMOVE="${TO_REMOVE:+$TO_REMOVE,}$candidate"
|
|
295
|
+
;;
|
|
296
|
+
esac
|
|
297
|
+
done
|
|
298
|
+
|
|
299
|
+
if [ -n "$TO_REMOVE" ]; then
|
|
300
|
+
echo "Removing stale workflow:* labels present on the issue ($TO_REMOVE)..."
|
|
301
|
+
gh issue edit "$ISSUE_NUMBER" "${GH_ARGS[@]}" --remove-label "$TO_REMOVE" 2>/dev/null || true
|
|
302
|
+
|
|
303
|
+
# -------------------------------------------------------------------------
|
|
304
|
+
# Post-condition verification + single retry (forge#1915)
|
|
305
|
+
#
|
|
306
|
+
# The intersection fix above (forge#1810) already prevents the "unbootstrapped
|
|
307
|
+
# label 404" all-or-nothing failure — every candidate in $TO_REMOVE is known
|
|
308
|
+
# to exist on the issue at the time it was computed. But the removal call
|
|
309
|
+
# itself can still fail for reasons unrelated to label existence (a transient
|
|
310
|
+
# `gh`/GitHub API hiccup, rate limiting, a dropped connection), and that
|
|
311
|
+
# failure was previously swallowed unconditionally by `2>/dev/null || true`
|
|
312
|
+
# with no verification — leaving stale workflow:* labels stacked alongside
|
|
313
|
+
# the newly-added terminal label with no diagnostic trail. Live evidence:
|
|
314
|
+
# issue #1892 closed with both workflow:in-review and workflow:merged applied
|
|
315
|
+
# simultaneously because this exact removal call silently failed once.
|
|
316
|
+
#
|
|
317
|
+
# Fix: re-fetch the issue's labels after the removal call. If any label in
|
|
318
|
+
# $TO_REMOVE is still present, retry the removal once. If it's *still*
|
|
319
|
+
# present after the retry, this is a real, persistent failure — surface it
|
|
320
|
+
# loudly (stderr ERROR + non-zero exit) instead of the unconditional "OK".
|
|
321
|
+
#
|
|
322
|
+
# forge#1929: the non-zero exit is DEFERRED (via $TRANSITION_EXIT_CODE)
|
|
323
|
+
# rather than immediate. This block runs before the needs-human clear step
|
|
324
|
+
# further down, which is scoped to TARGET_STATE=awaiting-merge and must
|
|
325
|
+
# run regardless of this failure — it is unrelated to stale-label removal.
|
|
326
|
+
# An immediate `exit 1` here would silently skip that unrelated cleanup
|
|
327
|
+
# step in the one case (persistent removal failure) it's most important
|
|
328
|
+
# for the caller to see a consistent, fully-applied label state.
|
|
329
|
+
# -------------------------------------------------------------------------
|
|
330
|
+
POST_REMOVE_LABELS=$(gh issue view "$ISSUE_NUMBER" "${GH_ARGS[@]}" --json labels \
|
|
331
|
+
--jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
332
|
+
|
|
333
|
+
STILL_PRESENT=""
|
|
334
|
+
IFS=',' read -ra TO_REMOVE_CHECK <<< "$TO_REMOVE"
|
|
335
|
+
for candidate in "${TO_REMOVE_CHECK[@]}"; do
|
|
336
|
+
case ",$POST_REMOVE_LABELS," in
|
|
337
|
+
*",$candidate,"*)
|
|
338
|
+
STILL_PRESENT="${STILL_PRESENT:+$STILL_PRESENT,}$candidate"
|
|
339
|
+
;;
|
|
340
|
+
esac
|
|
341
|
+
done
|
|
342
|
+
|
|
343
|
+
if [ -n "$STILL_PRESENT" ]; then
|
|
344
|
+
echo "WARNING: label removal did not take effect for ($STILL_PRESENT) — retrying once..." >&2
|
|
345
|
+
gh issue edit "$ISSUE_NUMBER" "${GH_ARGS[@]}" --remove-label "$STILL_PRESENT" 2>/dev/null || true
|
|
346
|
+
|
|
347
|
+
POST_RETRY_LABELS=$(gh issue view "$ISSUE_NUMBER" "${GH_ARGS[@]}" --json labels \
|
|
348
|
+
--jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
349
|
+
|
|
350
|
+
STILL_PRESENT_AFTER_RETRY=""
|
|
351
|
+
IFS=',' read -ra RETRY_CHECK <<< "$STILL_PRESENT"
|
|
352
|
+
for candidate in "${RETRY_CHECK[@]}"; do
|
|
353
|
+
case ",$POST_RETRY_LABELS," in
|
|
354
|
+
*",$candidate,"*)
|
|
355
|
+
STILL_PRESENT_AFTER_RETRY="${STILL_PRESENT_AFTER_RETRY:+$STILL_PRESENT_AFTER_RETRY,}$candidate"
|
|
356
|
+
;;
|
|
357
|
+
esac
|
|
358
|
+
done
|
|
359
|
+
|
|
360
|
+
if [ -n "$STILL_PRESENT_AFTER_RETRY" ]; then
|
|
361
|
+
echo "ERROR: failed to remove stale label(s) ($STILL_PRESENT_AFTER_RETRY) from issue #$ISSUE_NUMBER after retry — label state machine now inconsistent (issue carries both '$EFFECTIVE_LABEL' and the stale label(s) above)." >&2
|
|
362
|
+
TRANSITION_EXIT_CODE=1
|
|
363
|
+
else
|
|
364
|
+
echo "Retry succeeded — stale label(s) removed."
|
|
365
|
+
fi
|
|
366
|
+
fi
|
|
367
|
+
else
|
|
368
|
+
echo "No stale workflow:* labels present on the issue — nothing to remove."
|
|
369
|
+
fi
|
|
370
|
+
|
|
165
371
|
# ---------------------------------------------------------------------------
|
|
166
|
-
|
|
167
|
-
|
|
372
|
+
# Clear needs-human (best-effort) — SCOPED to workflow:awaiting-merge only
|
|
373
|
+
#
|
|
374
|
+
# needs-human is NOT a workflow:* label, so it is never included in
|
|
375
|
+
# VALID_STATES/REMOVE_LABELS above. It is a write-only, sticky, terminal
|
|
376
|
+
# label: once a human flags an issue/PR as needing attention, no routine
|
|
377
|
+
# forward-progress transition (investigating, ready-to-build, building,
|
|
378
|
+
# in-review, merged, invalid, decomposed) should silently clear it.
|
|
379
|
+
#
|
|
380
|
+
# The ONE deliberate exception is workflow:awaiting-merge (forge#1809/#1810):
|
|
381
|
+
# a remediated + re-reviewed PR that has moved off needs-human without yet
|
|
382
|
+
# meeting the auto-land bar. That is the single code path allowed to clear
|
|
383
|
+
# needs-human — every other TARGET_STATE must leave a pre-existing
|
|
384
|
+
# needs-human label untouched.
|
|
385
|
+
#
|
|
386
|
+
# Best-effort: `|| true` so a missing label never fails the script under
|
|
387
|
+
# `set -euo pipefail`.
|
|
388
|
+
# ---------------------------------------------------------------------------
|
|
389
|
+
if [ "$TARGET_STATE" = "awaiting-merge" ]; then
|
|
390
|
+
echo "Clearing needs-human (best-effort, scoped to awaiting-merge)..."
|
|
391
|
+
gh issue edit "$ISSUE_NUMBER" "${GH_ARGS[@]}" --remove-label "needs-human" 2>/dev/null || true
|
|
392
|
+
else
|
|
393
|
+
echo "Skipping needs-human clear — TARGET_STATE is '$TARGET_STATE', not 'awaiting-merge'."
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
# ---------------------------------------------------------------------------
|
|
397
|
+
# Final exit (forge#1929) — honor any deferred failure from the persistent
|
|
398
|
+
# stale-label removal check above. The needs-human clear step just above
|
|
399
|
+
# always ran first, regardless of that failure, so the caller-visible exit
|
|
400
|
+
# code still surfaces the same "fail loud" signal introduced by forge#1915,
|
|
401
|
+
# just after the unrelated cleanup step has had a chance to run.
|
|
402
|
+
# ---------------------------------------------------------------------------
|
|
403
|
+
if [ "$TRANSITION_EXIT_CODE" -ne 0 ]; then
|
|
404
|
+
echo "FAILED: $EFFECTIVE_LABEL set on issue #$ISSUE_NUMBER, but persistent stale-label removal failure occurred (see ERROR above)." >&2
|
|
405
|
+
exit "$TRANSITION_EXIT_CODE"
|
|
406
|
+
fi
|
|
168
407
|
|
|
169
408
|
echo "OK: $EFFECTIVE_LABEL set on issue #$ISSUE_NUMBER"
|