@cxi-lmai/ci-agent-platform 3.0.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/bin/init.mjs +236 -0
  4. package/package.json +47 -0
  5. package/payload/INSTALL.md +113 -0
  6. package/payload/agents/agent-architect.md +101 -0
  7. package/payload/agents/code-reviewer.md +87 -0
  8. package/payload/agents/codebase-auditor.md +73 -0
  9. package/payload/agents/coder.md +56 -0
  10. package/payload/agents/decomposer.md +70 -0
  11. package/payload/agents/docs-sync.md +115 -0
  12. package/payload/agents/e2e-test-writer.md +47 -0
  13. package/payload/agents/migration-reviewer.md +100 -0
  14. package/payload/agents/orchestrator.md +50 -0
  15. package/payload/agents/performance-reviewer.md +82 -0
  16. package/payload/agents/postmortem.md +83 -0
  17. package/payload/agents/release-mr.md +274 -0
  18. package/payload/agents/security-reviewer.md +122 -0
  19. package/payload/agents/test-fix.md +33 -0
  20. package/payload/agents/test-writer.md +40 -0
  21. package/payload/ci-templates/claude-pipeline.gitlab-ci.yml +233 -0
  22. package/payload/ci-templates/github/README.md +76 -0
  23. package/payload/ci-templates/github/claude-issue-pipeline.yml +141 -0
  24. package/payload/ci-templates/github/claude-pipeline.yml +141 -0
  25. package/payload/ci-templates/github/claude-test-fix.yml +104 -0
  26. package/payload/ci-templates/scripts/code.sh +114 -0
  27. package/payload/ci-templates/scripts/lib/issue-loop.sh +430 -0
  28. package/payload/ci-templates/scripts/lib/pipeline-common.sh +280 -0
  29. package/payload/ci-templates/scripts/lib/platform.sh +177 -0
  30. package/payload/ci-templates/scripts/lib/usage-capture.sh +110 -0
  31. package/payload/ci-templates/scripts/orchestrate.sh +294 -0
  32. package/payload/ci-templates/scripts/postmortem.sh +45 -0
  33. package/payload/ci-templates/scripts/review-fix.sh +90 -0
  34. package/payload/ci-templates/scripts/review.sh +93 -0
  35. package/payload/ci-templates/scripts/test-fix.sh +58 -0
  36. package/payload/skills/fix-review-findings/SKILL.md +79 -0
  37. package/payload/skills/fix-tests/SKILL.md +70 -0
  38. package/payload/skills/implement-issue/SKILL.md +62 -0
  39. package/payload/skills/init-pipeline-config/SKILL.md +96 -0
  40. package/payload/skills/postmortem-mr/SKILL.md +50 -0
  41. package/payload/skills/review-mr/SKILL.md +82 -0
  42. package/payload/skills/triage-issue/SKILL.md +74 -0
  43. package/payload/templates/pipeline-config.template.md +98 -0
  44. package/payload/templates/review_suppressions.template.md +25 -0
  45. package/payload/templates/spec-issue.template.md +64 -0
@@ -0,0 +1,141 @@
1
+ # =============================================================================
2
+ # Claude ci-agent-platform: GitHub Actions workflow (tier 1, review + review-fix)
3
+ # =============================================================================
4
+ # Copy this to .github/workflows/claude-pipeline.yml in your project. It mirrors
5
+ # the GitLab review and review-fix jobs. The test-fix job lives in a separate
6
+ # workflow (claude-test-fix.yml) because it hangs off YOUR test workflow.
7
+ #
8
+ # Configuration:
9
+ # - Repository/org VARIABLES (Settings > Variables): PIPE_TARGET_BRANCH,
10
+ # PIPE_LABEL_WIP, PIPE_LABEL_STUCK, PIPE_BOT_USER, PIPE_VERIFY_CMD, etc.
11
+ # Any PIPE_ variable overrides the defaults baked into the scripts.
12
+ # - SECRETS (Settings > Secrets): ANTHROPIC_API_KEY, and PIPE_BOT_TOKEN
13
+ # (a bot Personal Access Token). The default GITHUB_TOKEN cannot re-trigger
14
+ # workflows on its own push, so the fix loop needs a PAT to re-run review.
15
+ # - Vendor the ci-agent-platform repository's ci-templates/scripts/ into your
16
+ # repo (see its ci-templates/github/README.md), path set by
17
+ # PIPE_SCRIPTS_DIR below.
18
+ #
19
+ # on_failure mapping: GitLab's on_failure is `needs` + `if: failure()` here.
20
+ # =============================================================================
21
+
22
+ name: claude-pipeline
23
+
24
+ on:
25
+ pull_request:
26
+ types: [opened, synchronize, reopened, ready_for_review, labeled]
27
+
28
+ permissions:
29
+ contents: write
30
+ pull-requests: write
31
+
32
+ env:
33
+ PIPE_SCRIPTS_DIR: ".claude-pipeline/scripts"
34
+ PIPE_PLATFORM: "github"
35
+ PIPE_CONFIG_PATH: ".claude/pipeline-config.md"
36
+ PIPE_CONTEXT_DIR: "build/pipeline"
37
+ PIPE_TARGET_BRANCH: ${{ vars.PIPE_TARGET_BRANCH || github.event.repository.default_branch || 'main' }}
38
+ PIPE_LABEL_WIP: ${{ vars.PIPE_LABEL_WIP || 'pipe-wip' }}
39
+ PIPE_LABEL_STUCK: ${{ vars.PIPE_LABEL_STUCK || 'pipe-stuck' }}
40
+ PIPE_BOT_USER: ${{ vars.PIPE_BOT_USER || '' }}
41
+ PIPE_VERIFY_CMD: ${{ vars.PIPE_VERIFY_CMD || '' }}
42
+ PIPE_COMPILE_CMD: ${{ vars.PIPE_COMPILE_CMD || '' }}
43
+ PIPE_FIX_LOOP_CAP: ${{ vars.PIPE_FIX_LOOP_CAP || '2' }}
44
+ PIPE_COMMIT_REVIEWFIX: ${{ vars.PIPE_COMMIT_REVIEWFIX || 'Fix review findings' }}
45
+ PIPE_GIT_NAME: ${{ vars.PIPE_GIT_NAME || 'Pipeline Bot' }}
46
+ PIPE_GIT_EMAIL: ${{ vars.PIPE_GIT_EMAIL || 'bot@pipeline.ci' }}
47
+ PIPE_MODEL_REVIEW: ${{ vars.PIPE_MODEL_REVIEW || 'sonnet' }}
48
+ PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'sonnet' }}
49
+ PIPE_AGENT_ENV_ALLOWLIST: ${{ vars.PIPE_AGENT_ENV_ALLOWLIST || '' }}
50
+ # Runtime mapping onto the PIPE_ names the scripts read.
51
+ PIPE_REPO: ${{ github.repository }}
52
+ PIPE_PROJECT_PATH: ${{ github.repository }}
53
+ PIPE_SERVER_HOST: "github.com"
54
+ PIPE_MR_IID: ${{ github.event.pull_request.number }}
55
+ PIPE_SOURCE_BRANCH: ${{ github.event.pull_request.head.ref }}
56
+ PIPE_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
57
+ PIPE_PREV_HEAD_SHA: ${{ github.event.before }}
58
+
59
+ jobs:
60
+ review:
61
+ # Only non-draft PRs targeting the integration branch.
62
+ if: >-
63
+ github.event.pull_request.draft == false &&
64
+ github.event.pull_request.base.ref == (vars.PIPE_TARGET_BRANCH || github.event.repository.default_branch || 'main')
65
+ # One review at a time per PR (finding P5): a coder-created PR emits an
66
+ # `opened` event immediately followed by a `labeled` event (GitHub cannot
67
+ # attach labels at PR creation), which used to run two identical reviews.
68
+ # The newer event cancels the stale run. Job-level on purpose: review-fix
69
+ # must never be cancelled mid-push. The coder also creates its PRs
70
+ # draft-first (see _ghi_pr_create), so this is a second line of defence.
71
+ concurrency:
72
+ group: claude-review-pr-${{ github.event.pull_request.number }}
73
+ cancel-in-progress: true
74
+ runs-on: ubuntu-latest
75
+ # Hard wall-clock cap on API spend if a claude session hangs or loops.
76
+ timeout-minutes: 15
77
+ outputs:
78
+ has_bugs: ${{ steps.gate.outputs.has_bugs }}
79
+ steps:
80
+ - uses: actions/checkout@v4
81
+ with:
82
+ ref: ${{ github.event.pull_request.head.sha }}
83
+ fetch-depth: 0
84
+ token: ${{ secrets.PIPE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
85
+ - name: Install claude-code
86
+ run: command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code
87
+ - name: Run review
88
+ env:
89
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
90
+ GH_TOKEN: ${{ secrets.PIPE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
91
+ PIPE_NO_GATE: "1" # the gate step below owns the exit code
92
+ run: bash "$PIPE_SCRIPTS_DIR/review.sh"
93
+ - name: Gate on the verdict
94
+ id: gate
95
+ if: always()
96
+ run: |
97
+ HAS_BUGS=$(grep -E '^REVIEW_HAS_BUGS=' "$PIPE_CONTEXT_DIR/review.env" | cut -d= -f2 || echo false)
98
+ echo "has_bugs=${HAS_BUGS:-false}" >> "$GITHUB_OUTPUT"
99
+ if [ "${HAS_BUGS:-false}" = "true" ]; then
100
+ echo "Review found confirmed bugs, failing the check"
101
+ exit 1
102
+ fi
103
+ - name: Upload metrics
104
+ if: always()
105
+ uses: actions/upload-artifact@v4
106
+ with:
107
+ name: review-metrics
108
+ path: ${{ env.PIPE_CONTEXT_DIR }}/metrics/
109
+ if-no-files-found: ignore
110
+
111
+ review-fix:
112
+ needs: review
113
+ # Runs when review flagged bugs (its job failed / output true) on a wip PR.
114
+ if: >-
115
+ always() &&
116
+ needs.review.outputs.has_bugs == 'true' &&
117
+ contains(github.event.pull_request.labels.*.name, vars.PIPE_LABEL_WIP || 'pipe-wip')
118
+ runs-on: ubuntu-latest
119
+ # Hard wall-clock cap on API spend if a claude session hangs or loops.
120
+ timeout-minutes: 20
121
+ steps:
122
+ - uses: actions/checkout@v4
123
+ with:
124
+ ref: ${{ github.event.pull_request.head.ref }}
125
+ fetch-depth: 0
126
+ token: ${{ secrets.PIPE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
127
+ - name: Install claude-code
128
+ run: command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code
129
+ - name: Run review-fix
130
+ env:
131
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
132
+ GH_TOKEN: ${{ secrets.PIPE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
133
+ REVIEW_HAS_BUGS: "true"
134
+ run: bash "$PIPE_SCRIPTS_DIR/review-fix.sh"
135
+ - name: Upload metrics
136
+ if: always()
137
+ uses: actions/upload-artifact@v4
138
+ with:
139
+ name: review-fix-metrics
140
+ path: ${{ env.PIPE_CONTEXT_DIR }}/metrics/
141
+ if-no-files-found: ignore
@@ -0,0 +1,104 @@
1
+ # =============================================================================
2
+ # Claude ci-agent-platform: GitHub Actions test-fix (tier 1)
3
+ # =============================================================================
4
+ # Copy to .github/workflows/claude-test-fix.yml. It hangs off YOUR test workflow:
5
+ # when that workflow fails on a pull request, this one resolves the PR, downloads
6
+ # the test artifacts, and runs the /fix-tests skill.
7
+ #
8
+ # REQUIRED EDIT: set `workflows:` below to the exact `name:` of your test
9
+ # workflow, and make that workflow upload its test reports / coverage signal as
10
+ # an artifact named `test-reports` (or change PIPE_TEST_ARTIFACT here).
11
+ #
12
+ # Alternative (simpler) wiring: instead of this workflow, add a test-fix JOB to
13
+ # your own test workflow with `needs: [test]` and `if: ${{ failure() }}`, then
14
+ # run the same script. Use that if you prefer everything in one workflow. See the
15
+ # github/README.md note. This workflow_run version keeps the ci-agent-platform
16
+ # repository's CI out of your test workflow file entirely.
17
+ #
18
+ # Limitation: github.event.workflow_run.pull_requests is only populated for
19
+ # same-repo PRs. Fork PRs will be skipped (this is a GitHub security boundary).
20
+ # =============================================================================
21
+
22
+ name: claude-test-fix
23
+
24
+ on:
25
+ workflow_run:
26
+ workflows: ["CI"] # <-- set to your test workflow's name
27
+ types: [completed]
28
+
29
+ permissions:
30
+ contents: write
31
+ pull-requests: write
32
+ actions: read
33
+
34
+ env:
35
+ PIPE_SCRIPTS_DIR: ".claude-pipeline/scripts"
36
+ PIPE_PLATFORM: "github"
37
+ PIPE_CONFIG_PATH: ".claude/pipeline-config.md"
38
+ PIPE_CONTEXT_DIR: "build/pipeline"
39
+ PIPE_TARGET_BRANCH: ${{ vars.PIPE_TARGET_BRANCH || github.event.repository.default_branch || 'main' }}
40
+ PIPE_LABEL_WIP: ${{ vars.PIPE_LABEL_WIP || 'pipe-wip' }}
41
+ PIPE_LABEL_STUCK: ${{ vars.PIPE_LABEL_STUCK || 'pipe-stuck' }}
42
+ PIPE_BOT_USER: ${{ vars.PIPE_BOT_USER || '' }}
43
+ PIPE_VERIFY_CMD: ${{ vars.PIPE_VERIFY_CMD || '' }}
44
+ PIPE_COMPILE_CMD: ${{ vars.PIPE_COMPILE_CMD || '' }}
45
+ PIPE_TEST_REPORT_GLOB: ${{ vars.PIPE_TEST_REPORT_GLOB || '' }}
46
+ PIPE_COVERAGE_SIGNAL: ${{ vars.PIPE_COVERAGE_SIGNAL || '' }}
47
+ PIPE_FIX_LOOP_CAP: ${{ vars.PIPE_FIX_LOOP_CAP || '2' }}
48
+ PIPE_COMMIT_TESTFIX: ${{ vars.PIPE_COMMIT_TESTFIX || 'Fix test errors' }}
49
+ PIPE_COMMIT_COVERAGE: ${{ vars.PIPE_COMMIT_COVERAGE || 'Add coverage tests' }}
50
+ PIPE_GIT_NAME: ${{ vars.PIPE_GIT_NAME || 'Pipeline Bot' }}
51
+ PIPE_GIT_EMAIL: ${{ vars.PIPE_GIT_EMAIL || 'bot@pipeline.ci' }}
52
+ PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'sonnet' }}
53
+ PIPE_AGENT_ENV_ALLOWLIST: ${{ vars.PIPE_AGENT_ENV_ALLOWLIST || '' }}
54
+ PIPE_TEST_ARTIFACT: ${{ vars.PIPE_TEST_ARTIFACT || 'test-reports' }}
55
+ PIPE_REPO: ${{ github.repository }}
56
+ PIPE_PROJECT_PATH: ${{ github.repository }}
57
+ PIPE_SERVER_HOST: "github.com"
58
+ PIPE_SOURCE_BRANCH: ${{ github.event.workflow_run.head_branch }}
59
+ PIPE_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
60
+
61
+ jobs:
62
+ test-fix:
63
+ if: >-
64
+ github.event.workflow_run.conclusion == 'failure' &&
65
+ github.event.workflow_run.event == 'pull_request' &&
66
+ github.event.workflow_run.pull_requests[0] != null
67
+ runs-on: ubuntu-latest
68
+ # Hard wall-clock cap on API spend if a claude session hangs or loops.
69
+ timeout-minutes: 20
70
+ steps:
71
+ - name: Resolve the pull request
72
+ id: pr
73
+ env:
74
+ GH_TOKEN: ${{ secrets.PIPE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
75
+ run: |
76
+ echo "number=${{ github.event.workflow_run.pull_requests[0].number }}" >> "$GITHUB_OUTPUT"
77
+ - uses: actions/checkout@v4
78
+ with:
79
+ ref: ${{ github.event.workflow_run.head_branch }}
80
+ fetch-depth: 0
81
+ token: ${{ secrets.PIPE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
82
+ - name: Download test artifacts from the failed run
83
+ uses: actions/download-artifact@v4
84
+ with:
85
+ run-id: ${{ github.event.workflow_run.id }}
86
+ github-token: ${{ secrets.PIPE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
87
+ name: ${{ env.PIPE_TEST_ARTIFACT }}
88
+ path: .
89
+ continue-on-error: true
90
+ - name: Install claude-code
91
+ run: command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code
92
+ - name: Run test-fix
93
+ env:
94
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
95
+ GH_TOKEN: ${{ secrets.PIPE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
96
+ PIPE_MR_IID: ${{ steps.pr.outputs.number }}
97
+ run: bash "$PIPE_SCRIPTS_DIR/test-fix.sh"
98
+ - name: Upload metrics
99
+ if: always()
100
+ uses: actions/upload-artifact@v4
101
+ with:
102
+ name: test-fix-metrics
103
+ path: ${{ env.PIPE_CONTEXT_DIR }}/metrics/
104
+ if-no-files-found: ignore
@@ -0,0 +1,114 @@
1
+ #!/bin/bash
2
+ # Runner for the `code` job (issue -> code loop, coder side). Triggered by the
3
+ # orchestrator with CODER_ISSUE + CODER_BRANCH. Checks out the feature branch,
4
+ # runs the /implement-issue skill (which delegates to the coder agent and drafts
5
+ # the MR/PR body), then pushes and opens the MR/PR, or handles the no-commits
6
+ # case. All reasoning is in the skill; this script does token and git work.
7
+ #
8
+ # Generalized from a single-project original: the delegation prompt and the
9
+ # summary generation moved into the skill, platform calls go through
10
+ # lib/issue-loop.sh, and the coder runs non-root via pipe_run_claude.
11
+ #
12
+ # SECURITY NOTE: the coder agent runs with --dangerously-skip-permissions (it
13
+ # must edit files and run the build unattended). This is a deliberate
14
+ # choice for trusted inputs. Run only on an isolated ephemeral runner and, where
15
+ # the image allows, as an unprivileged user (see pipe_run_claude). This does not
16
+ # make arbitrary public issue or contributor content safe; see the README trust
17
+ # boundary. Do not run this job on a shared or privileged machine.
18
+ set -e
19
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
20
+ # shellcheck source=lib/pipeline-common.sh
21
+ source "$SCRIPT_DIR/lib/pipeline-common.sh"
22
+ # shellcheck source=lib/issue-loop.sh
23
+ source "$SCRIPT_DIR/lib/issue-loop.sh"
24
+ pipe_defaults
25
+
26
+ # --- 1. Validate inputs -----------------------------------------------------
27
+ [[ "${CODER_ISSUE:-}" =~ ^[0-9]+$ ]] || { echo "ERROR: CODER_ISSUE is not a valid issue number"; exit 1; }
28
+ [[ "${CODER_BRANCH:-}" =~ ^[a-z0-9][a-z0-9-]*$ ]] || { echo "ERROR: CODER_BRANCH contains invalid characters"; exit 1; }
29
+ export PIPE_ISSUE_IID="$CODER_ISSUE"
30
+
31
+ # --- 2. Git identity and feature branch -------------------------------------
32
+ pipe_git_identity
33
+ git fetch origin "$PIPE_TARGET_BRANCH"
34
+ git checkout -B "$CODER_BRANCH" "origin/$PIPE_TARGET_BRANCH"
35
+
36
+ # --- 3. Fetch the issue and build the coder input ---------------------------
37
+ META=$(issue_meta "$CODER_ISSUE")
38
+ ISSUE_TITLE=$(echo "$META" | jq -r '.title // ""')
39
+ ISSUE_DESC=$(echo "$META" | jq -r '.description // "(no description)"')
40
+ ISSUE_LABELS=$(echo "$META" | jq -r '.labels // ""')
41
+ pipe_log "Implementing #$CODER_ISSUE: $ISSUE_TITLE"
42
+
43
+ {
44
+ echo "# Issue #$CODER_ISSUE"
45
+ echo
46
+ echo "Title: $ISSUE_TITLE"
47
+ echo
48
+ echo "## Description"
49
+ echo "$ISSUE_DESC"
50
+ } > "$PIPE_CONTEXT_DIR/coder-input.md"
51
+
52
+ # --- 4. Run the implement-issue skill (delegates to the coder agent) --------
53
+ rm -f "$PIPE_CONTEXT_DIR/implement.env" "$PIPE_CONTEXT_DIR/mr-summary.md"
54
+ pipe_run_claude coder "/implement-issue" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_CODE" < /dev/null
55
+
56
+ # --- 5. Authoritative commit count (the runner owns the push decision) -------
57
+ COMMITS=$(git rev-list "origin/$PIPE_TARGET_BRANCH..HEAD" --count 2>/dev/null || echo 0)
58
+
59
+ # --- 6. No commits: comment on the issue and mark it stuck ------------------
60
+ # issue_implemented is emitted only when commits exist (E2E finding N-7:
61
+ # emitting it before this check produced the metric for stuck runs too).
62
+ if [ "${COMMITS:-0}" = "0" ]; then
63
+ pipe_log "Coder made no commits, marking #$CODER_ISSUE stuck"
64
+ pipe_metric_event coder issue_stuck \
65
+ "$(jq -n --argjson i "$CODER_ISSUE" '{issue_iid: $i, reason: "no_commits"}')"
66
+ # The agent's own account of why it stopped is the most useful thing in a
67
+ # stuck comment, and it was being read and then dropped.
68
+ EXPL=$(head -c 3000 "$PIPE_CONTEXT_DIR/implement.env" 2>/dev/null || true)
69
+ issue_comment "$CODER_ISSUE" "🤖 **Coder agent made no commits for issue #$CODER_ISSUE.**
70
+
71
+ The implementation agent ran but produced no commits. A human needs to take over.
72
+ ${EXPL:+
73
+ What the agent reported before stopping:
74
+
75
+ \`\`\`
76
+ $EXPL
77
+ \`\`\`
78
+ }
79
+ ${PIPE_JOB_URL:+CI job: $PIPE_JOB_URL}"
80
+ issue_set_labels "$CODER_ISSUE" "$PIPE_LABEL_STUCK" "$PIPE_LABEL_READY,$PIPE_LABEL_WIP"
81
+ exit 0
82
+ fi
83
+
84
+ pipe_metric_event coder issue_implemented \
85
+ "$(jq -n --argjson i "$CODER_ISSUE" --argjson c "${COMMITS:-0}" '{issue_iid: $i, commits: $c}')"
86
+
87
+ # --- 7. Push the branch -----------------------------------------------------
88
+ git push -u origin "$CODER_BRANCH"
89
+
90
+ # --- 8. Open the MR/PR ------------------------------------------------------
91
+ SUMMARY=$(head -c 6000 "$PIPE_CONTEXT_DIR/mr-summary.md" 2>/dev/null || true)
92
+ [ -z "$SUMMARY" ] && SUMMARY="Implemented by the ci-agent-platform coder agent."
93
+
94
+ # Inherit the issue labels onto the MR/PR: drop the ready label, add wip.
95
+ MR_LABELS=$( { echo "$ISSUE_LABELS" | tr ',' '\n' | grep -vxF "$PIPE_LABEL_READY" | grep -v '^$'; echo "$PIPE_LABEL_WIP"; } \
96
+ | awk '!seen[$0]++' | paste -sd, - )
97
+
98
+ MR_TITLE="Resolve \"$ISSUE_TITLE\""
99
+ MR_DESC="Closes #$CODER_ISSUE
100
+
101
+ $SUMMARY
102
+
103
+ ---
104
+ 🤖 Implemented by the ci-agent-platform coder"
105
+
106
+ # Ensure the MR/PR labels exist (repo/project may not have them yet).
107
+ issue_ensure_label "$PIPE_LABEL_WIP" "#3498DB" "Pipeline is working on this change"
108
+
109
+ MR_URL=$(mr_create "$CODER_BRANCH" "$PIPE_TARGET_BRANCH" "$MR_TITLE" "$MR_DESC" "$MR_LABELS")
110
+ if [ -n "$MR_URL" ]; then
111
+ pipe_log "Opened MR/PR: $MR_URL"
112
+ else
113
+ pipe_log "WARNING: MR/PR creation returned no URL (it may already exist for this branch)"
114
+ fi