@cxi-lmai/ci-agent-platform 3.0.1 → 3.1.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 +12 -4
- package/package.json +2 -2
- package/payload/INSTALL.md +8 -2
- package/payload/ci-templates/claude-pipeline.gitlab-ci.yml +210 -0
- package/payload/ci-templates/scripts/agent-architect.sh +233 -0
- package/payload/ci-templates/scripts/code.sh +26 -27
- package/payload/ci-templates/scripts/codebase-audit.sh +252 -0
- package/payload/ci-templates/scripts/coverage-ratchet.sh +77 -0
- package/payload/ci-templates/scripts/cve-fix.sh +246 -0
- package/payload/ci-templates/scripts/docs-sync.sh +225 -0
- package/payload/ci-templates/scripts/e2e-test-gen.sh +201 -0
- package/payload/ci-templates/scripts/lib/failure-notice.sh +104 -0
- package/payload/ci-templates/scripts/lib/issue-loop.sh +155 -40
- package/payload/ci-templates/scripts/lib/pipeline-common.sh +95 -21
- package/payload/ci-templates/scripts/lib/platform.sh +270 -13
- package/payload/ci-templates/scripts/lib/usage-capture-omp.sh +7 -1
- package/payload/ci-templates/scripts/lib/usage-capture.sh +7 -1
- package/payload/ci-templates/scripts/metrics-snapshot.sh +377 -0
- package/payload/ci-templates/scripts/orchestrate.sh +55 -37
- package/payload/ci-templates/scripts/postmortem.sh +10 -0
- package/payload/ci-templates/scripts/review-fix.sh +22 -11
- package/payload/ci-templates/scripts/review.sh +11 -7
- package/payload/ci-templates/scripts/test-fix.sh +7 -0
- package/payload/skills/agent-architect/SKILL.md +45 -0
- package/payload/skills/codebase-audit/SKILL.md +84 -0
- package/payload/skills/cve-fix/SKILL.md +98 -0
- package/payload/skills/docs-sync/SKILL.md +74 -0
- package/payload/skills/e2e-test-gen/SKILL.md +65 -0
- package/payload/skills/fix-review-findings/SKILL.md +2 -2
- package/payload/skills/fix-tests/SKILL.md +2 -2
- package/payload/templates/memory-index.template.md +34 -0
- package/payload/templates/pipeline-config.template.md +11 -1
- package/payload/templates/review_suppressions.template.md +55 -0
- package/payload/templates/spec-issue.template.md +39 -7
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
# Common (both platforms):
|
|
9
9
|
# PIPE_MR_IID MR iid (GitLab) or PR number (GitHub)
|
|
10
10
|
# PIPE_TARGET_BRANCH branch the MR/PR targets
|
|
11
|
-
# PIPE_SOURCE_BRANCH the MR/PR source branch
|
|
11
|
+
# PIPE_SOURCE_BRANCH the MR/PR source branch (optional: platform_mr_meta
|
|
12
|
+
# reports it as `source_branch` when the runtime does not)
|
|
12
13
|
# PIPE_HEAD_SHA current head commit under review
|
|
13
14
|
# PIPE_PROJECT_PATH namespace/project (for git push URL)
|
|
14
15
|
# PIPE_SERVER_HOST git host (for git push URL)
|
|
@@ -58,7 +59,10 @@ _gl_get() {
|
|
|
58
59
|
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/merge_requests/$PIPE_MR_IID$1"
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
# `source_branch` is the branch the MR/PR was opened from. It comes from the API
|
|
63
|
+
# rather than the CI runtime because branch and workflow_run pipelines carry no
|
|
64
|
+
# merge-request source ref, and a runner that pushes needs it.
|
|
65
|
+
_gl_mr_meta() { _gl_get "" | jq -r '{title: (.title // ""), description: (.description // ""), labels: ((.labels // []) | join(",")), source_branch: (.source_branch // "")}'; }
|
|
62
66
|
_gl_mr_labels() { _gl_get "" | jq -r '((.labels // []) | join(","))'; }
|
|
63
67
|
# Callers take the LAST matching note, so the array must be chronological AND
|
|
64
68
|
# must contain the newest notes. One asc page would return the oldest 100 on a
|
|
@@ -66,11 +70,12 @@ _gl_mr_labels() { _gl_get "" | jq -r '((.labels // []) | join(","))'; }
|
|
|
66
70
|
_gl_notes() { _gl_get "/notes?per_page=100&sort=desc" | jq -r '[.[] | {author: .author.username, body: .body}] | reverse'; }
|
|
67
71
|
_gl_issue() { curl -s -H "PRIVATE-TOKEN: $PIPE_TOKEN" "$PIPE_API_URL/projects/$PIPE_PROJECT_ID/issues/$1" | jq -r '{iid: .iid, title: (.title // ""), description: (.description // "")}'; }
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
_gl_post_comment_file() {
|
|
74
|
+
# $1 = body file under PIPE_CONTEXT_DIR.
|
|
70
75
|
curl --fail-with-body -s -X POST \
|
|
71
76
|
-H "PRIVATE-TOKEN: $PIPE_TOKEN" \
|
|
72
77
|
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/merge_requests/$PIPE_MR_IID/notes" \
|
|
73
|
-
--data-urlencode "body
|
|
78
|
+
--data-urlencode "body@$1" > /dev/null
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
_gl_set_labels() {
|
|
@@ -102,15 +107,22 @@ _gl_compute_diff_base() {
|
|
|
102
107
|
|
|
103
108
|
_gh_get() { gh api "repos/$PIPE_REPO/$1"; }
|
|
104
109
|
|
|
105
|
-
_gh_mr_meta() { _gh_get "pulls/$PIPE_MR_IID" | jq -r '{title: (.title // ""), description: (.body // ""), labels: ((.labels // []) | map(.name) | join(","))}'; }
|
|
110
|
+
_gh_mr_meta() { _gh_get "pulls/$PIPE_MR_IID" | jq -r '{title: (.title // ""), description: (.body // ""), labels: ((.labels // []) | map(.name) | join(",")), source_branch: (.head.ref // "")}'; }
|
|
106
111
|
_gh_mr_labels() { _gh_get "pulls/$PIPE_MR_IID" | jq -r '((.labels // []) | map(.name) | join(","))'; }
|
|
107
112
|
# The per-issue comments endpoint has no sort param, so paginate all pages to
|
|
108
113
|
# guarantee the newest comments are present (callers take the last match).
|
|
109
114
|
_gh_notes() { gh api --paginate "repos/$PIPE_REPO/issues/$PIPE_MR_IID/comments?per_page=100" --jq '.[] | {author: .user.login, body: .body}' | jq -s '.'; }
|
|
110
115
|
_gh_issue() { _gh_get "issues/$1" | jq -r '{iid: .number, title: (.title // ""), description: (.body // "")}'; }
|
|
111
116
|
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
_gh_post_comment_file() {
|
|
118
|
+
# $1 = body file under PIPE_CONTEXT_DIR.
|
|
119
|
+
local payload status=0
|
|
120
|
+
payload=$(mktemp "$PIPE_CONTEXT_DIR/github-comment.XXXXXX.json")
|
|
121
|
+
jq -n --rawfile body "$1" '{body:$body}' > "$payload"
|
|
122
|
+
gh api -X POST "repos/$PIPE_REPO/issues/$PIPE_MR_IID/comments" \
|
|
123
|
+
-H "Content-Type: application/json" --input "$payload" > /dev/null || status=$?
|
|
124
|
+
rm -f "$payload"
|
|
125
|
+
return "$status"
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
_gh_set_labels() {
|
|
@@ -135,6 +147,179 @@ _gh_compute_diff_base() {
|
|
|
135
147
|
fi
|
|
136
148
|
}
|
|
137
149
|
|
|
150
|
+
# --- Pipeline and artifact helpers ------------------------------------------
|
|
151
|
+
|
|
152
|
+
_gl_completed_pipelines() {
|
|
153
|
+
local updated_after
|
|
154
|
+
updated_after=$(date -u -d "$1 days ago" '+%Y-%m-%dT%H:%M:%SZ') || return 1
|
|
155
|
+
curl -s --fail -H "PRIVATE-TOKEN: $PIPE_TOKEN" \
|
|
156
|
+
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/pipelines?scope=finished&updated_after=$updated_after&per_page=100" |
|
|
157
|
+
jq -c '[.[] | {id, status: (.status // ""), ref: (.ref // ""), updated_at: (.updated_at // "")}]'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_gl_pipeline_artifacts() {
|
|
161
|
+
curl -s --fail -H "PRIVATE-TOKEN: $PIPE_TOKEN" \
|
|
162
|
+
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/pipelines/$1/jobs?per_page=100" |
|
|
163
|
+
jq -c '[.[] | {job_id: .id, job_name: (.name // ""), artifact_available: (((.artifacts_file // {}) | .filename) != null)}]'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_gl_download_artifact() {
|
|
167
|
+
curl -sSL --fail -H "PRIVATE-TOKEN: $PIPE_TOKEN" \
|
|
168
|
+
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/jobs/$1/artifacts" -o "$2"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
_gl_latest_success_coverage() {
|
|
172
|
+
curl -s --fail -H "PRIVATE-TOKEN: $PIPE_TOKEN" \
|
|
173
|
+
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/pipelines?ref=$1&status=success&per_page=1" |
|
|
174
|
+
jq -r '.[0].coverage // ""'
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
_gl_merge_requests_updated_since() {
|
|
178
|
+
local page response page_size results
|
|
179
|
+
page=1
|
|
180
|
+
results='[]'
|
|
181
|
+
while :; do
|
|
182
|
+
response=$(curl -s --fail -H "PRIVATE-TOKEN: $PIPE_TOKEN" \
|
|
183
|
+
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/merge_requests?updated_after=$1&per_page=100&page=$page&scope=all") || return 1
|
|
184
|
+
page_size=$(jq -r 'length' <<< "$response") || return 1
|
|
185
|
+
results=$(jq -c --argjson results "$results" \
|
|
186
|
+
'$results + [.[] | {iid, title: (.title // ""), state: (.state // ""), author: (.author.username // ""), labels: (.labels // []), created_at: (.created_at // ""), merged_at: .merged_at, target_branch: (.target_branch // ""), description: (.description // "")}]' <<< "$response") || return 1
|
|
187
|
+
[ "$page_size" -lt 100 ] && break
|
|
188
|
+
page=$((page + 1))
|
|
189
|
+
done
|
|
190
|
+
printf '%s\n' "$results"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Merged MRs targeting a branch, newest first, filtered to iid > since_iid.
|
|
194
|
+
# Used by the codebase-audit job to find work since the last audit.
|
|
195
|
+
_gl_merged_mrs() {
|
|
196
|
+
# $1=target_branch $2=since_iid $3=limit. One page, newest merged first;
|
|
197
|
+
# matches the source script's own non-paginated fetch. Captured into a
|
|
198
|
+
# variable (not piped straight into jq) so a curl failure propagates: a
|
|
199
|
+
# pipeline's exit status is jq's, not curl's, without `set -o pipefail`.
|
|
200
|
+
local response
|
|
201
|
+
response=$(curl -s --fail -H "PRIVATE-TOKEN: $PIPE_TOKEN" \
|
|
202
|
+
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/merge_requests?target_branch=$1&state=merged&per_page=$3&order_by=created_at&sort=desc") || return 1
|
|
203
|
+
jq -c --argjson since "$2" '[.[] | select(.iid > $since) | {iid, title: (.title // ""), created_at: (.created_at // "")}]' <<< "$response"
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
_gl_mr_changes() {
|
|
207
|
+
# $1=iid $2=limit. Changed file paths for one merged MR, capped at limit.
|
|
208
|
+
local response
|
|
209
|
+
response=$(curl -s --fail -H "PRIVATE-TOKEN: $PIPE_TOKEN" \
|
|
210
|
+
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/merge_requests/$1/changes?per_page=$2") || return 1
|
|
211
|
+
jq -r --argjson limit "$2" '[.changes[]?.new_path] | .[0:$limit][]' <<< "$response" 2>/dev/null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# $1=iid $2=limit $3=sort("asc"|"desc"). Normalized, non-system MR notes.
|
|
215
|
+
_gl_mr_notes() {
|
|
216
|
+
local response
|
|
217
|
+
[[ "$2" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
218
|
+
[[ "$3" = "asc" || "$3" = "desc" ]] || return 1
|
|
219
|
+
response=$(curl -s --fail -H "PRIVATE-TOKEN: $PIPE_TOKEN" \
|
|
220
|
+
"$PIPE_API_URL/projects/$PIPE_PROJECT_ID/merge_requests/$1/notes?per_page=$2&sort=$3") || return 1
|
|
221
|
+
jq --argjson limit "$2" '[.[] | select(.system != true)
|
|
222
|
+
| {author: (.author.username // ""), body: (.body // "")}] | .[0:$limit]' <<< "$response"
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# These APIs return downloadable binary responses as well as JSON. Use curl
|
|
226
|
+
# rather than gh so artifact downloads remain a silent, status-only operation.
|
|
227
|
+
_gh_pipeline_api() {
|
|
228
|
+
local token="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
|
|
229
|
+
curl -s --fail \
|
|
230
|
+
-H "Accept: application/vnd.github+json" \
|
|
231
|
+
-H "Authorization: Bearer $token" \
|
|
232
|
+
"${GITHUB_API_URL:-https://api.github.com}/repos/$PIPE_REPO/$1"
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_gh_completed_pipelines() {
|
|
236
|
+
local updated_after page response page_size results
|
|
237
|
+
updated_after=$(date -u -d "$1 days ago" '+%Y-%m-%dT%H:%M:%SZ') || return 1
|
|
238
|
+
page=1
|
|
239
|
+
results='[]'
|
|
240
|
+
while :; do
|
|
241
|
+
response=$(_gh_pipeline_api "actions/runs?status=completed&per_page=100&page=$page") || return 1
|
|
242
|
+
page_size=$(jq -r '(.workflow_runs // []) | length' <<< "$response") || return 1
|
|
243
|
+
results=$(jq -c --argjson results "$results" --arg updated_after "$updated_after" \
|
|
244
|
+
'$results + [.workflow_runs[] | select((.updated_at // "") >= $updated_after) | {id, status: (.conclusion // .status // ""), ref: (.head_branch // ""), updated_at: (.updated_at // "")}]' <<< "$response") || return 1
|
|
245
|
+
[ "$page_size" -lt 100 ] && break
|
|
246
|
+
page=$((page + 1))
|
|
247
|
+
done
|
|
248
|
+
printf '%s\n' "$results"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
_gh_pipeline_artifacts() {
|
|
252
|
+
_gh_pipeline_api "actions/runs/$1/artifacts?per_page=100" |
|
|
253
|
+
jq -c '[.artifacts[] | {job_id: .id, job_name: (.name // ""), artifact_available: ((.expired // true) | not)}]'
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
_gh_download_artifact() {
|
|
257
|
+
local token="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
|
|
258
|
+
curl -sSL --fail \
|
|
259
|
+
-H "Accept: application/vnd.github+json" \
|
|
260
|
+
-H "Authorization: Bearer $token" \
|
|
261
|
+
"${GITHUB_API_URL:-https://api.github.com}/repos/$PIPE_REPO/actions/artifacts/$1/zip" -o "$2"
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
_gh_latest_success_coverage() {
|
|
265
|
+
_gh_pipeline_api "actions/runs?branch=$1&status=success&per_page=1" |
|
|
266
|
+
jq -r '.workflow_runs[0].coverage // ""'
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# The pull-request API has no updated-since filter, so the newest-first listing
|
|
270
|
+
# is walked until a page carries nothing inside the window.
|
|
271
|
+
_gh_merge_requests_updated_since() {
|
|
272
|
+
local page response page_size kept kept_size results
|
|
273
|
+
page=1
|
|
274
|
+
results='[]'
|
|
275
|
+
while :; do
|
|
276
|
+
response=$(_gh_pipeline_api "pulls?state=all&sort=updated&direction=desc&per_page=100&page=$page") || return 1
|
|
277
|
+
page_size=$(jq -r 'length' <<< "$response") || return 1
|
|
278
|
+
kept=$(jq -c --arg since "$1" \
|
|
279
|
+
'[.[] | select((.updated_at // "") >= $since) | {iid: .number, title: (.title // ""), state: (if .merged_at then "merged" else (.state // "") end), author: (.user.login // ""), labels: [(.labels // [])[].name], created_at: (.created_at // ""), merged_at: .merged_at, target_branch: (.base.ref // ""), description: (.body // "")}]' <<< "$response") || return 1
|
|
280
|
+
kept_size=$(jq -r 'length' <<< "$kept") || return 1
|
|
281
|
+
results=$(jq -nc --argjson results "$results" --argjson kept "$kept" '$results + $kept') || return 1
|
|
282
|
+
[ "$kept_size" -lt "$page_size" ] && break
|
|
283
|
+
[ "$page_size" -lt 100 ] && break
|
|
284
|
+
page=$((page + 1))
|
|
285
|
+
done
|
|
286
|
+
printf '%s\n' "$results"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# Merged PRs targeting a branch, newest first, filtered to number > since_iid.
|
|
290
|
+
# GitHub's closed-PR listing has no server-side merged-only filter, so
|
|
291
|
+
# merged_at != null is applied client-side.
|
|
292
|
+
_gh_merged_mrs() {
|
|
293
|
+
# $1=target_branch $2=since_iid $3=limit.
|
|
294
|
+
local response
|
|
295
|
+
response=$(_gh_pipeline_api "pulls?base=$1&state=closed&per_page=$3&sort=created&direction=desc") || return 1
|
|
296
|
+
jq -c --argjson since "$2" '[.[] | select(.merged_at != null and .number > $since) | {iid: .number, title: (.title // ""), created_at: (.created_at // "")}]' <<< "$response"
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
_gh_mr_changes() {
|
|
300
|
+
# $1=iid $2=limit. Changed file paths for one merged PR, capped at limit.
|
|
301
|
+
local response
|
|
302
|
+
response=$(_gh_pipeline_api "pulls/$1/files?per_page=$2") || return 1
|
|
303
|
+
jq -r --argjson limit "$2" '[.[]?.filename] | .[0:$limit][]' <<< "$response" 2>/dev/null
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# $1=iid $2=limit $3=sort("asc"|"desc"). GitHub returns comments oldest
|
|
307
|
+
# first, so descend requests paginate before reversing and slicing to preserve
|
|
308
|
+
# real newest-first semantics rather than reversing an arbitrary first page.
|
|
309
|
+
_gh_mr_notes() {
|
|
310
|
+
local response filter
|
|
311
|
+
[[ "$2" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
312
|
+
[[ "$3" = "asc" || "$3" = "desc" ]] || return 1
|
|
313
|
+
if [ "$3" = "asc" ]; then
|
|
314
|
+
response=$(gh api "repos/$PIPE_REPO/issues/$1/comments?per_page=$2") || return 1
|
|
315
|
+
else
|
|
316
|
+
response=$(gh api --paginate "repos/$PIPE_REPO/issues/$1/comments?per_page=100") || return 1
|
|
317
|
+
fi
|
|
318
|
+
filter='[.[] | {author: (.user.login // ""), body: (.body // "")}]'
|
|
319
|
+
[ "$3" = "desc" ] && filter="$filter | reverse"
|
|
320
|
+
jq --argjson limit "$2" "$filter | .[0:\$limit]" <<< "$response"
|
|
321
|
+
}
|
|
322
|
+
|
|
138
323
|
# --- Bot identity -------------------------------------------------------------
|
|
139
324
|
# The scripts filter MR/PR comments by the bot's username to recognise their own
|
|
140
325
|
# previous output. The token already knows whose it is, so an empty
|
|
@@ -155,12 +340,84 @@ platform_resolve_bot_user() {
|
|
|
155
340
|
|
|
156
341
|
# --- Public dispatch --------------------------------------------------------
|
|
157
342
|
|
|
158
|
-
platform_mr_meta()
|
|
159
|
-
platform_mr_labels()
|
|
160
|
-
platform_notes()
|
|
161
|
-
platform_issue()
|
|
162
|
-
|
|
163
|
-
platform_set_labels()
|
|
343
|
+
platform_mr_meta() { case "$PIPE_PLATFORM" in gitlab) _gl_mr_meta ;; github) _gh_mr_meta ;; esac; }
|
|
344
|
+
platform_mr_labels() { case "$PIPE_PLATFORM" in gitlab) _gl_mr_labels ;; github) _gh_mr_labels ;; esac; }
|
|
345
|
+
platform_notes() { case "$PIPE_PLATFORM" in gitlab) _gl_notes ;; github) _gh_notes ;; esac; }
|
|
346
|
+
platform_issue() { case "$PIPE_PLATFORM" in gitlab) _gl_issue "$1" ;; github) _gh_issue "$1" ;; esac; }
|
|
347
|
+
platform_post_comment_file() { case "$PIPE_PLATFORM" in gitlab) _gl_post_comment_file "$1" ;; github) _gh_post_comment_file "$1" ;; esac; }
|
|
348
|
+
platform_set_labels() { case "$PIPE_PLATFORM" in gitlab) _gl_set_labels "$1" "$2" ;; github) _gh_set_labels "$1" "$2" ;; esac; }
|
|
349
|
+
|
|
350
|
+
platform_completed_pipelines() {
|
|
351
|
+
[ "$#" -eq 1 ] || return 1
|
|
352
|
+
case "$PIPE_PLATFORM" in
|
|
353
|
+
gitlab) _gl_completed_pipelines "$1" ;;
|
|
354
|
+
github) _gh_completed_pipelines "$1" ;;
|
|
355
|
+
*) return 1 ;;
|
|
356
|
+
esac
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
platform_pipeline_artifacts() {
|
|
360
|
+
[ "$#" -eq 1 ] || return 1
|
|
361
|
+
case "$PIPE_PLATFORM" in
|
|
362
|
+
gitlab) _gl_pipeline_artifacts "$1" ;;
|
|
363
|
+
github) _gh_pipeline_artifacts "$1" ;;
|
|
364
|
+
*) return 1 ;;
|
|
365
|
+
esac
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
platform_download_artifact() {
|
|
369
|
+
[ "$#" -eq 2 ] || return 1
|
|
370
|
+
case "$PIPE_PLATFORM" in
|
|
371
|
+
gitlab) _gl_download_artifact "$1" "$2" ;;
|
|
372
|
+
github) _gh_download_artifact "$1" "$2" ;;
|
|
373
|
+
*) return 1 ;;
|
|
374
|
+
esac
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
platform_latest_success_coverage() {
|
|
378
|
+
[ "$#" -eq 1 ] || return 1
|
|
379
|
+
case "$PIPE_PLATFORM" in
|
|
380
|
+
gitlab) _gl_latest_success_coverage "$1" ;;
|
|
381
|
+
github) _gh_latest_success_coverage "$1" ;;
|
|
382
|
+
*) return 1 ;;
|
|
383
|
+
esac
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
platform_merge_requests_updated_since() {
|
|
387
|
+
[ "$#" -eq 1 ] || return 1
|
|
388
|
+
case "$PIPE_PLATFORM" in
|
|
389
|
+
gitlab) _gl_merge_requests_updated_since "$1" ;;
|
|
390
|
+
github) _gh_merge_requests_updated_since "$1" ;;
|
|
391
|
+
*) return 1 ;;
|
|
392
|
+
esac
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
platform_merged_mrs() {
|
|
396
|
+
[ "$#" -eq 3 ] || return 1
|
|
397
|
+
case "$PIPE_PLATFORM" in
|
|
398
|
+
gitlab) _gl_merged_mrs "$1" "$2" "$3" ;;
|
|
399
|
+
github) _gh_merged_mrs "$1" "$2" "$3" ;;
|
|
400
|
+
*) return 1 ;;
|
|
401
|
+
esac
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
platform_mr_changes() {
|
|
405
|
+
[ "$#" -eq 2 ] || return 1
|
|
406
|
+
case "$PIPE_PLATFORM" in
|
|
407
|
+
gitlab) _gl_mr_changes "$1" "$2" ;;
|
|
408
|
+
github) _gh_mr_changes "$1" "$2" ;;
|
|
409
|
+
*) return 1 ;;
|
|
410
|
+
esac
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
platform_mr_notes() {
|
|
414
|
+
[ "$#" -eq 3 ] || return 1
|
|
415
|
+
case "$PIPE_PLATFORM" in
|
|
416
|
+
gitlab) _gl_mr_notes "$1" "$2" "$3" ;;
|
|
417
|
+
github) _gh_mr_notes "$1" "$2" "$3" ;;
|
|
418
|
+
*) return 1 ;;
|
|
419
|
+
esac
|
|
420
|
+
}
|
|
164
421
|
|
|
165
422
|
# Sets PIPE_DIFF_BASE and PIPE_IS_INCREMENTAL. Call before building the diff.
|
|
166
423
|
platform_compute_diff_base() {
|
|
@@ -110,7 +110,13 @@ capture_omp() {
|
|
|
110
110
|
omp_exit_code: $exit_code
|
|
111
111
|
}' > "$record_file"
|
|
112
112
|
|
|
113
|
-
# If
|
|
113
|
+
# If the agent failed, forward stderr so debugging still works, and keep a
|
|
114
|
+
# durable copy: the failure detector runs after the invocation returns and
|
|
115
|
+
# cannot read a temp file the wrapper already deleted.
|
|
116
|
+
if [ -n "${PIPE_AGENT_STDERR:-}" ]; then
|
|
117
|
+
mkdir -p "$(dirname "$PIPE_AGENT_STDERR")" 2>/dev/null || true
|
|
118
|
+
cp "$stderr_file" "$PIPE_AGENT_STDERR" 2>/dev/null || true
|
|
119
|
+
fi
|
|
114
120
|
[ "$exit_code" -ne 0 ] && cat "$stderr_file" >&2
|
|
115
121
|
rm -f "$stream_file" "$stderr_file"
|
|
116
122
|
return "$exit_code"
|
|
@@ -103,7 +103,13 @@ capture_claude() {
|
|
|
103
103
|
claude_exit_code: $exit_code
|
|
104
104
|
}' > "$record_file"
|
|
105
105
|
|
|
106
|
-
# If
|
|
106
|
+
# If the agent failed, forward stderr so debugging still works, and keep a
|
|
107
|
+
# durable copy: the failure detector runs after the invocation returns and
|
|
108
|
+
# cannot read a temp file the wrapper already deleted.
|
|
109
|
+
if [ -n "${PIPE_AGENT_STDERR:-}" ]; then
|
|
110
|
+
mkdir -p "$(dirname "$PIPE_AGENT_STDERR")" 2>/dev/null || true
|
|
111
|
+
cp "$stderr_file" "$PIPE_AGENT_STDERR" 2>/dev/null || true
|
|
112
|
+
fi
|
|
107
113
|
[ "$exit_code" -ne 0 ] && cat "$stderr_file" >&2
|
|
108
114
|
rm -f "$stream_file" "$stderr_file"
|
|
109
115
|
return "$exit_code"
|