@cxi-lmai/ci-agent-platform 3.0.1 → 3.1.1
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/agents/agent-architect.md +1 -1
- package/payload/agents/code-reviewer.md +1 -1
- package/payload/agents/codebase-auditor.md +1 -1
- package/payload/agents/coder.md +1 -1
- package/payload/agents/decomposer.md +1 -1
- package/payload/agents/docs-sync.md +1 -1
- package/payload/agents/e2e-test-writer.md +1 -1
- package/payload/agents/performance-reviewer.md +1 -1
- package/payload/agents/release-mr.md +1 -1
- package/payload/agents/security-reviewer.md +1 -1
- package/payload/agents/test-fix.md +1 -1
- package/payload/agents/test-writer.md +1 -1
- package/payload/agents-omp/agent-architect.md +1 -1
- package/payload/agents-omp/code-reviewer.md +1 -1
- package/payload/agents-omp/codebase-auditor.md +1 -1
- package/payload/agents-omp/coder.md +1 -1
- package/payload/agents-omp/decomposer.md +1 -1
- package/payload/agents-omp/docs-sync.md +1 -1
- package/payload/agents-omp/e2e-test-writer.md +1 -1
- package/payload/agents-omp/migration-reviewer.md +1 -1
- package/payload/agents-omp/orchestrator.md +1 -1
- package/payload/agents-omp/performance-reviewer.md +1 -1
- package/payload/agents-omp/postmortem.md +1 -1
- package/payload/agents-omp/release-mr.md +1 -1
- package/payload/agents-omp/security-reviewer.md +1 -1
- package/payload/agents-omp/test-fix.md +1 -1
- package/payload/agents-omp/test-writer.md +1 -1
- package/payload/ci-templates/claude-pipeline.gitlab-ci.yml +210 -0
- package/payload/ci-templates/github/claude-issue-pipeline.yml +1 -1
- package/payload/ci-templates/github/claude-pipeline.yml +2 -2
- package/payload/ci-templates/github/claude-test-fix.yml +1 -1
- 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 +100 -26
- 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
|
@@ -74,7 +74,7 @@ env:
|
|
|
74
74
|
PIPE_GIT_NAME: ${{ vars.PIPE_GIT_NAME || 'Pipeline Bot' }}
|
|
75
75
|
PIPE_GIT_EMAIL: ${{ vars.PIPE_GIT_EMAIL || 'bot@pipeline.ci' }}
|
|
76
76
|
PIPE_MODEL_TRIAGE: ${{ vars.PIPE_MODEL_TRIAGE || 'haiku' }}
|
|
77
|
-
PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'claude-sonnet-5
|
|
77
|
+
PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'claude-sonnet-5' }}
|
|
78
78
|
PIPE_AGENT_ENV_ALLOWLIST: ${{ vars.PIPE_AGENT_ENV_ALLOWLIST || '' }}
|
|
79
79
|
# Runtime mapping onto the PIPE_ names the scripts read.
|
|
80
80
|
PIPE_REPO: ${{ github.repository }}
|
|
@@ -44,8 +44,8 @@ env:
|
|
|
44
44
|
PIPE_COMMIT_REVIEWFIX: ${{ vars.PIPE_COMMIT_REVIEWFIX || 'Fix review findings' }}
|
|
45
45
|
PIPE_GIT_NAME: ${{ vars.PIPE_GIT_NAME || 'Pipeline Bot' }}
|
|
46
46
|
PIPE_GIT_EMAIL: ${{ vars.PIPE_GIT_EMAIL || 'bot@pipeline.ci' }}
|
|
47
|
-
PIPE_MODEL_REVIEW: ${{ vars.PIPE_MODEL_REVIEW || 'claude-sonnet-5
|
|
48
|
-
PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'claude-sonnet-5
|
|
47
|
+
PIPE_MODEL_REVIEW: ${{ vars.PIPE_MODEL_REVIEW || 'claude-sonnet-5' }}
|
|
48
|
+
PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'claude-sonnet-5' }}
|
|
49
49
|
PIPE_AGENT_ENV_ALLOWLIST: ${{ vars.PIPE_AGENT_ENV_ALLOWLIST || '' }}
|
|
50
50
|
# Runtime mapping onto the PIPE_ names the scripts read.
|
|
51
51
|
PIPE_REPO: ${{ github.repository }}
|
|
@@ -49,7 +49,7 @@ env:
|
|
|
49
49
|
PIPE_COMMIT_COVERAGE: ${{ vars.PIPE_COMMIT_COVERAGE || 'Add coverage tests' }}
|
|
50
50
|
PIPE_GIT_NAME: ${{ vars.PIPE_GIT_NAME || 'Pipeline Bot' }}
|
|
51
51
|
PIPE_GIT_EMAIL: ${{ vars.PIPE_GIT_EMAIL || 'bot@pipeline.ci' }}
|
|
52
|
-
PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'claude-sonnet-5
|
|
52
|
+
PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'claude-sonnet-5' }}
|
|
53
53
|
PIPE_AGENT_ENV_ALLOWLIST: ${{ vars.PIPE_AGENT_ENV_ALLOWLIST || '' }}
|
|
54
54
|
PIPE_TEST_ARTIFACT: ${{ vars.PIPE_TEST_ARTIFACT || 'test-reports' }}
|
|
55
55
|
PIPE_REPO: ${{ github.repository }}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Runner for the opt-in weekly agent-architect job. It collects bounded,
|
|
3
|
+
# forge-neutral evidence; the /agent-architect skill performs the analysis.
|
|
4
|
+
set -e
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
# shellcheck source=lib/pipeline-common.sh
|
|
7
|
+
source "$SCRIPT_DIR/lib/pipeline-common.sh"
|
|
8
|
+
# shellcheck source=lib/issue-loop.sh
|
|
9
|
+
source "$SCRIPT_DIR/lib/issue-loop.sh"
|
|
10
|
+
pipe_defaults
|
|
11
|
+
|
|
12
|
+
ARCHITECT_ENV="$PIPE_CONTEXT_DIR/architect.env"
|
|
13
|
+
CONTEXT_FILE="$PIPE_CONTEXT_DIR/architect-context.md"
|
|
14
|
+
REPORT_FILE="$PIPE_CONTEXT_DIR/architect-report.md"
|
|
15
|
+
PROPOSALS_FILE="$PIPE_CONTEXT_DIR/architect-proposals.json"
|
|
16
|
+
if [ "${DRY_RUN+x}" = x ]; then
|
|
17
|
+
DRY_RUN="$DRY_RUN"
|
|
18
|
+
else
|
|
19
|
+
DRY_RUN="${PIPE_ARCHITECT_DRY_RUN:-true}"
|
|
20
|
+
fi
|
|
21
|
+
WINDOW_DAYS="${PIPE_ARCHITECT_WINDOW_DAYS:-7}"
|
|
22
|
+
|
|
23
|
+
write_marker() {
|
|
24
|
+
printf 'ARCHITECT_STATUS=%s\nARCHITECT_PROPOSALS=%s\nARCHITECT_ISSUES_FILED=%s\n' \
|
|
25
|
+
"$1" "$2" "$3" > "$ARCHITECT_ENV"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if [ "${PIPE_AGENT_ARCHITECT:-0}" != "1" ]; then
|
|
29
|
+
write_marker disabled 0 0
|
|
30
|
+
pipe_log "Agent architect is disabled"
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
if ! [[ "$WINDOW_DAYS" =~ ^[1-9][0-9]*$ ]]; then
|
|
35
|
+
write_marker invalid_window 0 0
|
|
36
|
+
pipe_log "ERROR: PIPE_ARCHITECT_WINDOW_DAYS must be a positive integer"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if date --version >/dev/null 2>&1; then
|
|
41
|
+
SINCE=$(date -u -d "$WINDOW_DAYS days ago" '+%Y-%m-%dT%H:%M:%SZ')
|
|
42
|
+
else
|
|
43
|
+
SINCE=$(date -u -v-"$WINDOW_DAYS"d '+%Y-%m-%dT%H:%M:%SZ')
|
|
44
|
+
fi
|
|
45
|
+
WEEK_TAG=$(date -u +%G-W%V)
|
|
46
|
+
|
|
47
|
+
# The updated-since helper has the window semantics the source job needs. It
|
|
48
|
+
# returns both states, so retain only merged MRs/PRs targeting this project’s
|
|
49
|
+
# configured integration branch and cap the prompt input before any per-item IO.
|
|
50
|
+
RECENT_MRS=$(platform_merge_requests_updated_since "$SINCE") || {
|
|
51
|
+
write_marker fetch_failed 0 0
|
|
52
|
+
pipe_log "ERROR: failed to fetch recently updated MRs/PRs"
|
|
53
|
+
exit 1
|
|
54
|
+
}
|
|
55
|
+
MRS=$(jq -c --arg target "$PIPE_TARGET_BRANCH" \
|
|
56
|
+
'[.[] | select(.state == "merged" and .target_branch == $target)] | .[0:30]' <<< "$RECENT_MRS") || {
|
|
57
|
+
write_marker fetch_failed 0 0
|
|
58
|
+
pipe_log "ERROR: unexpected merged MR/PR response"
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
MR_COUNT=$(jq -r 'length' <<< "$MRS")
|
|
62
|
+
if ! [[ "$MR_COUNT" =~ ^[0-9]+$ ]]; then
|
|
63
|
+
write_marker fetch_failed 0 0
|
|
64
|
+
pipe_log "ERROR: could not count merged MRs/PRs"
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
if [ "$MR_COUNT" = 0 ]; then
|
|
68
|
+
write_marker no_evidence 0 0
|
|
69
|
+
pipe_log "No merged MRs/PRs in the last $WINDOW_DAYS days"
|
|
70
|
+
exit 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Stuck issues are current actionable context. Stuck MRs/PRs are selected from
|
|
74
|
+
# the same bounded recent window, so neither evidence source can expand prompt
|
|
75
|
+
# size without a hard limit.
|
|
76
|
+
STUCK_ISSUES=$(issues_by_labels "$PIPE_LABEL_STUCK" "" opened 20) || {
|
|
77
|
+
pipe_log "WARNING: failed to fetch open $PIPE_LABEL_STUCK issues; continuing without them"
|
|
78
|
+
STUCK_ISSUES='[]'
|
|
79
|
+
}
|
|
80
|
+
STUCK_MRS=$(jq -c --arg label "$PIPE_LABEL_STUCK" \
|
|
81
|
+
'[.[] | select((.labels // []) | index($label))] | .[0:20]' <<< "$RECENT_MRS") || STUCK_MRS='[]'
|
|
82
|
+
OPEN_IMPROVEMENTS=$(issues_by_label_created "$PIPE_LABEL_IMPROVEMENT" opened 10) || {
|
|
83
|
+
pipe_log "WARNING: failed to fetch open $PIPE_LABEL_IMPROVEMENT issues; continuing without dedup context"
|
|
84
|
+
OPEN_IMPROVEMENTS='[]'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Resolve the configured bot identity once so automatic comments do not become
|
|
88
|
+
# evidence. A failed lookup merely leaves the filter empty rather than failing a
|
|
89
|
+
# read-only scheduled analysis.
|
|
90
|
+
platform_resolve_bot_user || true
|
|
91
|
+
|
|
92
|
+
MR_EVIDENCE=""
|
|
93
|
+
while IFS= read -r IID; do
|
|
94
|
+
TITLE=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .title' <<< "$MRS")
|
|
95
|
+
AUTHOR=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .author' <<< "$MRS")
|
|
96
|
+
LABELS=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | (.labels // []) | join(",")' <<< "$MRS")
|
|
97
|
+
FILES=$(platform_mr_changes "$IID" 15 2>/dev/null | sed 's/^/ /' || true)
|
|
98
|
+
NOTES=$(platform_mr_notes "$IID" 20 asc 2>/dev/null \
|
|
99
|
+
| jq -r --arg bot "${PIPE_BOT_USER:-}" \
|
|
100
|
+
'[.[] | select($bot == "" or .author != $bot)
|
|
101
|
+
| " [" + .author + "]: " + ((.body | gsub("[\\r\\n]+"; " "))[0:200])]
|
|
102
|
+
| .[0:20] | join("\n")' 2>/dev/null || true)
|
|
103
|
+
MR_EVIDENCE="${MR_EVIDENCE}
|
|
104
|
+
### MR/PR !${IID}: ${TITLE}
|
|
105
|
+
Author: ${AUTHOR} | Labels: ${LABELS}
|
|
106
|
+
Changed files:
|
|
107
|
+
${FILES:- (none)}
|
|
108
|
+
Human review comments:
|
|
109
|
+
${NOTES:- (none)}
|
|
110
|
+
"
|
|
111
|
+
done < <(jq -r '.[].iid' <<< "$MRS")
|
|
112
|
+
|
|
113
|
+
STUCK_EVIDENCE=""
|
|
114
|
+
while IFS= read -r IID; do
|
|
115
|
+
TITLE=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .title' <<< "$STUCK_ISSUES")
|
|
116
|
+
DESCRIPTION=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .description // "(none)"' <<< "$STUCK_ISSUES" | head -c 400)
|
|
117
|
+
STUCK_EVIDENCE="${STUCK_EVIDENCE}
|
|
118
|
+
### Stuck issue #${IID}: ${TITLE}
|
|
119
|
+
${DESCRIPTION}
|
|
120
|
+
"
|
|
121
|
+
done < <(jq -r '.[].iid' <<< "$STUCK_ISSUES")
|
|
122
|
+
while IFS= read -r IID; do
|
|
123
|
+
TITLE=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .title' <<< "$STUCK_MRS")
|
|
124
|
+
NOTES=$(platform_mr_notes "$IID" 15 asc 2>/dev/null \
|
|
125
|
+
| jq -r --arg bot "${PIPE_BOT_USER:-}" \
|
|
126
|
+
'[.[] | select($bot == "" or .author != $bot)
|
|
127
|
+
| " [" + .author + "]: " + ((.body | gsub("[\\r\\n]+"; " "))[0:200])]
|
|
128
|
+
| .[0:15] | join("\n")' 2>/dev/null || true)
|
|
129
|
+
STUCK_EVIDENCE="${STUCK_EVIDENCE}
|
|
130
|
+
### Stuck MR/PR !${IID}: ${TITLE}
|
|
131
|
+
Human comments:
|
|
132
|
+
${NOTES:- (none)}
|
|
133
|
+
"
|
|
134
|
+
done < <(jq -r '.[].iid' <<< "$STUCK_MRS")
|
|
135
|
+
|
|
136
|
+
OPEN_CONTEXT=$(jq -r '.[] | "Issue #\(.iid) (\(.created_at[0:10])): \(.title)\n\(.description[0:500])\n---"' <<< "$OPEN_IMPROVEMENTS" 2>/dev/null | head -c 6000 || true)
|
|
137
|
+
rm -f "$ARCHITECT_ENV" "$REPORT_FILE" "$PROPOSALS_FILE"
|
|
138
|
+
{
|
|
139
|
+
echo "# Agent architect context"
|
|
140
|
+
echo
|
|
141
|
+
echo "Time window: last $WINDOW_DAYS days (since $SINCE)"
|
|
142
|
+
echo "Week: $WEEK_TAG"
|
|
143
|
+
echo "Ready label: $PIPE_LABEL_READY"
|
|
144
|
+
echo "Improvement label: $PIPE_LABEL_IMPROVEMENT"
|
|
145
|
+
echo
|
|
146
|
+
echo "## Merged MRs/PRs"
|
|
147
|
+
printf '%s\n' "$MR_EVIDENCE"
|
|
148
|
+
echo "## Stuck items"
|
|
149
|
+
printf '%s\n' "${STUCK_EVIDENCE:-(none)}"
|
|
150
|
+
if [ -n "$OPEN_CONTEXT" ]; then
|
|
151
|
+
echo
|
|
152
|
+
echo "## Already-open improvement issues — do not re-propose these"
|
|
153
|
+
printf '%s\n' "$OPEN_CONTEXT"
|
|
154
|
+
fi
|
|
155
|
+
} > "$CONTEXT_FILE"
|
|
156
|
+
|
|
157
|
+
REPORT=$(pipe_run_agent agent-architect "/agent-architect" "Agent,Read,Glob,Grep" "$PIPE_MODEL_REVIEW" < /dev/null)
|
|
158
|
+
printf '%s\n' "$REPORT" > "$REPORT_FILE"
|
|
159
|
+
if [ ! -s "$REPORT_FILE" ]; then
|
|
160
|
+
write_marker no_output 0 0
|
|
161
|
+
pipe_failure_log_notice_files "the weekly agent-architect analysis" \
|
|
162
|
+
"review the recent changes and stuck items by hand" \
|
|
163
|
+
"$PIPE_AGENT_STDERR" "$REPORT_FILE" | while IFS= read -r line; do pipe_log "$line"; done
|
|
164
|
+
exit 0
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# Retain at most five complete delimiter blocks. The JSON file keeps body text
|
|
168
|
+
# out of shell words and is also the sole source for issue creation below.
|
|
169
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
170
|
+
write_marker parser_failed 0 0
|
|
171
|
+
pipe_log "WARNING: python3 is unavailable; cannot parse architect proposals"
|
|
172
|
+
exit 0
|
|
173
|
+
fi
|
|
174
|
+
if ! python3 - "$REPORT_FILE" "$PROPOSALS_FILE" <<'PY'
|
|
175
|
+
import json
|
|
176
|
+
import re
|
|
177
|
+
import sys
|
|
178
|
+
|
|
179
|
+
report_path, proposals_path = sys.argv[1:]
|
|
180
|
+
text = open(report_path, encoding="utf-8").read()
|
|
181
|
+
proposals = []
|
|
182
|
+
for block in re.findall(r"<!-- ISSUE-START -->(.*?)<!-- ISSUE-END -->", text, re.S)[:5]:
|
|
183
|
+
title = next((line[7:].strip() for line in block.splitlines() if line.startswith("Title: ")), "")
|
|
184
|
+
if not title or len(title) > 72:
|
|
185
|
+
continue
|
|
186
|
+
proposals.append({"title": title, "body": block.strip()[:8000]})
|
|
187
|
+
with open(proposals_path, "w", encoding="utf-8") as output:
|
|
188
|
+
json.dump(proposals, output)
|
|
189
|
+
PY
|
|
190
|
+
then
|
|
191
|
+
rm -f "$PROPOSALS_FILE"
|
|
192
|
+
write_marker parser_failed 0 0
|
|
193
|
+
pipe_log "WARNING: could not parse architect proposals"
|
|
194
|
+
exit 0
|
|
195
|
+
fi
|
|
196
|
+
if ! jq -e 'type == "array"' "$PROPOSALS_FILE" >/dev/null 2>&1; then
|
|
197
|
+
rm -f "$PROPOSALS_FILE"
|
|
198
|
+
write_marker parser_failed 0 0
|
|
199
|
+
pipe_log "WARNING: architect proposal parser produced invalid output"
|
|
200
|
+
exit 0
|
|
201
|
+
fi
|
|
202
|
+
PROPOSAL_COUNT=$(jq -r 'length' "$PROPOSALS_FILE")
|
|
203
|
+
if [ "$PROPOSAL_COUNT" = 0 ]; then
|
|
204
|
+
write_marker no_proposals 0 0
|
|
205
|
+
pipe_log "No concrete architect proposals"
|
|
206
|
+
exit 0
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
case "$DRY_RUN" in
|
|
210
|
+
false|0) ;;
|
|
211
|
+
*)
|
|
212
|
+
write_marker ok "$PROPOSAL_COUNT" 0
|
|
213
|
+
pipe_log "DRY RUN: $PROPOSAL_COUNT proposal(s), no issues filed"
|
|
214
|
+
cat "$REPORT_FILE"
|
|
215
|
+
exit 0
|
|
216
|
+
;;
|
|
217
|
+
esac
|
|
218
|
+
|
|
219
|
+
issue_ensure_label "$PIPE_LABEL_IMPROVEMENT" "#6F42C1" "Agent improvement proposals"
|
|
220
|
+
ISSUES_FILE="$PIPE_CONTEXT_DIR/architect-issue.md"
|
|
221
|
+
ISSUES_FILED=0
|
|
222
|
+
while IFS= read -r proposal; do
|
|
223
|
+
TITLE=$(jq -r '.title' <<< "$proposal")
|
|
224
|
+
jq -r '.body' <<< "$proposal" > "$ISSUES_FILE"
|
|
225
|
+
ISSUE_IID=$(issue_create_file "$TITLE" "$ISSUES_FILE" "$PIPE_LABEL_READY,$PIPE_LABEL_IMPROVEMENT")
|
|
226
|
+
if [ -n "$ISSUE_IID" ]; then
|
|
227
|
+
ISSUES_FILED=$((ISSUES_FILED + 1))
|
|
228
|
+
pipe_log "Filed architect proposal as #$ISSUE_IID"
|
|
229
|
+
else
|
|
230
|
+
pipe_log "WARNING: could not file architect proposal: $TITLE"
|
|
231
|
+
fi
|
|
232
|
+
done < <(jq -c '.[]' "$PROPOSALS_FILE")
|
|
233
|
+
write_marker ok "$PROPOSAL_COUNT" "$ISSUES_FILED"
|
|
@@ -61,24 +61,21 @@ COMMITS=$(git rev-list "origin/$PIPE_TARGET_BRANCH..HEAD" --count 2>/dev/null ||
|
|
|
61
61
|
# issue_implemented is emitted only when commits exist (E2E finding N-7:
|
|
62
62
|
# emitting it before this check produced the metric for stuck runs too).
|
|
63
63
|
if [ "${COMMITS:-0}" = "0" ]; then
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
$
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
${PIPE_JOB_URL:+CI job: $PIPE_JOB_URL}"
|
|
81
|
-
issue_set_labels "$CODER_ISSUE" "$PIPE_LABEL_STUCK" "$PIPE_LABEL_READY,$PIPE_LABEL_WIP"
|
|
64
|
+
NOTICE_FILE="$PIPE_CONTEXT_DIR/implementation-failure.md"
|
|
65
|
+
{
|
|
66
|
+
printf '🤖 '
|
|
67
|
+
pipe_failure_notice_files "implementation of issue #$CODER_ISSUE" "implement this issue by hand" \
|
|
68
|
+
"$PIPE_AGENT_STDERR" "$PIPE_CONTEXT_DIR/implement.env"
|
|
69
|
+
} > "$NOTICE_FILE"
|
|
70
|
+
issue_comment_file "$CODER_ISSUE" "$NOTICE_FILE"
|
|
71
|
+
if pipe_is_credit_exhaustion_files "$PIPE_AGENT_STDERR" "$PIPE_CONTEXT_DIR/implement.env"; then
|
|
72
|
+
pipe_log "Coder credit exhausted; #$CODER_ISSUE remains $PIPE_LABEL_READY"
|
|
73
|
+
else
|
|
74
|
+
pipe_log "Coder made no commits, marking #$CODER_ISSUE stuck"
|
|
75
|
+
pipe_metric_event coder issue_stuck \
|
|
76
|
+
"$(jq -n --argjson i "$CODER_ISSUE" '{issue_iid: $i, reason: "no_commits"}')"
|
|
77
|
+
issue_set_labels "$CODER_ISSUE" "$PIPE_LABEL_STUCK" "$PIPE_LABEL_READY,$PIPE_LABEL_WIP"
|
|
78
|
+
fi
|
|
82
79
|
exit 0
|
|
83
80
|
fi
|
|
84
81
|
|
|
@@ -89,25 +86,27 @@ pipe_metric_event coder issue_implemented \
|
|
|
89
86
|
git push -u origin "$CODER_BRANCH"
|
|
90
87
|
|
|
91
88
|
# --- 8. Open the MR/PR ------------------------------------------------------
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
MR_DESC_FILE="$PIPE_CONTEXT_DIR/mr-description.md"
|
|
90
|
+
{
|
|
91
|
+
printf 'Closes #%s\n\n' "$CODER_ISSUE"
|
|
92
|
+
if [ -s "$PIPE_CONTEXT_DIR/mr-summary.md" ]; then
|
|
93
|
+
cat "$PIPE_CONTEXT_DIR/mr-summary.md"
|
|
94
|
+
else
|
|
95
|
+
printf 'Implemented by the ci-agent-platform coder agent.'
|
|
96
|
+
fi
|
|
97
|
+
printf '\n\n---\n🤖 Implemented by the ci-agent-platform coder\n'
|
|
98
|
+
} > "$MR_DESC_FILE"
|
|
94
99
|
|
|
95
100
|
# Inherit the issue labels onto the MR/PR: drop the ready label, add wip.
|
|
96
101
|
MR_LABELS=$( { echo "$ISSUE_LABELS" | tr ',' '\n' | grep -vxF "$PIPE_LABEL_READY" | grep -v '^$'; echo "$PIPE_LABEL_WIP"; } \
|
|
97
102
|
| awk '!seen[$0]++' | paste -sd, - )
|
|
98
103
|
|
|
99
104
|
MR_TITLE="Resolve \"$ISSUE_TITLE\""
|
|
100
|
-
MR_DESC="Closes #$CODER_ISSUE
|
|
101
|
-
|
|
102
|
-
$SUMMARY
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
🤖 Implemented by the ci-agent-platform coder"
|
|
106
105
|
|
|
107
106
|
# Ensure the MR/PR labels exist (repo/project may not have them yet).
|
|
108
107
|
issue_ensure_label "$PIPE_LABEL_WIP" "#3498DB" "Pipeline is working on this change"
|
|
109
108
|
|
|
110
|
-
MR_URL=$(
|
|
109
|
+
MR_URL=$(mr_create_file "$CODER_BRANCH" "$PIPE_TARGET_BRANCH" "$MR_TITLE" "$MR_DESC_FILE" "$MR_LABELS")
|
|
111
110
|
if [ -n "$MR_URL" ]; then
|
|
112
111
|
pipe_log "Opened MR/PR: $MR_URL"
|
|
113
112
|
else
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Runner for the opt-in `codebase-audit` job (periodic convention-drift scan).
|
|
3
|
+
# Fetches merged MRs/PRs since the last audit, runs the `/codebase-audit` skill
|
|
4
|
+
# in `audit` mode (delegates to the codebase-auditor agent), files the findings
|
|
5
|
+
# as an issue, and — when a push-capable identity is configured and the report
|
|
6
|
+
# suggests documentation updates — runs the skill again in `apply-docs` mode
|
|
7
|
+
# (delegates to the coder agent), then pushes the branch and opens the MR/PR.
|
|
8
|
+
# All reasoning is in the skill; this script does token, API and git work.
|
|
9
|
+
#
|
|
10
|
+
# Generalized from a single-project original: the last-audited-MR lookup, the
|
|
11
|
+
# MR/PR summary, and the doc-suggestion extraction moved into the skill/runner
|
|
12
|
+
# split, platform calls go through lib/platform.sh and lib/issue-loop.sh, and
|
|
13
|
+
# the codebase-auditor/coder agents run via pipe_run_agent.
|
|
14
|
+
set -e
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
# shellcheck source=lib/pipeline-common.sh
|
|
17
|
+
source "$SCRIPT_DIR/lib/pipeline-common.sh"
|
|
18
|
+
# shellcheck source=lib/issue-loop.sh
|
|
19
|
+
source "$SCRIPT_DIR/lib/issue-loop.sh"
|
|
20
|
+
pipe_defaults
|
|
21
|
+
|
|
22
|
+
CONTEXT_FILE="$PIPE_CONTEXT_DIR/audit-context.md"
|
|
23
|
+
REPORT_FILE="$PIPE_CONTEXT_DIR/audit-report.md"
|
|
24
|
+
SUGGESTIONS_FILE="$PIPE_CONTEXT_DIR/audit-doc-suggestions.md"
|
|
25
|
+
AUDIT_ENV="$PIPE_CONTEXT_DIR/audit.env"
|
|
26
|
+
|
|
27
|
+
# --- 1. Last-audited MR/PR IID, parsed from the most recent audit issue -----
|
|
28
|
+
# The most recent codebase-audit-labeled issue regardless of state: a closed
|
|
29
|
+
# one still marks how far the previous audit got. A genuine fetch failure
|
|
30
|
+
# must not be mistaken for "no prior audit issue" (which legitimately yields
|
|
31
|
+
# an empty array and keeps LAST_AUDITED_IID at 0): capture the helper's own
|
|
32
|
+
# exit status explicitly instead of piping it straight into jq.
|
|
33
|
+
LAST_AUDITED_IID=0
|
|
34
|
+
LAST_AUDIT_ISSUES=$(issues_by_label_created "$PIPE_LABEL_AUDIT" all 1) || {
|
|
35
|
+
pipe_log "ERROR: failed to fetch the most recent $PIPE_LABEL_AUDIT-labeled issue"
|
|
36
|
+
exit 1
|
|
37
|
+
}
|
|
38
|
+
LAST_AUDIT_DESC=$(jq -r '.[0].description // ""' <<< "$LAST_AUDIT_ISSUES" 2>/dev/null || echo "")
|
|
39
|
+
if [ -n "$LAST_AUDIT_DESC" ]; then
|
|
40
|
+
PARSED_IID=$(printf '%s' "$LAST_AUDIT_DESC" | grep -oE 'audited-up-to-mr-iid: [0-9]+' | grep -oE '[0-9]+$' || echo "")
|
|
41
|
+
[ -n "$PARSED_IID" ] && LAST_AUDITED_IID="$PARSED_IID"
|
|
42
|
+
fi
|
|
43
|
+
pipe_log "Last audited MR/PR IID: $LAST_AUDITED_IID"
|
|
44
|
+
|
|
45
|
+
# --- 2. Merged MRs/PRs since then, with a changed-file summary --------------
|
|
46
|
+
MRS=$(platform_merged_mrs "$PIPE_TARGET_BRANCH" "$LAST_AUDITED_IID" 50) || {
|
|
47
|
+
pipe_log "ERROR: failed to fetch merged MRs/PRs"
|
|
48
|
+
exit 1
|
|
49
|
+
}
|
|
50
|
+
MR_COUNT=$(echo "$MRS" | jq 'length' 2>/dev/null || echo "")
|
|
51
|
+
if ! [[ "$MR_COUNT" =~ ^[0-9]+$ ]]; then
|
|
52
|
+
pipe_log "ERROR: unexpected API response while counting merged MRs/PRs"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
pipe_log "Analyzing $MR_COUNT recently merged MR/PR(s) (since IID $LAST_AUDITED_IID)"
|
|
56
|
+
[ "$MR_COUNT" = "0" ] && { pipe_log "No new MRs/PRs since last audit — skipping"; exit 0; }
|
|
57
|
+
MAX_AUDITED_IID=$(echo "$MRS" | jq '[.[].iid] | max // 0' 2>/dev/null || echo "")
|
|
58
|
+
if ! [[ "$MAX_AUDITED_IID" =~ ^[0-9]+$ ]]; then
|
|
59
|
+
pipe_log "ERROR: could not determine the max merged MR/PR IID from the API response"
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
MR_SUMMARY=""
|
|
64
|
+
while IFS= read -r IID; do
|
|
65
|
+
TITLE=$(echo "$MRS" | jq -r --argjson i "$IID" '.[] | select(.iid == $i) | .title')
|
|
66
|
+
# A single MR's changed-file fetch failing is an acceptable degradation (an
|
|
67
|
+
# empty file list in its summary line), not grounds to abort the whole audit.
|
|
68
|
+
FILES=$(platform_mr_changes "$IID" 10 | paste -sd, -) || true
|
|
69
|
+
MR_SUMMARY="${MR_SUMMARY}MR/PR !${IID} ${TITLE} -- Files ${FILES}; "
|
|
70
|
+
done < <(echo "$MRS" | jq -r '.[].iid')
|
|
71
|
+
|
|
72
|
+
# --- 3. Open audit issues, as dedup context for the auditor ------------------
|
|
73
|
+
OPEN_AUDIT_CONTEXT=""
|
|
74
|
+
# Dedup context only tells the auditor which findings are already filed; a
|
|
75
|
+
# transient fetch failure here must not abort the whole scheduled audit
|
|
76
|
+
# (unlike step 1's watermark read, which is load-bearing). Degrade to an
|
|
77
|
+
# empty result and continue, analogous to a single MR's changes-fetch
|
|
78
|
+
# degradation in step 2.
|
|
79
|
+
OPEN_AUDITS=$(issues_by_label_created "$PIPE_LABEL_AUDIT" opened 5) || {
|
|
80
|
+
pipe_log "WARNING: failed to fetch open $PIPE_LABEL_AUDIT-labeled issues for dedup context — continuing without it"
|
|
81
|
+
OPEN_AUDITS="[]"
|
|
82
|
+
}
|
|
83
|
+
OPEN_COUNT=$(echo "$OPEN_AUDITS" | jq 'length' 2>/dev/null || echo 0)
|
|
84
|
+
if [ "$OPEN_COUNT" != "0" ] && [ "$OPEN_COUNT" != "null" ]; then
|
|
85
|
+
OPEN_AUDIT_CONTEXT=$(echo "$OPEN_AUDITS" | jq -r '.[] | "Issue #\(.iid) (\(.created_at[0:10])): \(.title)\n\(.description[0:800])\n---"' 2>/dev/null | head -c 8000 || echo "")
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# --- 4. Run the /codebase-audit skill in audit mode --------------------------
|
|
89
|
+
# Write the context file the skill and the codebase-auditor agent read.
|
|
90
|
+
# Unbounded text stays out of argv/env; this content is already bounded above.
|
|
91
|
+
rm -f "$AUDIT_ENV" "$REPORT_FILE" "$SUGGESTIONS_FILE"
|
|
92
|
+
{
|
|
93
|
+
echo "# Codebase audit context"
|
|
94
|
+
echo
|
|
95
|
+
echo "Mode: audit"
|
|
96
|
+
echo
|
|
97
|
+
echo "## Recently merged MRs/PRs"
|
|
98
|
+
echo
|
|
99
|
+
printf '%s\n' "$MR_SUMMARY"
|
|
100
|
+
if [ -n "$OPEN_AUDIT_CONTEXT" ]; then
|
|
101
|
+
echo
|
|
102
|
+
echo "## Already-open ${PIPE_LABEL_AUDIT}-labeled issues (skip findings already covered here)"
|
|
103
|
+
echo
|
|
104
|
+
printf '%s\n' "$OPEN_AUDIT_CONTEXT"
|
|
105
|
+
fi
|
|
106
|
+
} > "$CONTEXT_FILE"
|
|
107
|
+
|
|
108
|
+
pipe_run_agent codebase-audit "/codebase-audit" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_TRIAGE" < /dev/null
|
|
109
|
+
|
|
110
|
+
AUDIT_STATUS=$(pipe_get_env "$AUDIT_ENV" AUDIT_STATUS)
|
|
111
|
+
AUDIT_FINDINGS_TOTAL=$(pipe_get_env "$AUDIT_ENV" AUDIT_FINDINGS_TOTAL); AUDIT_FINDINGS_TOTAL=${AUDIT_FINDINGS_TOTAL:-0}
|
|
112
|
+
# pipe_get_env does no shell expansion: a marker that copies the sample line
|
|
113
|
+
# literally (e.g. `AUDIT_REPORT_PATH=$PIPE_CONTEXT_DIR/audit-report.md`
|
|
114
|
+
# unexpanded) or otherwise points at a path that doesn't exist must not
|
|
115
|
+
# discard a perfectly good report at the runner's own default path (review
|
|
116
|
+
# round 2 finding).
|
|
117
|
+
AUDIT_REPORT_PATH=$(pipe_get_env "$AUDIT_ENV" AUDIT_REPORT_PATH)
|
|
118
|
+
if [ ! -s "$AUDIT_REPORT_PATH" ] && [ -s "$REPORT_FILE" ]; then
|
|
119
|
+
AUDIT_REPORT_PATH="$REPORT_FILE"
|
|
120
|
+
fi
|
|
121
|
+
AUDIT_REPORT_PATH=${AUDIT_REPORT_PATH:-$REPORT_FILE}
|
|
122
|
+
|
|
123
|
+
# --- 5. Empty output, or a missing/empty report file: log-only notice -------
|
|
124
|
+
# (no issue exists yet to post to). A model-authored marker claiming ok/
|
|
125
|
+
# no_findings with no actual report on disk is a reachable contract failure,
|
|
126
|
+
# not just a literal no_output status; guard the file the same way
|
|
127
|
+
# docs-sync.sh guards its own agent output before touching it.
|
|
128
|
+
if [ -z "$AUDIT_STATUS" ] || [ "$AUDIT_STATUS" = "no_output" ] || [ ! -s "$AUDIT_REPORT_PATH" ]; then
|
|
129
|
+
pipe_failure_log_notice_files "the periodic codebase audit" \
|
|
130
|
+
"audit recent MRs/PRs by hand for convention violations, or re-run this job after topping up" \
|
|
131
|
+
"$PIPE_AGENT_STDERR" "$AUDIT_REPORT_PATH" | while IFS= read -r line; do pipe_log "$line"; done
|
|
132
|
+
exit 0
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# --- 6. No findings above threshold: nothing to file --------------------------
|
|
136
|
+
if [ "$AUDIT_STATUS" = "no_findings" ]; then
|
|
137
|
+
pipe_log "No findings above threshold in either section — skipping issue creation"
|
|
138
|
+
exit 0
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# --- 7. File the findings as an issue -----------------------------------------
|
|
142
|
+
issue_ensure_label "$PIPE_LABEL_AUDIT" "#6F42C1" "Periodic convention-drift audit findings"
|
|
143
|
+
ISSUE_BODY_FILE="$PIPE_CONTEXT_DIR/audit-issue-body.md"
|
|
144
|
+
{
|
|
145
|
+
printf '<!-- audited-up-to-mr-iid: %s -->\n' "$MAX_AUDITED_IID"
|
|
146
|
+
head -c 20000 "$AUDIT_REPORT_PATH"
|
|
147
|
+
} > "$ISSUE_BODY_FILE"
|
|
148
|
+
ISSUE_IID=$(issue_create_file "Codebase Audit — $(date +%Y-%m-%d)" "$ISSUE_BODY_FILE" "$PIPE_LABEL_AUDIT")
|
|
149
|
+
if [ -z "$ISSUE_IID" ]; then
|
|
150
|
+
pipe_log "ERROR: failed to create the audit findings issue"
|
|
151
|
+
exit 0
|
|
152
|
+
fi
|
|
153
|
+
pipe_log "Filed audit findings as #$ISSUE_IID"
|
|
154
|
+
# The runner is the one that knows the IID: append it now so the marker
|
|
155
|
+
# artifact is complete when the job finishes (see the skill's own contract).
|
|
156
|
+
printf 'AUDIT_ISSUE_IID=%s\n' "$ISSUE_IID" >> "$AUDIT_ENV"
|
|
157
|
+
|
|
158
|
+
pipe_metric_event codebase-audit audit_filed \
|
|
159
|
+
"$(jq -n --argjson i "$ISSUE_IID" --argjson f "$AUDIT_FINDINGS_TOTAL" '{issue_iid: $i, findings_total: $f}')"
|
|
160
|
+
|
|
161
|
+
# --- 8. No push-capable identity: audit-only run, stop here -----------------
|
|
162
|
+
PUSH_TOKEN=""
|
|
163
|
+
case "$PIPE_PLATFORM" in
|
|
164
|
+
gitlab) PUSH_TOKEN="${PIPE_TOKEN:-}" ;;
|
|
165
|
+
github) PUSH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-}}" ;;
|
|
166
|
+
esac
|
|
167
|
+
if [ -z "$PUSH_TOKEN" ]; then
|
|
168
|
+
pipe_log "No push-capable identity configured — skipping auto-fix phase"
|
|
169
|
+
exit 0
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# --- 9. No doc-update suggestions: nothing for the coder to apply -----------
|
|
173
|
+
if [ ! -s "$SUGGESTIONS_FILE" ]; then
|
|
174
|
+
pipe_log "No documentation update suggestions — skipping auto-fix phase"
|
|
175
|
+
exit 0
|
|
176
|
+
fi
|
|
177
|
+
pipe_log "Doc suggestions found — spawning the coder agent"
|
|
178
|
+
|
|
179
|
+
# --- 10. Branch, then run the skill again in apply-docs mode -----------------
|
|
180
|
+
pipe_git_identity
|
|
181
|
+
git fetch origin "$PIPE_TARGET_BRANCH"
|
|
182
|
+
BRANCH="audit-$(date +%Y-%m-%d)"
|
|
183
|
+
# A same-day retry (transient failure, manual re-trigger) can find this
|
|
184
|
+
# branch already on the remote from an earlier attempt. Fetch it so the
|
|
185
|
+
# --force-with-lease push below has a known expected state instead of
|
|
186
|
+
# refusing for lack of a remote-tracking ref.
|
|
187
|
+
git fetch origin "$BRANCH" 2>/dev/null || true
|
|
188
|
+
git checkout -B "$BRANCH" "origin/$PIPE_TARGET_BRANCH"
|
|
189
|
+
|
|
190
|
+
APPLY_ENV="$PIPE_CONTEXT_DIR/audit-apply.env"
|
|
191
|
+
rm -f "$APPLY_ENV"
|
|
192
|
+
{
|
|
193
|
+
echo "# Codebase audit apply-docs context"
|
|
194
|
+
echo
|
|
195
|
+
echo "Mode: apply-docs"
|
|
196
|
+
echo
|
|
197
|
+
echo "Issue: #$ISSUE_IID"
|
|
198
|
+
echo "Documentation roots: $PIPE_DOCS_ROOTS"
|
|
199
|
+
echo "Commit subject: $PIPE_COMMIT_AUDIT"
|
|
200
|
+
echo "Doc suggestions file: $SUGGESTIONS_FILE"
|
|
201
|
+
echo "Full audit report: $AUDIT_REPORT_PATH"
|
|
202
|
+
} > "$CONTEXT_FILE"
|
|
203
|
+
|
|
204
|
+
pipe_run_agent codebase-audit-apply "/codebase-audit" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_CODE" < /dev/null
|
|
205
|
+
|
|
206
|
+
# --- 11. Authoritative commit count (the runner owns the push decision) ------
|
|
207
|
+
AHEAD=$(git rev-list "origin/$PIPE_TARGET_BRANCH..HEAD" --count 2>/dev/null || echo 0)
|
|
208
|
+
if [ "${AHEAD:-0}" = "0" ]; then
|
|
209
|
+
pipe_log "Coder made no commits — skipping MR/PR"
|
|
210
|
+
# "No commits" reads as "nothing needed fixing", but a credit outage produces
|
|
211
|
+
# the same silence. Issue #$ISSUE_IID already holds the findings, so on a
|
|
212
|
+
# credit outage tell its readers the apply step never ran rather than
|
|
213
|
+
# leaving them to assume it found nothing.
|
|
214
|
+
pipe_failure_log_notice_files "the audit apply step" \
|
|
215
|
+
"apply the findings in this issue by hand" \
|
|
216
|
+
"$PIPE_AGENT_STDERR" "$APPLY_ENV" | while IFS= read -r line; do pipe_log "$line"; done
|
|
217
|
+
if pipe_is_credit_exhaustion_files "$PIPE_AGENT_STDERR" "$APPLY_ENV"; then
|
|
218
|
+
NOTICE_FILE="$PIPE_CONTEXT_DIR/audit-apply-credit-notice.md"
|
|
219
|
+
pipe_failure_notice_files "application of these audit findings" \
|
|
220
|
+
"apply the findings above by hand, or re-run the audit job after topping up" \
|
|
221
|
+
"$PIPE_AGENT_STDERR" "$APPLY_ENV" > "$NOTICE_FILE"
|
|
222
|
+
issue_comment_file "$ISSUE_IID" "$NOTICE_FILE"
|
|
223
|
+
pipe_log "Posted credit-exhaustion notice to issue #$ISSUE_IID"
|
|
224
|
+
fi
|
|
225
|
+
git checkout "$PIPE_TARGET_BRANCH" 2>/dev/null || true
|
|
226
|
+
git branch -D "$BRANCH" 2>/dev/null || true
|
|
227
|
+
exit 0
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
pipe_metric_event codebase-audit audit_apply_committed \
|
|
231
|
+
"$(jq -n --argjson i "$ISSUE_IID" --argjson c "$AHEAD" '{issue_iid: $i, commits: $c}')"
|
|
232
|
+
|
|
233
|
+
# --- 12. Push and open the MR/PR ---------------------------------------------
|
|
234
|
+
# --force-with-lease: this is a dedicated bot branch reset fresh from the
|
|
235
|
+
# target branch every run, so a same-day retry intentionally replaces
|
|
236
|
+
# whatever an earlier attempt left on the remote. --force-with-lease (rather
|
|
237
|
+
# than a bare --force) still refuses if the branch changed unexpectedly
|
|
238
|
+
# between the fetch above and now.
|
|
239
|
+
git push --force-with-lease -u origin "$BRANCH"
|
|
240
|
+
|
|
241
|
+
MR_DESC_FILE="$PIPE_CONTEXT_DIR/audit-mr-description.md"
|
|
242
|
+
{
|
|
243
|
+
printf 'Automated documentation updates from the periodic convention audit.\n\nCloses #%s\n\n---\n\n' "$ISSUE_IID"
|
|
244
|
+
head -c 10000 "$AUDIT_REPORT_PATH"
|
|
245
|
+
} > "$MR_DESC_FILE"
|
|
246
|
+
|
|
247
|
+
MR_URL=$(mr_create_file "$BRANCH" "$PIPE_TARGET_BRANCH" "docs: codebase audit updates ($(date +%Y-%m-%d))" "$MR_DESC_FILE" "$PIPE_LABEL_AUDIT")
|
|
248
|
+
if [ -n "$MR_URL" ]; then
|
|
249
|
+
pipe_log "Opened MR/PR: $MR_URL"
|
|
250
|
+
else
|
|
251
|
+
pipe_log "WARNING: MR/PR creation returned no URL (it may already exist for this branch)"
|
|
252
|
+
fi
|