@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.
Files changed (69) hide show
  1. package/README.md +27 -5
  2. package/package.json +2 -2
  3. package/payload/INSTALL.md +14 -5
  4. package/payload/agents/agent-architect.md +1 -1
  5. package/payload/agents/code-reviewer.md +1 -1
  6. package/payload/agents/codebase-auditor.md +1 -1
  7. package/payload/agents/coder.md +3 -3
  8. package/payload/agents/decomposer.md +1 -1
  9. package/payload/agents/docs-sync.md +1 -1
  10. package/payload/agents/e2e-test-writer.md +1 -1
  11. package/payload/agents/performance-reviewer.md +1 -1
  12. package/payload/agents/release-mr.md +1 -1
  13. package/payload/agents/security-reviewer.md +1 -1
  14. package/payload/agents/test-fix.md +4 -4
  15. package/payload/agents/test-writer.md +3 -3
  16. package/payload/agents-omp/agent-architect.md +101 -0
  17. package/payload/agents-omp/code-reviewer.md +86 -0
  18. package/payload/agents-omp/codebase-auditor.md +73 -0
  19. package/payload/agents-omp/coder.md +57 -0
  20. package/payload/agents-omp/decomposer.md +70 -0
  21. package/payload/agents-omp/docs-sync.md +114 -0
  22. package/payload/agents-omp/e2e-test-writer.md +47 -0
  23. package/payload/agents-omp/migration-reviewer.md +99 -0
  24. package/payload/agents-omp/orchestrator.md +50 -0
  25. package/payload/agents-omp/performance-reviewer.md +81 -0
  26. package/payload/agents-omp/postmortem.md +82 -0
  27. package/payload/agents-omp/release-mr.md +274 -0
  28. package/payload/agents-omp/security-reviewer.md +121 -0
  29. package/payload/agents-omp/test-fix.md +33 -0
  30. package/payload/agents-omp/test-writer.md +39 -0
  31. package/payload/ci-templates/claude-pipeline.gitlab-ci.yml +220 -7
  32. package/payload/ci-templates/github/claude-issue-pipeline.yml +1 -1
  33. package/payload/ci-templates/github/claude-pipeline.yml +2 -2
  34. package/payload/ci-templates/github/claude-test-fix.yml +1 -1
  35. package/payload/ci-templates/scripts/agent-architect.sh +233 -0
  36. package/payload/ci-templates/scripts/code.sh +35 -35
  37. package/payload/ci-templates/scripts/codebase-audit.sh +252 -0
  38. package/payload/ci-templates/scripts/coverage-ratchet.sh +77 -0
  39. package/payload/ci-templates/scripts/cve-fix.sh +246 -0
  40. package/payload/ci-templates/scripts/docs-sync.sh +225 -0
  41. package/payload/ci-templates/scripts/e2e-test-gen.sh +201 -0
  42. package/payload/ci-templates/scripts/lib/failure-notice.sh +104 -0
  43. package/payload/ci-templates/scripts/lib/issue-loop.sh +155 -40
  44. package/payload/ci-templates/scripts/lib/pipeline-common.sh +233 -31
  45. package/payload/ci-templates/scripts/lib/platform.sh +270 -13
  46. package/payload/ci-templates/scripts/lib/usage-capture-omp.sh +123 -0
  47. package/payload/ci-templates/scripts/lib/usage-capture.sh +7 -1
  48. package/payload/ci-templates/scripts/metrics-snapshot.sh +377 -0
  49. package/payload/ci-templates/scripts/orchestrate.sh +56 -38
  50. package/payload/ci-templates/scripts/postmortem.sh +11 -1
  51. package/payload/ci-templates/scripts/review-fix.sh +23 -12
  52. package/payload/ci-templates/scripts/review.sh +12 -8
  53. package/payload/ci-templates/scripts/test-fix.sh +8 -1
  54. package/payload/skills/agent-architect/SKILL.md +45 -0
  55. package/payload/skills/codebase-audit/SKILL.md +84 -0
  56. package/payload/skills/cve-fix/SKILL.md +98 -0
  57. package/payload/skills/docs-sync/SKILL.md +74 -0
  58. package/payload/skills/e2e-test-gen/SKILL.md +65 -0
  59. package/payload/skills/fix-review-findings/SKILL.md +3 -3
  60. package/payload/skills/fix-tests/SKILL.md +4 -4
  61. package/payload/skills/implement-issue/SKILL.md +1 -1
  62. package/payload/skills/init-pipeline-config/SKILL.md +4 -4
  63. package/payload/skills/postmortem-mr/SKILL.md +1 -1
  64. package/payload/skills/review-mr/SKILL.md +1 -1
  65. package/payload/skills/triage-issue/SKILL.md +2 -2
  66. package/payload/templates/memory-index.template.md +34 -0
  67. package/payload/templates/pipeline-config.template.md +11 -1
  68. package/payload/templates/review_suppressions.template.md +55 -0
  69. 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
+ }