-
Notifications
You must be signed in to change notification settings - Fork 2.5k
311 lines (299 loc) · 16 KB
/
Copy pathbugfix-version-index.yml
File metadata and controls
311 lines (299 loc) · 16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
name: Bugfix Version Index
run-name: Bugfix Version Index - ${{ github.event_name }} ${{ github.event.number || github.ref_name || github.event.schedule || 'run' }}
# Maintains the deterministic code_kb.versions index (fix side) + the heuristic
# code_kb.task_summaries cause cache, via the github-bugfix-versions plugin run directly with
# `claude -p` on the [ubuntu, ai] runner. The runner checks out this repo, so the plugin's
# version_backfill.py has a real clone for `git tag --contains` (no REST-only path, no container
# mount). Events:
#
# ① origin bugfix PR merges to main → :index-fix (seed versions) + :affected-versions (cache cause)
# ② a backport PR merges to branch-X.Y* → :index-fix (rebuild the origin's versions record)
# ③ a release tag X.Y.Z is pushed → :index-fix <tag> (resolve pending-<line> → concrete tag)
# ④ daily schedule → :index-fix reconcile (pending-only, versions/fix side)
# + :affected-versions reconcile (cause cache self-heal)
# ⑤ weekly schedule → :index-fix reconcile-all (re-check concrete too — corrects
# a wrong concrete tag / reverse error; heavier)
# ⑥ manual dispatch (migrate-enrichment) → :affected-versions migrate-enrichment (one-off: backfill
# legacy cause records up to the rich schema — severity /
# cause author+title / ## Summary + ## User Impact body;
# dispatch-only, separate from the daily correctness sweep)
#
# NOTE: plugin commands MUST be namespaced (/github-bugfix-versions:<cmd>). A bare /index-fix in
# headless `-p` does NOT resolve — it is silently treated as prompt text (no write).
#
# The index steps FAIL LOUD: no `|| true`, and `defaults.run.shell: bash` forces `-o pipefail`
# so a failing `claude ... | tee` propagates claude's non-zero exit (tee's success does not mask it).
# The cause step (affected-versions) stays best-effort; the daily `affected-versions reconcile` job
# (④) re-selects any origin still cause-missing/partial/stale, so a transient origin-merge cause
# failure self-heals on a later run (bounded + per-record backoff — see the skill's Reconcile mode).
#
# PREREQUISITES on the [ubuntu, ai] runner (one-time, infra):
# - `gh` (authenticated via GITHUB_TOKEN), `git`, `python3` on PATH — version_backfill.py uses gh
# for PR/backport data and `git tag --contains` against the checkout to resolve concrete tags;
# - `claude` with the `github-bugfix-versions` plugin installed and the `context-base` MCP
# configured with `code_kb` USAGE (read+write) for the runner's identity;
# - `code_kb.versions` (collection_type=knowledge) and `code_kb.task_summaries`
# (collection_type=task_summary) collections pre-created by an admin.
on:
pull_request_target:
branches:
- main
# release lines. Use [0-9]+ (one-or-more), NOT bare [0-9] (single digit) — else multi-digit
# versions like branch-3.12, branch-3.5.20, branch-3.12.20 silently never match the filter.
- 'branch-[0-9]+.[0-9]+'
- 'branch-[0-9]+.[0-9]+.[0-9]+'
types:
- closed
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
schedule:
- cron: '0 19 * * *' # daily 19:00 UTC (03:00 Asia/Shanghai) → reconcile (pending-only)
- cron: '0 20 * * 0' # weekly Sun 20:00 UTC (Mon 04:00 Asia/Shanghai) → reconcile-all (concrete too)
workflow_dispatch: # manual trigger — run a self-heal sweep on demand, don't only wait for cron
inputs:
mode:
description: 'Sweep to run'
type: choice
required: true
default: reconcile
options:
- reconcile # = daily: index-fix pending sweep + affected-versions cause self-heal
- reconcile-all # = weekly: re-check concrete records too (heavier)
- migrate-enrichment # = one-off: backfill legacy cause records up to the rich schema
budget:
description: 'migrate-enrichment only: per-run analysis cap (integer, e.g. 20; blank = low default). Clamped to 50 in the skill.'
type: string
required: false
default: ''
# Concurrency is per-ORIGIN for the single-origin EVENTS (origin-merge / backport-merge share
# `BVI-…-origin-N`, so different origins run in parallel but the same origin serializes — a
# whole-entity upsert can't be lost). The BULK jobs (tag-cut / reconcile / reconcile-all) use their
# OWN group, so they are NOT serialized against a same-origin backport-merge. That race (a bulk run
# overwriting a backport just written by an event) is handled in the plugin, NOT here: the monotonic
# guard keeps a concrete earliest_fix_tag from reverting to pending, and the anti-clobber guard drops
# any origin whose stored record was updated after the sweep started (so a mid-sweep backport-merge
# write is never clobbered). A shared group is deliberately NOT used — it would make different-origin
# backport-writes cancel each other (only one pending kept), dropping writes.
permissions:
contents: read
pull-requests: read
defaults:
run:
shell: bash # GitHub runs this as `bash --noprofile --norc -eo pipefail {0}` → pipefail makes
# `claude ... | tee` fail the step when claude fails (tee's exit no longer masks it).
jobs:
# ① origin bugfix PR merged to main → seed versions (fix side) + cache cause (task_summaries).
origin-merge:
if: >
github.event_name == 'pull_request_target' &&
github.event.action == 'closed' &&
github.event.pull_request.merged == true &&
github.base_ref == 'main' &&
contains(github.event.pull_request.title, '[BugFix]') &&
!startsWith(github.head_ref, 'mergify/') &&
!contains(github.head_ref, '-sync-pr-')
concurrency:
group: BVI-${{ github.repository }}-origin-${{ github.event.number }}
cancel-in-progress: false
runs-on: [ubuntu, ai]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: fetch release branches
run: git fetch origin '+refs/heads/branch-*:refs/remotes/origin/branch-*' || true
- name: index-fix (deterministic fix side → code_kb.versions) [FAIL-LOUD]
run: |
echo "origin-merge PR #${{ github.event.number }} → ${PR_URL}"
claude --dangerously-skip-permissions \
-p "/github-bugfix-versions:index-fix ${PR_URL}" 2>&1 | tee "${RUNNER_TEMP}/index_fix.txt"
- name: affected-versions (heuristic cause → code_kb.task_summaries) [best-effort]
if: always()
continue-on-error: true
run: |
claude --dangerously-skip-permissions \
-p "/github-bugfix-versions:affected-versions ${PR_URL}" 2>&1 | tee "${RUNNER_TEMP}/affected_versions.txt" || true
# ② a backport PR merged to a release branch → rebuild EACH origin it backports.
backport-resolve:
if: >
github.event_name == 'pull_request_target' &&
github.event.action == 'closed' &&
github.event.pull_request.merged == true &&
startsWith(github.base_ref, 'branch-') &&
contains(github.event.pull_request.title, 'backport') &&
!contains(github.head_ref, '-sync-pr-')
runs-on: [ubuntu, ai]
outputs:
origins: ${{ steps.parse.outputs.origins }}
steps:
- name: parse all (backport #N) markers → origin list
id: parse
env:
TITLE: ${{ github.event.pull_request.title }}
run: |
json="$(printf '%s' "$TITLE" | grep -oiE 'backport #[0-9]+' | grep -oE '[0-9]+' | sort -un \
| awk 'BEGIN{printf "["} {printf "%s\"%s\"",(NR>1?",":""),$0} END{printf "]"}')"
echo "origins=$json" >> "$GITHUB_OUTPUT"
echo "backport PR #${{ github.event.number }} (${{ github.base_ref }}) → origins: $json"
backport-write:
needs: backport-resolve
if: ${{ needs.backport-resolve.outputs.origins != '[]' && needs.backport-resolve.outputs.origins != '' }}
strategy:
fail-fast: false
matrix:
origin: ${{ fromJSON(needs.backport-resolve.outputs.origins) }}
concurrency:
group: BVI-${{ github.repository }}-origin-${{ matrix.origin }}
cancel-in-progress: false
runs-on: [ubuntu, ai]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: fetch release branches
run: git fetch origin '+refs/heads/branch-*:refs/remotes/origin/branch-*' || true
- name: index-fix (rebuild origin ${{ matrix.origin }} from its merged backports) [FAIL-LOUD]
run: |
echo "rebuild origin ${{ github.repository }}#${{ matrix.origin }} (triggered by backport PR #${{ github.event.number }})"
claude --dangerously-skip-permissions \
-p "/github-bugfix-versions:index-fix ${{ github.repository }}#${{ matrix.origin }}" 2>&1 | tee "${RUNNER_TEMP}/index_fix_${{ matrix.origin }}.txt"
# ③ release tag cut → resolve pending-<line> records on that line to the concrete tag.
tag-cut:
if: github.event_name == 'push'
concurrency:
group: BVI-${{ github.repository }}-tagcut-${{ github.ref_name }}
cancel-in-progress: false
runs-on: [ubuntu, ai]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
TAG: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: fetch release branches
run: git fetch origin '+refs/heads/branch-*:refs/remotes/origin/branch-*' || true
- name: index-fix (tag-cut → concrete tag for this line) [FAIL-LOUD]
run: |
echo "tag-cut ${TAG}"
claude --dangerously-skip-permissions \
-p "/github-bugfix-versions:index-fix ${TAG}" 2>&1 | tee "${RUNNER_TEMP}/index_fix.txt"
# ④ daily self-heal → pending-only sweep across all maintained lines.
reconcile:
if: >
(github.event_name == 'schedule' && github.event.schedule == '0 19 * * *') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'reconcile')
concurrency:
group: BVI-${{ github.repository }}-reconcile
cancel-in-progress: false
runs-on: [ubuntu, ai]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: fetch release branches
run: git fetch origin '+refs/heads/branch-*:refs/remotes/origin/branch-*' || true
- name: index-fix reconcile (re-sweep pending on all maintained lines) [FAIL-LOUD]
run: |
echo "daily reconcile (versions / fix side)"
claude --dangerously-skip-permissions \
-p "/github-bugfix-versions:index-fix reconcile" 2>&1 | tee "${RUNNER_TEMP}/reconcile.txt"
- name: affected-versions reconcile (cause cache self-heal) [best-effort]
if: always()
continue-on-error: true
run: |
# Cause-side self-heal: fill/refresh task_summaries for in-scope bugfixes whose cause is
# missing/partial/stale (e.g. an origin-merge cause step that failed best-effort, or a
# historical gap). Bounded per run (N=30d priority / MAX_FULL_ANALYSES=12 default hard cap /
# R=3 reserved backlog / MAX_CONCURRENCY=4), with per-record backoff so a poison record can't
# starve the quota — see the skill. Rich-schema backfill of legacy records is a SEPARATE mode
# (migrate-enrichment, job ⑥), not this daily correctness sweep.
# Best-effort + `|| true`: cause is heuristic; a transient blip must not fail the FAIL-LOUD
# versions reconcile above, and tomorrow's run re-selects anything left cause-incomplete.
echo "daily reconcile (cause side)"
claude --dangerously-skip-permissions \
-p "/github-bugfix-versions:affected-versions reconcile" 2>&1 | tee "${RUNNER_TEMP}/affected_reconcile.txt" || true
# ⑤ weekly deep self-heal → re-check concrete records too, correcting a wrong concrete tag
# (reverse error like #74855: stored 4.1.3 but really 4.1.2). Heavier — every maintained-line
# origin is rebuilt from GitHub — so weekly, off-peak, not on the daily cron.
reconcile-all:
if: >
(github.event_name == 'schedule' && github.event.schedule == '0 20 * * 0') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'reconcile-all')
concurrency:
group: BVI-${{ github.repository }}-reconcile-all
cancel-in-progress: false
runs-on: [ubuntu, ai]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: fetch release branches
run: git fetch origin '+refs/heads/branch-*:refs/remotes/origin/branch-*' || true
- name: index-fix reconcile-all (re-check concrete + pending on all maintained lines) [FAIL-LOUD]
run: |
echo "weekly reconcile-all"
claude --dangerously-skip-permissions \
-p "/github-bugfix-versions:index-fix reconcile-all" 2>&1 | tee "${RUNNER_TEMP}/reconcile_all.txt"
# ⑥ manual one-off → backfill legacy cause records up to the rich schema (severity / cause author+title /
# ## Summary + ## User Impact body). SEPARATE from the daily correctness reconcile (④) so it never
# competes for that budget; dispatch-only (no cron). Bounded in the skill: MAX_FULL_ANALYSES default 3,
# raised via the `budget` input and clamped to 50. Best-effort (cause is heuristic).
migrate-enrichment:
if: >
github.event_name == 'workflow_dispatch' &&
github.event.inputs.mode == 'migrate-enrichment'
concurrency:
group: BVI-${{ github.repository }}-migrate-enrichment
cancel-in-progress: false
runs-on: [ubuntu, ai]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
RUN_BUDGET: ${{ github.event.inputs.budget }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: fetch release branches
run: git fetch origin '+refs/heads/branch-*:refs/remotes/origin/branch-*' || true
- name: validate budget input [FAIL-LOUD]
# A free-form dispatch input flows into a `--dangerously-skip-permissions` `claude -p` prompt as
# $ARGUMENTS. It is not a shell-injection vector (double-quoted env expansion), but an arbitrary
# string would be PROMPT injection, and a plain typo would be silently masked to a green no-op by
# the best-effort `|| true` below. So validate FIRST, in its own step WITHOUT continue-on-error:
# empty (= low default) is fine; otherwise it MUST be digits only. A bad value fails the job (red),
# and the best-effort step below is then skipped.
run: |
if [ -n "${RUN_BUDGET}" ] && ! [[ "${RUN_BUDGET}" =~ ^[0-9]+$ ]]; then
echo "::error::budget must be empty or a non-negative integer (got '${RUN_BUDGET}'); the skill clamps it to 50"
exit 1
fi
- name: affected-versions migrate-enrichment (rich-schema backfill) [best-effort]
continue-on-error: true
run: |
echo "migrate-enrichment (budget='${RUN_BUDGET}')"
claude --dangerously-skip-permissions \
-p "/github-bugfix-versions:affected-versions migrate-enrichment ${RUN_BUDGET}" 2>&1 \
| tee "${RUNNER_TEMP}/migrate_enrichment.txt" || true