@cxi-lmai/ci-agent-platform 3.0.0 → 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 +27 -5
- package/package.json +2 -2
- package/payload/INSTALL.md +14 -5
- 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 +3 -3
- 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 +4 -4
- package/payload/agents/test-writer.md +3 -3
- package/payload/agents-omp/agent-architect.md +101 -0
- package/payload/agents-omp/code-reviewer.md +86 -0
- package/payload/agents-omp/codebase-auditor.md +73 -0
- package/payload/agents-omp/coder.md +57 -0
- package/payload/agents-omp/decomposer.md +70 -0
- package/payload/agents-omp/docs-sync.md +114 -0
- package/payload/agents-omp/e2e-test-writer.md +47 -0
- package/payload/agents-omp/migration-reviewer.md +99 -0
- package/payload/agents-omp/orchestrator.md +50 -0
- package/payload/agents-omp/performance-reviewer.md +81 -0
- package/payload/agents-omp/postmortem.md +82 -0
- package/payload/agents-omp/release-mr.md +274 -0
- package/payload/agents-omp/security-reviewer.md +121 -0
- package/payload/agents-omp/test-fix.md +33 -0
- package/payload/agents-omp/test-writer.md +39 -0
- package/payload/ci-templates/claude-pipeline.gitlab-ci.yml +220 -7
- 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 +35 -35
- 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 +233 -31
- package/payload/ci-templates/scripts/lib/platform.sh +270 -13
- package/payload/ci-templates/scripts/lib/usage-capture-omp.sh +123 -0
- 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 +56 -38
- package/payload/ci-templates/scripts/postmortem.sh +11 -1
- package/payload/ci-templates/scripts/review-fix.sh +23 -12
- package/payload/ci-templates/scripts/review.sh +12 -8
- package/payload/ci-templates/scripts/test-fix.sh +8 -1
- 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 +3 -3
- package/payload/skills/fix-tests/SKILL.md +4 -4
- package/payload/skills/implement-issue/SKILL.md +1 -1
- package/payload/skills/init-pipeline-config/SKILL.md +4 -4
- package/payload/skills/postmortem-mr/SKILL.md +1 -1
- package/payload/skills/review-mr/SKILL.md +1 -1
- package/payload/skills/triage-issue/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
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Runner for the opt-in `docs-sync` job. Finds documentation gaps AND acts on
|
|
3
|
+
# them in a single pass: on a wip MR/PR the skill applies the documentation
|
|
4
|
+
# edits and this script commits and pushes them, on any other MR/PR the skill
|
|
5
|
+
# reports the gaps and this script posts them as an advisory comment.
|
|
6
|
+
#
|
|
7
|
+
# The runner is mechanical: it classifies the MR/PR, computes the changed files
|
|
8
|
+
# and the full diff, runs the /docs-sync skill, posts the verdict, and commits.
|
|
9
|
+
# Every judgement about what needs documenting lives in the skill and in the
|
|
10
|
+
# docs-sync agent it spawns.
|
|
11
|
+
#
|
|
12
|
+
# The diff is always base..head, never an incremental slice: a gap introduced by
|
|
13
|
+
# an earlier push must still be found on a later one.
|
|
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
|
+
pipe_defaults
|
|
19
|
+
|
|
20
|
+
DOCS_MARKER="**Automated Documentation Sync**"
|
|
21
|
+
CONTEXT_FILE="$PIPE_CONTEXT_DIR/docs-sync-context.md"
|
|
22
|
+
FILES_FILE="$PIPE_CONTEXT_DIR/docs-sync-files.txt"
|
|
23
|
+
DIFF_FILE="$PIPE_CONTEXT_DIR/docs-sync-diff.patch"
|
|
24
|
+
DOCS_ENV="$PIPE_CONTEXT_DIR/docs-sync.env"
|
|
25
|
+
COMMENT_FILE="$PIPE_CONTEXT_DIR/docs-sync-comment.md"
|
|
26
|
+
|
|
27
|
+
# Documentation roots the agent may edit and this script may commit. Comma
|
|
28
|
+
# separated in PIPE_DOCS_ROOTS; a root is a directory or a single file.
|
|
29
|
+
# The split is a bash field split, not `tr ',' '\n'` piped into `read`: that
|
|
30
|
+
# pipeline emits no trailing newline, so `read` silently drops the LAST root.
|
|
31
|
+
# Surrounding whitespace is trimmed per element so "docs, CLAUDE.md" works.
|
|
32
|
+
DOCS_ROOTS=()
|
|
33
|
+
IFS=',' read -ra DOCS_ROOTS_RAW <<< "$PIPE_DOCS_ROOTS"
|
|
34
|
+
for root in "${DOCS_ROOTS_RAW[@]}"; do
|
|
35
|
+
root="${root#"${root%%[![:space:]]*}"}"
|
|
36
|
+
root="${root%"${root##*[![:space:]]}"}"
|
|
37
|
+
if [ -n "$root" ]; then
|
|
38
|
+
DOCS_ROOTS+=("$root")
|
|
39
|
+
fi
|
|
40
|
+
done
|
|
41
|
+
if [ "${#DOCS_ROOTS[@]}" -eq 0 ]; then
|
|
42
|
+
pipe_log "PIPE_DOCS_ROOTS is empty, nothing to sync"
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# True when the path is one of the documentation roots or sits under one.
|
|
47
|
+
docs_root_path() {
|
|
48
|
+
local root
|
|
49
|
+
for root in "${DOCS_ROOTS[@]}"; do
|
|
50
|
+
[ "$1" = "$root" ] && return 0
|
|
51
|
+
case "$1" in "$root"/*) return 0 ;; esac
|
|
52
|
+
done
|
|
53
|
+
return 1
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# 1. Fetch MR/PR meta (token work). The source branch comes from the same read:
|
|
57
|
+
# it is what an applied documentation commit is pushed to, and the CI runtime
|
|
58
|
+
# does not always provide it.
|
|
59
|
+
META=$(platform_mr_meta)
|
|
60
|
+
MR_TITLE=$(echo "$META" | jq -r '.title // ""')
|
|
61
|
+
MR_LABELS=$(echo "$META" | jq -r '.labels // ""')
|
|
62
|
+
META_SOURCE_BRANCH=$(echo "$META" | jq -r '.source_branch // ""')
|
|
63
|
+
if [ -z "${PIPE_SOURCE_BRANCH:-}" ] && [ -n "$META_SOURCE_BRANCH" ]; then
|
|
64
|
+
PIPE_SOURCE_BRANCH="$META_SOURCE_BRANCH"
|
|
65
|
+
export PIPE_SOURCE_BRANCH
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# 2. Classify. Only an autonomous (wip) MR/PR gets its documentation edited;
|
|
69
|
+
# a human-authored one gets a report.
|
|
70
|
+
DOCS_MODE=report
|
|
71
|
+
if echo "$MR_LABELS" | grep -q "$PIPE_LABEL_WIP"; then
|
|
72
|
+
DOCS_MODE=apply
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# 3. Full-MR diff base. platform_compute_diff_base would hand back the previous
|
|
76
|
+
# push head on a re-run, which is the wrong scope for this job, so the base
|
|
77
|
+
# is always the merge base with the target branch.
|
|
78
|
+
git fetch origin "$PIPE_TARGET_BRANCH" 2>/dev/null || true
|
|
79
|
+
DIFF_BASE=$(git merge-base "origin/$PIPE_TARGET_BRANCH" "$PIPE_HEAD_SHA" 2>/dev/null || echo "origin/$PIPE_TARGET_BRANCH")
|
|
80
|
+
pipe_log "mode=$DOCS_MODE diff base=$DIFF_BASE head=$PIPE_HEAD_SHA"
|
|
81
|
+
|
|
82
|
+
git diff --name-only "$DIFF_BASE" "$PIPE_HEAD_SHA" > "$FILES_FILE" 2>/dev/null || : > "$FILES_FILE"
|
|
83
|
+
if [ ! -s "$FILES_FILE" ]; then
|
|
84
|
+
pipe_log "No changed files in $DIFF_BASE..$PIPE_HEAD_SHA, skipping docs-sync"
|
|
85
|
+
exit 0
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# 4. Skip when the MR/PR only touches documentation: there is no new behaviour
|
|
89
|
+
# to document, and the author is already editing the docs.
|
|
90
|
+
CODE_CHANGED=false
|
|
91
|
+
while IFS= read -r file; do
|
|
92
|
+
[ -n "$file" ] || continue
|
|
93
|
+
if ! docs_root_path "$file"; then
|
|
94
|
+
CODE_CHANGED=true
|
|
95
|
+
break
|
|
96
|
+
fi
|
|
97
|
+
done < "$FILES_FILE"
|
|
98
|
+
if [ "$CODE_CHANGED" = "false" ]; then
|
|
99
|
+
pipe_log "Only documentation paths changed, skipping docs-sync"
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
git diff "$DIFF_BASE" "$PIPE_HEAD_SHA" > "$DIFF_FILE" 2>/dev/null || : > "$DIFF_FILE"
|
|
104
|
+
|
|
105
|
+
# 5. In apply mode, check out the real source branch so the edits and the commit
|
|
106
|
+
# land on its tip. Loop guard: at most one consecutive automated docs commit
|
|
107
|
+
# per branch streak, otherwise apply -> pipeline -> apply would never settle.
|
|
108
|
+
# The guard degrades this run to report mode instead of skipping it, so the
|
|
109
|
+
# MR/PR still gets a verdict.
|
|
110
|
+
if [ "$DOCS_MODE" = "apply" ]; then
|
|
111
|
+
pipe_git_identity
|
|
112
|
+
pipe_checkout_source
|
|
113
|
+
if [ "$(pipe_count_fix_commits "$PIPE_COMMIT_DOCSSYNC")" -gt 0 ]; then
|
|
114
|
+
pipe_log "Tip is already an automated docs commit, degrading to report mode"
|
|
115
|
+
DOCS_MODE=report
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# 6. Write the context file the skill reads. Unbounded text (the diff, the file
|
|
120
|
+
# list) stays in its own file, never in argv or the environment.
|
|
121
|
+
{
|
|
122
|
+
echo "# Documentation sync context"
|
|
123
|
+
echo
|
|
124
|
+
echo "Mode: $DOCS_MODE"
|
|
125
|
+
echo "Title: $MR_TITLE"
|
|
126
|
+
echo "Documentation roots: $PIPE_DOCS_ROOTS"
|
|
127
|
+
echo "Changed files: $FILES_FILE"
|
|
128
|
+
echo "Full diff: $DIFF_FILE"
|
|
129
|
+
echo "Diff range: $DIFF_BASE..$PIPE_HEAD_SHA"
|
|
130
|
+
} > "$CONTEXT_FILE"
|
|
131
|
+
|
|
132
|
+
# 7. Run the skill. It reads the context, spawns the docs-sync agent in the
|
|
133
|
+
# requested mode, writes $PIPE_RESULT_DOCSSYNC, the comment body, and the
|
|
134
|
+
# docs-sync.env marker this script reads back.
|
|
135
|
+
pipe_run_agent docs-sync "/docs-sync" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_REVIEW" < /dev/null
|
|
136
|
+
|
|
137
|
+
# 8. Read the marker. DOCS_COMMENT_PATH carries the body because a comment is
|
|
138
|
+
# unbounded text. A result file written by a model can be malformed JSON, so
|
|
139
|
+
# the fallback extraction is defensive: an unparseable or comment-less result
|
|
140
|
+
# is treated exactly like a missing one.
|
|
141
|
+
DOCS_HAS_GAPS=$(pipe_get_env "$DOCS_ENV" DOCS_HAS_GAPS); DOCS_HAS_GAPS=${DOCS_HAS_GAPS:-false}
|
|
142
|
+
DOCS_CHANGED=$(pipe_get_env "$DOCS_ENV" DOCS_CHANGED); DOCS_CHANGED=${DOCS_CHANGED:-false}
|
|
143
|
+
DOCS_BODY=$(pipe_get_env "$DOCS_ENV" DOCS_COMMENT_PATH); DOCS_BODY=${DOCS_BODY:-$PIPE_CONTEXT_DIR/docs-sync-body.md}
|
|
144
|
+
if [ ! -s "$DOCS_BODY" ] && [ -f "$PIPE_RESULT_DOCSSYNC" ]; then
|
|
145
|
+
RESULT_BODY="$PIPE_CONTEXT_DIR/docs-sync-result-body.md"
|
|
146
|
+
jq -re '.comment // empty' "$PIPE_RESULT_DOCSSYNC" > "$RESULT_BODY" 2>/dev/null || true
|
|
147
|
+
if [ -s "$RESULT_BODY" ]; then
|
|
148
|
+
DOCS_BODY="$RESULT_BODY"
|
|
149
|
+
fi
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
DOCS_FAILED=false
|
|
153
|
+
if [ -s "$DOCS_BODY" ]; then
|
|
154
|
+
{
|
|
155
|
+
printf '%s\n\n' "$DOCS_MARKER"
|
|
156
|
+
cat "$DOCS_BODY"
|
|
157
|
+
} > "$COMMENT_FILE"
|
|
158
|
+
else
|
|
159
|
+
DOCS_FAILED=true
|
|
160
|
+
{
|
|
161
|
+
printf '%s\n\n⚠️ ' "$DOCS_MARKER"
|
|
162
|
+
pipe_failure_notice_files "documentation sync" \
|
|
163
|
+
"check by hand whether these changes need updates under $PIPE_DOCS_ROOTS" \
|
|
164
|
+
"$PIPE_AGENT_STDERR" "$PIPE_RESULT_DOCSSYNC"
|
|
165
|
+
} > "$COMMENT_FILE"
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# 9. Always post the verdict. Metrics reflect the runner's actual commit
|
|
169
|
+
# decision, not the skill's informational DOCS_CHANGED marker.
|
|
170
|
+
DOCS_COMMITTED=false
|
|
171
|
+
emit_docs_metric() {
|
|
172
|
+
pipe_metric_event docs-sync docs_sync_gaps \
|
|
173
|
+
"$(jq -n --arg m "$DOCS_MODE" \
|
|
174
|
+
--argjson g "$([ "$DOCS_HAS_GAPS" = "true" ] && echo true || echo false)" \
|
|
175
|
+
--argjson c "$([ "$DOCS_COMMITTED" = "true" ] && echo true || echo false)" \
|
|
176
|
+
--argjson f "$([ "$DOCS_FAILED" = "true" ] && echo true || echo false)" \
|
|
177
|
+
'{mode: $m, has_gaps: $g, changed: $c, failed: $f}')"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
platform_post_comment_file "$COMMENT_FILE"
|
|
181
|
+
|
|
182
|
+
# 10. Commit and push what the agent applied. A failed agent applied nothing, so
|
|
183
|
+
# the notice posted above is the whole output and the exit is 0: an
|
|
184
|
+
# automation outage is not a documentation gap.
|
|
185
|
+
if [ "$DOCS_FAILED" = "true" ]; then
|
|
186
|
+
emit_docs_metric
|
|
187
|
+
pipe_log "Agent did not complete, manual-review notice posted, nothing to commit"
|
|
188
|
+
exit 0
|
|
189
|
+
fi
|
|
190
|
+
if [ "$DOCS_MODE" != "apply" ]; then
|
|
191
|
+
emit_docs_metric
|
|
192
|
+
exit 0
|
|
193
|
+
fi
|
|
194
|
+
if [ "$DOCS_CHANGED" != "true" ]; then
|
|
195
|
+
pipe_log "Skill reported no documentation edits, checking the staged diff anyway"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# Stage each root on its own: one missing root must not stop the others from
|
|
199
|
+
# being staged, which a single `git add` of the whole set would do.
|
|
200
|
+
for root in "${DOCS_ROOTS[@]}"; do
|
|
201
|
+
[ -e "$root" ] || continue
|
|
202
|
+
git add -- "$root" || true
|
|
203
|
+
done
|
|
204
|
+
if git diff --cached --quiet; then
|
|
205
|
+
emit_docs_metric
|
|
206
|
+
pipe_log "No documentation changes staged, nothing to commit"
|
|
207
|
+
exit 0
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
git commit -m "$PIPE_COMMIT_DOCSSYNC"
|
|
211
|
+
DOCS_COMMITTED=true
|
|
212
|
+
emit_docs_metric
|
|
213
|
+
|
|
214
|
+
# The agent may only touch the documentation roots, but an edit outside them
|
|
215
|
+
# would leave the working tree dirty and abort the rebase below before it
|
|
216
|
+
# starts, after which `git rebase --abort` fails too. Discard out-of-scope
|
|
217
|
+
# edits so the tree is clean, enforcing the documented boundary.
|
|
218
|
+
if ! git diff --quiet; then
|
|
219
|
+
pipe_log "Discarding out-of-scope working-tree changes left outside $PIPE_DOCS_ROOTS:"
|
|
220
|
+
git diff --name-only
|
|
221
|
+
git checkout -- .
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
pipe_rebase_and_push
|
|
225
|
+
pipe_log "Pushed the docs-sync commit, a new pipeline will run"
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Runner for the opt-in, issue-driven half of E2E test generation. The runner
|
|
3
|
+
# owns forge mutations and git; the skill may only write a new test file.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
# shellcheck source=lib/pipeline-common.sh
|
|
8
|
+
source "$SCRIPT_DIR/lib/pipeline-common.sh"
|
|
9
|
+
# shellcheck source=lib/issue-loop.sh
|
|
10
|
+
source "$SCRIPT_DIR/lib/issue-loop.sh"
|
|
11
|
+
pipe_defaults
|
|
12
|
+
|
|
13
|
+
E2E_ENV="$PIPE_CONTEXT_DIR/e2e-gen.env"
|
|
14
|
+
ISSUES_FILE="$PIPE_CONTEXT_DIR/e2e-gen-issues.json"
|
|
15
|
+
MR_BODY="$PIPE_CONTEXT_DIR/e2e-gen-mr.md"
|
|
16
|
+
GENERATED=0
|
|
17
|
+
CREDIT_ABORT=0
|
|
18
|
+
MR_CREATED=0
|
|
19
|
+
|
|
20
|
+
write_marker() {
|
|
21
|
+
printf 'E2E_GENERATED_COUNT=%s\nE2E_CREDIT_ABORT=%s\nE2E_MR_CREATED=%s\n' \
|
|
22
|
+
"$GENERATED" "$CREDIT_ABORT" "$MR_CREATED" > "$E2E_ENV"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Persist an explicit zero marker even for an intentionally inactive schedule.
|
|
26
|
+
write_marker
|
|
27
|
+
if [ "$PIPE_E2E_TEST_GEN" != "1" ]; then
|
|
28
|
+
pipe_log "E2E test generation is disabled"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
case "$PIPE_E2E_TEST_DIR" in
|
|
33
|
+
''|/*|..|../*|*/..|*/../*)
|
|
34
|
+
pipe_log "Invalid project-relative E2E test directory: $PIPE_E2E_TEST_DIR"
|
|
35
|
+
exit 0
|
|
36
|
+
;;
|
|
37
|
+
esac
|
|
38
|
+
|
|
39
|
+
# E2E verification is intentionally separate from PIPE_VERIFY_CMD: projects
|
|
40
|
+
# configure a command that accepts one generated E2E spec as its final argument.
|
|
41
|
+
if [ -z "$PIPE_E2E_VERIFY_CMD" ]; then
|
|
42
|
+
pipe_log "PIPE_E2E_VERIFY_CMD is required to verify generated E2E tests; leaving issues unchanged"
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
if [ -z "$PIPE_E2E_BASE_URL" ]; then
|
|
46
|
+
pipe_log "PIPE_E2E_BASE_URL is required for generated E2E tests; leaving issues unchanged"
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
if ! issues_by_labels "$PIPE_LABEL_TESTREADY" "$PIPE_LABEL_E2E_SCOPE" opened "$PIPE_ISSUE_SCAN" > "$ISSUES_FILE"; then
|
|
51
|
+
pipe_log "Could not list test-ready issues"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
ISSUE_COUNT=$(jq 'length' "$ISSUES_FILE")
|
|
55
|
+
if [ "$ISSUE_COUNT" = "0" ]; then
|
|
56
|
+
pipe_log "No issues need E2E tests"
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
BRANCH="e2e-test-gen-$(date -u +%Y%m%d)"
|
|
61
|
+
pipe_git_identity
|
|
62
|
+
git fetch origin "$PIPE_TARGET_BRANCH" >/dev/null 2>&1 || {
|
|
63
|
+
pipe_log "Could not fetch target branch $PIPE_TARGET_BRANCH"
|
|
64
|
+
exit 1
|
|
65
|
+
}
|
|
66
|
+
# A branch of this daily name may contain a human amendment. Never reuse or
|
|
67
|
+
# force-push it; a later scheduled run can safely make a fresh daily branch.
|
|
68
|
+
git fetch origin "$BRANCH" >/dev/null 2>&1 || true
|
|
69
|
+
if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
|
|
70
|
+
pipe_log "Branch $BRANCH already exists; refusing to overwrite possible human work"
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
git checkout -b "$BRANCH" "origin/$PIPE_TARGET_BRANCH"
|
|
74
|
+
|
|
75
|
+
mark_stuck() {
|
|
76
|
+
issue_set_labels "$1" "$PIPE_LABEL_STUCK" "$PIPE_LABEL_TESTREADY" || true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
credit_abort() {
|
|
80
|
+
local iid="$1" body="$PIPE_CONTEXT_DIR/e2e-gen-credit-$1.md"
|
|
81
|
+
pipe_failure_notice_files "E2E test generation" \
|
|
82
|
+
"wait for credits to be restored, then let a later schedule retry this issue" \
|
|
83
|
+
"$PIPE_AGENT_STDERR" > "$body"
|
|
84
|
+
issue_comment_file "$iid" "$body" || pipe_log "Could not post credit-exhaustion notice for issue #$iid"
|
|
85
|
+
CREDIT_ABORT=1
|
|
86
|
+
write_marker
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
while IFS= read -r ISSUE; do
|
|
90
|
+
IID=$(jq -r '.iid' <<< "$ISSUE")
|
|
91
|
+
TITLE_FILE="$PIPE_CONTEXT_DIR/e2e-gen-title-$IID.txt"
|
|
92
|
+
DESC_FILE="$PIPE_CONTEXT_DIR/e2e-gen-description-$IID.txt"
|
|
93
|
+
CONTEXT_FILE="$PIPE_CONTEXT_DIR/e2e-gen-issue-$IID.md"
|
|
94
|
+
BEFORE_FILE="$PIPE_CONTEXT_DIR/e2e-gen-before-$IID.txt"
|
|
95
|
+
NEW_FILE="$PIPE_CONTEXT_DIR/e2e-gen-new-$IID.txt"
|
|
96
|
+
|
|
97
|
+
jq -r '(.title // "") | .[0:500]' <<< "$ISSUE" > "$TITLE_FILE"
|
|
98
|
+
jq -r '(.description // "(no description)") | .[0:3000]' <<< "$ISSUE" > "$DESC_FILE"
|
|
99
|
+
{
|
|
100
|
+
printf '# E2E test generation context\n\n'
|
|
101
|
+
printf 'Mode: generate\n'
|
|
102
|
+
printf 'Issue IID: %s\n' "$IID"
|
|
103
|
+
printf 'Issue Title File: %s\n' "$TITLE_FILE"
|
|
104
|
+
printf 'Issue Description File: %s\n' "$DESC_FILE"
|
|
105
|
+
printf 'Test Directory: %s\n' "$PIPE_E2E_TEST_DIR"
|
|
106
|
+
printf 'Base URL: %s\n' "$PIPE_E2E_BASE_URL"
|
|
107
|
+
printf 'Project Configuration: %s\n' "$PIPE_CONFIG_PATH"
|
|
108
|
+
printf 'Verification Command: %s\n' "$PIPE_E2E_VERIFY_CMD"
|
|
109
|
+
} > "$CONTEXT_FILE"
|
|
110
|
+
|
|
111
|
+
git ls-files --others --exclude-standard -- "$PIPE_E2E_TEST_DIR" | sort > "$BEFORE_FILE"
|
|
112
|
+
: > "$PIPE_AGENT_STDERR"
|
|
113
|
+
pipe_run_agent e2e-test-writer "/e2e-test-gen" "Agent,Read,Write,Edit,Glob,Grep" "$PIPE_MODEL_CODE" < /dev/null
|
|
114
|
+
# Discard all tracked edits: accepted work is strictly newly-created test files.
|
|
115
|
+
git reset --hard HEAD >/dev/null
|
|
116
|
+
if pipe_is_credit_exhaustion_files "$PIPE_AGENT_STDERR"; then
|
|
117
|
+
git ls-files --others --exclude-standard -- "$PIPE_E2E_TEST_DIR" | sort > "$PIPE_CONTEXT_DIR/e2e-gen-credit-after-$IID.txt"
|
|
118
|
+
comm -13 "$BEFORE_FILE" "$PIPE_CONTEXT_DIR/e2e-gen-credit-after-$IID.txt" | while IFS= read -r rejected; do
|
|
119
|
+
rm -f -- "$rejected"
|
|
120
|
+
done
|
|
121
|
+
credit_abort "$IID"
|
|
122
|
+
break
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
git ls-files --others --exclude-standard -- "$PIPE_E2E_TEST_DIR" | sort > "$PIPE_CONTEXT_DIR/e2e-gen-after-$IID.txt"
|
|
126
|
+
comm -13 "$BEFORE_FILE" "$PIPE_CONTEXT_DIR/e2e-gen-after-$IID.txt" > "$NEW_FILE"
|
|
127
|
+
if [ ! -s "$NEW_FILE" ]; then
|
|
128
|
+
pipe_log "No new E2E test file generated for issue #$IID"
|
|
129
|
+
mark_stuck "$IID"
|
|
130
|
+
continue
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
TEST_PASSED=true
|
|
134
|
+
while IFS= read -r SPEC; do
|
|
135
|
+
[ -n "$SPEC" ] || continue
|
|
136
|
+
if ! bash -c "$PIPE_E2E_VERIFY_CMD \"\$1\"" e2e-test-gen "$SPEC"; then
|
|
137
|
+
pipe_log "Generated E2E test $SPEC failed verification; attempting one repair"
|
|
138
|
+
{
|
|
139
|
+
printf '# E2E test generation context\n\n'
|
|
140
|
+
printf 'Mode: repair\n'
|
|
141
|
+
printf 'Generated Test: %s\n' "$SPEC"
|
|
142
|
+
printf 'Test Directory: %s\n' "$PIPE_E2E_TEST_DIR"
|
|
143
|
+
printf 'Base URL: %s\n' "$PIPE_E2E_BASE_URL"
|
|
144
|
+
printf 'Project Configuration: %s\n' "$PIPE_CONFIG_PATH"
|
|
145
|
+
printf 'Verification Command: %s\n' "$PIPE_E2E_VERIFY_CMD"
|
|
146
|
+
} > "$CONTEXT_FILE"
|
|
147
|
+
: > "$PIPE_AGENT_STDERR"
|
|
148
|
+
pipe_run_agent e2e-test-writer "/e2e-test-gen" "Agent,Read,Write,Edit,Glob,Grep" "$PIPE_MODEL_CODE" < /dev/null
|
|
149
|
+
git reset --hard HEAD >/dev/null
|
|
150
|
+
if pipe_is_credit_exhaustion_files "$PIPE_AGENT_STDERR"; then
|
|
151
|
+
while IFS= read -r rejected; do rm -f -- "$rejected"; done < "$NEW_FILE"
|
|
152
|
+
credit_abort "$IID"
|
|
153
|
+
break 2
|
|
154
|
+
fi
|
|
155
|
+
if ! bash -c "$PIPE_E2E_VERIFY_CMD \"\$1\"" e2e-test-gen "$SPEC"; then
|
|
156
|
+
TEST_PASSED=false
|
|
157
|
+
break
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
done < "$NEW_FILE"
|
|
161
|
+
|
|
162
|
+
if [ "$TEST_PASSED" = true ]; then
|
|
163
|
+
while IFS= read -r SPEC; do [ -n "$SPEC" ] && git add -- "$SPEC"; done < "$NEW_FILE"
|
|
164
|
+
if git diff --cached --quiet; then
|
|
165
|
+
pipe_log "Generated files for issue #$IID were not stageable"
|
|
166
|
+
mark_stuck "$IID"
|
|
167
|
+
continue
|
|
168
|
+
fi
|
|
169
|
+
git commit -m "test: add E2E coverage for issue #$IID"
|
|
170
|
+
GENERATED=$((GENERATED + 1))
|
|
171
|
+
issue_set_labels "$IID" "" "$PIPE_LABEL_TESTREADY" || true
|
|
172
|
+
else
|
|
173
|
+
while IFS= read -r rejected; do rm -f -- "$rejected"; done < "$NEW_FILE"
|
|
174
|
+
mark_stuck "$IID"
|
|
175
|
+
fi
|
|
176
|
+
write_marker
|
|
177
|
+
done < <(jq -c '.[]' "$ISSUES_FILE")
|
|
178
|
+
|
|
179
|
+
if [ "$GENERATED" = "0" ]; then
|
|
180
|
+
write_marker
|
|
181
|
+
exit 0
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# This is a new branch by construction. A concurrent branch creation makes this
|
|
185
|
+
# ordinary push fail rather than overwriting the other author's ref.
|
|
186
|
+
if ! git push origin "HEAD:refs/heads/$BRANCH"; then
|
|
187
|
+
pipe_log "Could not push $BRANCH safely; no MR/PR was created"
|
|
188
|
+
write_marker
|
|
189
|
+
exit 1
|
|
190
|
+
fi
|
|
191
|
+
{
|
|
192
|
+
printf '# Automated E2E test generation\n\n'
|
|
193
|
+
printf 'Generated and verified E2E tests for %s issue(s).\n' "$GENERATED"
|
|
194
|
+
} > "$MR_BODY"
|
|
195
|
+
MR_URL=$(mr_create_file "$BRANCH" "$PIPE_TARGET_BRANCH" "test: generated E2E coverage" "$MR_BODY" "" || true)
|
|
196
|
+
if [ -n "$MR_URL" ]; then
|
|
197
|
+
MR_CREATED=1
|
|
198
|
+
else
|
|
199
|
+
pipe_log "Push succeeded but MR/PR creation failed"
|
|
200
|
+
fi
|
|
201
|
+
write_marker
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# Source-only library. Failure notices distinguish two classes: model credit or
|
|
3
|
+
# quota exhaustion, which cannot succeed until the balance is restored, and all
|
|
4
|
+
# other agent failures, which may be transient or require manual follow-up.
|
|
5
|
+
#
|
|
6
|
+
# Both supported harness wordings are covered here: one reports that a credit
|
|
7
|
+
# balance is too low, while the other reports that a request requires more
|
|
8
|
+
# credits. Notices intentionally keep that implementation detail generic.
|
|
9
|
+
|
|
10
|
+
set -u
|
|
11
|
+
|
|
12
|
+
PIPE_CREDIT_EXHAUSTION_PATTERN='credit balance is too low|insufficient credits?|out of credits?|credits? exhausted|quota exceeded|requires more credits|payment required|402'
|
|
13
|
+
|
|
14
|
+
pipe_is_credit_exhaustion() {
|
|
15
|
+
local text
|
|
16
|
+
for text in "$@"; do
|
|
17
|
+
[ -n "$text" ] && printf '%s\n' "$text" | grep -qiE "$PIPE_CREDIT_EXHAUSTION_PATTERN" && return 0
|
|
18
|
+
done
|
|
19
|
+
return 1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pipe_failure_notice() {
|
|
23
|
+
local task="$1" manual_action="$2"
|
|
24
|
+
shift 2
|
|
25
|
+
|
|
26
|
+
if pipe_is_credit_exhaustion "$@"; then
|
|
27
|
+
printf '**Automated %s could not run because the model credit balance is exhausted.**\n\n' "$task"
|
|
28
|
+
printf 'This is an automation outage, not a finding about your changes.\n\n'
|
|
29
|
+
printf 'Retrying the pipeline will not help until the balance is topped up.\n\n'
|
|
30
|
+
printf 'Please %s. Automation resumes on later pushes.\n' "$manual_action"
|
|
31
|
+
else
|
|
32
|
+
printf '**Automated %s failed to complete.**\n\n' "$task"
|
|
33
|
+
printf 'No result was produced. Check the CI job log for details.\n\n'
|
|
34
|
+
printf 'The failure may be transient, so retrying the pipeline is appropriate. If it continues, please %s.\n' "$manual_action"
|
|
35
|
+
fi
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pipe_failure_log_notice() {
|
|
39
|
+
local task="$1" manual_action="$2"
|
|
40
|
+
shift 2
|
|
41
|
+
|
|
42
|
+
if pipe_is_credit_exhaustion "$@"; then
|
|
43
|
+
printf 'CREDIT EXHAUSTED: Automated %s could not run because the model credit balance is exhausted.\n' "$task"
|
|
44
|
+
printf 'Retrying will not help until the balance is topped up. Please %s.\n' "$manual_action"
|
|
45
|
+
else
|
|
46
|
+
printf 'FAILED: Automated %s failed to complete.\n' "$task"
|
|
47
|
+
printf 'Check stderr, retry if the failure is transient, and if it persists please %s.\n' "$manual_action"
|
|
48
|
+
fi
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pipe_is_credit_exhaustion_files() {
|
|
52
|
+
local file
|
|
53
|
+
for file in "$@"; do
|
|
54
|
+
[ -s "$file" ] && grep -qiE "$PIPE_CREDIT_EXHAUSTION_PATTERN" "$file" && return 0
|
|
55
|
+
done
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pipe_failure_notice_files() {
|
|
60
|
+
local task="$1" manual_action="$2"
|
|
61
|
+
shift 2
|
|
62
|
+
|
|
63
|
+
if pipe_is_credit_exhaustion_files "$@"; then
|
|
64
|
+
printf '**Automated %s could not run because the model credit balance is exhausted.**\n\n' "$task"
|
|
65
|
+
printf 'This is an automation outage, not a finding about your changes.\n\n'
|
|
66
|
+
printf 'Retrying the pipeline will not help until the balance is topped up.\n\n'
|
|
67
|
+
printf 'Please %s. Automation resumes on later pushes.\n' "$manual_action"
|
|
68
|
+
else
|
|
69
|
+
printf '**Automated %s failed to complete.**\n\n' "$task"
|
|
70
|
+
printf 'No result was produced. Check the CI job log for details.\n\n'
|
|
71
|
+
printf 'The failure may be transient, so retrying the pipeline is appropriate. If it continues, please %s.\n' "$manual_action"
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pipe_failure_log_notice_files() {
|
|
76
|
+
local task="$1" manual_action="$2"
|
|
77
|
+
shift 2
|
|
78
|
+
|
|
79
|
+
if pipe_is_credit_exhaustion_files "$@"; then
|
|
80
|
+
printf 'CREDIT EXHAUSTED: Automated %s could not run because the model credit balance is exhausted.\n' "$task"
|
|
81
|
+
printf 'Retrying will not help until the balance is topped up. Please %s.\n' "$manual_action"
|
|
82
|
+
else
|
|
83
|
+
printf 'FAILED: Automated %s failed to complete.\n' "$task"
|
|
84
|
+
printf 'Check stderr, retry if the failure is transient, and if it persists please %s.\n' "$manual_action"
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pipe_agent_failed() {
|
|
89
|
+
PIPE_AGENT_STDERR_TEXT=''
|
|
90
|
+
export PIPE_AGENT_STDERR_TEXT
|
|
91
|
+
|
|
92
|
+
if [ -f "${PIPE_AGENT_STDERR:-}" ]; then
|
|
93
|
+
PIPE_AGENT_STDERR_TEXT=$(cat "$PIPE_AGENT_STDERR" 2>/dev/null) || PIPE_AGENT_STDERR_TEXT=''
|
|
94
|
+
export PIPE_AGENT_STDERR_TEXT
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if [ -z "${1:-}" ]; then
|
|
98
|
+
[ -n "$PIPE_AGENT_STDERR_TEXT" ]
|
|
99
|
+
elif [ -n "$PIPE_AGENT_STDERR_TEXT" ]; then
|
|
100
|
+
return 1
|
|
101
|
+
else
|
|
102
|
+
return 0
|
|
103
|
+
fi
|
|
104
|
+
}
|