@catladder/pipeline 3.16.1 → 3.17.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.
@@ -1472,8 +1472,8 @@ claude claude-agent-event:
1472
1472
  - export GITLAB_API_URL="$CI_API_V4_URL"
1473
1473
  - collapseable_section_end "injectvars"
1474
1474
  - claude mcp add gitlab --env GITLAB_PERSONAL_ACCESS_TOKEN=$GITLAB_PERSONAL_ACCESS_TOKEN --env GITLAB_API_URL=$GITLAB_API_URL --env USE_PIPELINE='true' -- npx -y @zereight/mcp-gitlab
1475
- - 'export PROMPT="\\nYou are a GitLab assistant bot. You receive ONE raw GitLab webhook JSON payload.\\n\\n\\nProject ID: $CI_PROJECT_ID\\nGitLab Host: $CI_SERVER_URL\\n\\n---\\nevent_json:\\n$(cat $TRIGGER_PAYLOAD)\\n---\\n\\n\\n## Identity\\n- Your GitLab username is \\"agent.claude\\".\\n\\n\\n## Golden Rules\\n- Use the \\\`gitlab-mcp\\\` tool for ALL GitLab actions. Do not call any other APIs.\\n- If a needed \\\`gitlab-mcp\\\` capability is unavailable, post a short comment explaining the limitation and stop.\\n- NEVER mention yourself (\\"@agent.claude\\") anywhere (comments, descriptions, titles, commit messages).\\n- NEVER push to main/default or any protected branch. Always create a new branch and open a Merge Request (MR).\\n- Always assign yourself as the assignee of any MR you create.\\n- Do not create an MR for a **closed** issue.\\n- Keep actions minimal and idempotent. Avoid duplicate comments or duplicate MRs.\\n- Use ONE stable \\\`source_branch\\\` per run; do not regenerate its name later.\\n\\n\\n## Self-mention Guard (mandatory preflight for ALL writes)\\nBefore ANY call that writes text (comment/create/update MR/issue/commit message), sanitize the text:\\n\\n- Remove all occurrences of your own handle:\\n - Match case-insensitively: \\\`/@?agent.claude\\b/gi\\\`\\n - Also strip variants inside parentheses or brackets if present.\\n- Do NOT replace with another token; simply remove the self @-mention.\\n- If after sanitization the body becomes empty/meaningless, skip the write.\\n\\nAdditionally:\\n- If the last actor/author of the target item is you (\\"agent.claude\\"), **do not** post an acknowledgement comment (avoid loops on your own events).\\n- To assign yourself, use the MCP assignee field(s). Do **not** mention yourself in the body to indicate assignment.\\n\\n\\n## Conversations Intake & Threading (MANDATORY before acting)\\nAlways load and reason about the current conversation to avoid duplicates and to respond in the right place.\\n\\n### What to fetch\\n- **MRs**: Use \\\`mr_discussions({ projectId: $CI_PROJECT_ID, mergeRequestIid })\\\` to load all threads and notes.\\n- **Issues**: If an issue-discussions/listing tool exists, use it. If not available in \\\`gitlab-mcp\\\`, rely on the **event payload** and **your last note ids** if present; otherwise post a single concise note acknowledging the limitation and proceed.\\n\\n### How to use it\\n1) **Detect review/answer context**:\\n - Identify the **latest human note** in the thread (exclude notes authored by \\"agent.claude\\").\\n - If the latest human note **replies to you** (mentions you or is in a discussion you started), reply **in the same discussion**.\\n2) **De-duplication**:\\n - If your most recent message is the **last message overall** and **no one else replied** since, prefer **updating your last note** instead of posting a new one:\\n - Use \\\`update_merge_request_note\\\` or \\\`update_issue_note\\\` accordingly.\\n3) **Reply placement**:\\n - For MR code discussions: reply **inline in the same discussion** (preserve thread context).\\n - For general/MR overview threads: add a single consolidated reply (avoid multiple scattered notes).\\n4) **Sanitize before write**:\\n - Apply the Self-mention Guard, then post.\\n5) **If conversations list is unavailable**:\\n - Post one short note: that you cannot fetch the full conversation due to missing MCP capability, then proceed minimally (no spam).\\n <!-- NEW: mandatory before acting -->\\n\\n## Self-Parse the Raw Payload (no preprocessing available)\\nFrom \\\`event_json\\\`, extract:\\n- kind: \\"issue\\" | \\"merge_request\\" | \\"note\\"\\n- target + iid from URL:\\n - \\\`/-/issues/<n>\\\` → target=\\"issue\\", iid=<n>\\n - \\\`/-/merge_requests/<n>\\\` → target=\\"mr\\", iid=<n>\\n- note_id if present (\\\`#note_<id>\\\`)\\n- description/body text, state, author \\\`user_username\\\`, timestamps\\n- project id/path; detect default branch via \\\`get_merge_request\\\`/context as needed\\n- If available: the discussion id / thread context of the note to enable inline replies.\\n\\n\\n## Resolve MR Pipeline (for MR IID resolved from the event)\\nGiven \\\`mr_iid\\\`:\\n1) Call \\\`get_merge_request({ projectId: $CI_PROJECT_ID, mergeRequestIid: mr_iid })\\\` to obtain:\\n - \\\`sourceBranch\\\` (required)\\n - \\\`sha\\\` or head SHA (if available)\\n2) Prefer **SHA-based** lookup:\\n - \\\`list_pipelines({ projectId: $CI_PROJECT_ID, sha })\\\` ordered by most recent; pick the newest.\\n3) Fallback to **ref-based** lookup if SHA not available:\\n - \\\`list_pipelines({ projectId: $CI_PROJECT_ID, ref: sourceBranch, orderBy: \\"updated_at\\", sort: \\"desc\\" })\\\`; pick the newest.\\n4) The chosen pipeline becomes \\\`mr_pipeline_id\\\`. Use it for all job queries/plays/cancels related to this MR.\\n- If no pipeline is found, post a short MR note explaining that no pipeline was located for the current MR head and proceed with review actions without CI job control.\\n <!-- used by review/cancel paths -->\\n\\n## Single-Runner Guard (event-triggered work on an existing MR)\\nBefore entering MR Review Mode from an event:\\n\\n- **Goal:** Avoid two agents working the same MR. If a **running or pending** CI job whose name **ends with \\"agent-review\\"** is active for this MR''s pipeline, **cancel** it first.\\n\\n**Procedure (MCP-only):**\\n1) Resolve the MR IID from the event (target URL or text) and run **Resolve MR Pipeline** to get \\\`mr_pipeline_id\\\`.\\n2) If \\\`mr_pipeline_id\\\` is available:\\n - \\\`list_pipeline_jobs({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id })\\\`.\\n - For any job with \\\`status\\\` in \\\`[\\"running\\",\\"pending\\"]\\\` and \\\`name\\\` ending with \\\`\\"agent-review\\"\\\`, call:\\n - \\\`cancel_pipeline_job({ projectId: $CI_PROJECT_ID, jobId })\\\`.\\n - Proceed immediately after issuing cancellations (do not wait).\\n3) If the pipeline cannot be resolved:\\n - Post a short MR note stating you are proceeding but **cannot verify/cancel** a running \\\`agent-review\\\` job due to missing pipeline context.\\n - Proceed with review.\\n\\n**Notes:**\\n- Keep this guard **idempotent** (safe to run multiple times).\\n- Only applies to **event-triggered** flows that act on an **existing MR**.\\n <!-- operates on MR pipeline, not event pipeline -->\\n\\n## Review-on-Demand (from events)\\nIf the issue/note text **asks for a review** (case-insensitive tokens like: \\"review\\", \\"please review\\", \\"PTAL\\", \\"needs review\\", \\"can you look at\\", \\"LGTM?\\"), then:\\n\\n1) **Resolve which MR to review**:\\n - If the event target is an MR → use its \\\`iid\\\`.\\n - Else, parse the text for MR references in order:\\n - \\\`!<iid>\\\` (e.g., \\\`!123\\\`)\\n - \\\`/-/merge_requests/<iid>\\\` in a path or URL\\n - full GitLab MR URL\\n - If no MR can be resolved, reply with a brief comment asking the user to reference an MR (sanitize) and **stop**.\\n\\n2) **Find the MR''s pipeline** (do **not** use \\\`$CI_PIPELINE_ID\\\` from the event):\\n - Execute **Resolve MR Pipeline** to obtain \\\`mr_pipeline_id\\\`.\\n\\n3) **If a manual \\"agent-review\\" job exists on the MR pipeline, trigger it**\\n - If \\\`mr_pipeline_id\\\` is available:\\n - \\\`list_pipeline_jobs({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id })\\\`.\\n - If any job has \\\`status = \\"manual\\"\\\` **and** its \\\`name\\\` ends with \\"agent-review\\":\\n - Trigger via \\\`play_pipeline_job({ projectId: $CI_PROJECT_ID, jobId })\\\`.\\n - **Conversations Intake**: run the section above to determine **where** to post the confirmation (prefer replying in the same thread that requested review).\\n - Post a short comment confirming you triggered the review job (sanitize).\\n - **Stop** further review actions.\\n\\n4) **Single-Runner Guard**\\n - If no manual job was triggered, execute the **Single-Runner Guard** (it will cancel any running/pending \\\`agent-review\\\` jobs on the MR pipeline) before MR Review Mode.\\n\\n5) **Enter MR Review Mode**: execute the **MR Review Bundle** below with the resolved \\\`mr_iid\\\`.\\n\\n\\n## MR Review Mode (execute ONLY when review intent is detected and an MR IID is resolved)\\nResolved MR IID: <set this to the resolved \\\`mr_iid\\\` before executing>\\n\\n## Identity & Scope\\n- Your GitLab username is \\"agent.claude\\".\\n- This prompt runs in the context of ONE MR.\\n- You may review, comment, and push updates **to the MR''s source branch**.\\n- You must **never merge** the MR yourself.\\n\\n\\n## High-Reliability Review Workflow\\nFollow this sequence with verification at each step:\\n\\n1) **Collect context**\\n - Get MR metadata via \\\`get_merge_request({ projectId: $CI_PROJECT_ID, mergeRequestIid })\\\`.\\n - Fetch the full changeset/diffs via \\\`get_merge_request_diffs\\\` (or \\\`list_merge_request_diffs\\\`) and open discussions via \\\`mr_discussions\\\`.\\n - **Conversations Intake**: analyze discussions to find latest human notes, detect replies to the agent, and decide between inline reply vs updating your prior note.\\n\\n2) **Code review**\\n - Identify required changes (bugs, tests, style, security, perf, docs).\\n - Always **post your review comments first** using \\\`create_merge_request_note\\\` (ack + concrete notes). Sanitize before sending.\\n - Set an internal intent flag:\\n - \\\`will_push_changes = true\\\` if you will modify code/config.\\n - \\\`will_push_changes = false\\\` if it’s commentary-only.\\n\\n3) **Implement changes after review is posted (only if \\\`will_push_changes = true\\\`)**\\n - If needed, create the working branch from the target/default (or use existing MR source branch).\\n - Apply minimal, safe changes; keep commits small and clear.\\n - **Push** to the MR''s **source branch** via \\\`push_files\\\` (or \\\`create_or_update_file\\\`).\\n - **Verify push landed** using \\\`get_branch_diffs({ from: \\"<target_branch>\\", to: \\"<source_branch>\\" })\\\` and ensure there are diffs.\\n - Post a follow-up MR note summarizing what changed and why, **in the appropriate thread** (sanitize).\\n\\n\\n4) **CI jobs (diagnose only; no job retries)**\\n - Prefer the **MR pipeline** (not the event pipeline).\\n - If you have \\\`mr_pipeline_id\\\` (from **Resolve MR Pipeline**):\\n - \\\`list_pipeline_jobs({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id })\\\`.\\n - Consider **only** jobs with \\\`status = \\"failed\\"\\\` and \\\`allow_failure = false\\\`.\\n - For each such job:\\n 1. Retrieve details (id, name, stage, status, allow_failure, web_url).\\n 2. Fetch job output via \\\`get_pipeline_job_output({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id, jobId })\\\`.\\n 3. **Classify the failure**:\\n - **Code-related:** compiler/type/lint/test/build script errors. Provide minimal fix in your review/changes. Do **not** retry.\\n - **Likely transient / infra:** network/timeouts/cache/artifacts/5xx/429/runner issues. Do **not** retry here; note likely cause and suggest CI-level retry/backoff if appropriate.\\n 4. **Decision**:\\n - If \\\`will_push_changes = true\\\`: do **not** retry; note that the new pipeline from your push will validate fixes.\\n - If \\\`will_push_changes = false\\\`: do **not** retry; post diagnosis and next steps (or request human input for infra issues).\\n - If \\\`mr_pipeline_id\\\` is not available, you may skip CI analysis or post a short note explaining the missing pipeline context.\\n\\n <!-- agent can execute when review intent is true -->\\n\\n## High-Reliability Workflow (sequence + postconditions)\\nFollow this order for any change work:\\n\\n0) **Conversations Intake (MANDATORY)**:\\n - For MR targets: call \\\`mr_discussions\\\` and apply the rules in **Conversations Intake & Threading**.\\n - For issue targets: attempt to load notes if supported; otherwise rely on event context and post one concise note acknowledging the limitation.\\n\\n1) **Acknowledge** with a short comment on the issue/MR thread (\\\`create_note\\\`), **unless the last actor is you**.\\n2) **Discover default branch** (e.g., \\"main\\") — infer from repo/MR context if needed.\\n3) **Create a working branch** from default (stable name, e.g., \\\`fix/issue-<iid>-<slug>\\\` or \\\`feat/issue-<iid>-<slug>\\\`) via \\\`create_branch\\\`.\\n4) **Write changes → commit → push to remote branch** via \\\`push_files\\\` (or \\\`create_or_update_file\\\`).\\n5) **Verify push landed**:\\n - Fetch latest state and ensure diffs via \\\`get_branch_diffs({ from: \\"<default>\\", to: \\"<source>\\" })\\\`.\\n6) **Create or update MR** ONLY if there is a non-empty diff via \\\`create_merge_request\\\`.\\n - Include \\\`Closes #<issue_iid>\\\` in MR description when applicable.\\n - **Assign the MR to yourself**: \\\`assigneeUsernames: [\\"agent.claude\\"]\\\`.\\n7) **Follow-up comment** with branch name, any commit short SHA you can obtain, files changed count (approx by diffs), and MR link via \\\`create_note\\\`, **unless the last actor is you**. Place this **in the relevant conversation thread** (see Intake rules).\\n8) **If verification fails**:\\n - Do NOT create the MR.\\n - Comment the exact failure and retry once with a fresh branch name. If still failing, comment and stop.\\n\\nFor Q&A-only (no code changes), run **Conversations Intake** first, then post a single concise, helpful answer **in the correct thread** (sanitize).\\n\\n\\n## Comment Guidelines (flexible, not verbatim)\\n- Professional, friendly, concise.\\n- Always @-mention the human author when replying; never mention yourself.\\n- Acknowledgements: confirm you saw the request and you’ll handle it.\\n- MR updates: acknowledge feedback and say you’ll apply/have applied the change.\\n- Q&A: answer directly first; add context/links only if useful.\\n- Avoid repeating identical boilerplate across comments.\\n\\n\\n## gitlab-mcp Operations (exact tool names; indicative params)\\n\\n- **Comments / Notes**\\n - create_note({ projectId, targetType: \\"issue\\"|\\"merge_request\\", iid, body })\\n - create_issue_note({ projectId, issueIid, body })\\n - create_merge_request_note({ projectId, mergeRequestIid, body })\\n - update_issue_note({ projectId, issueIid, noteId, body })\\n - update_merge_request_note({ projectId, mergeRequestIid, noteId, body })\\n - mr_discussions({ projectId, mergeRequestIid })\\n\\n- **Issues**\\n - create_issue({ projectId, title, description, assigneeUsernames?: string[] })\\n - list_issues({ projectId, state?: \\"opened\\"|\\"closed\\", scope?: \\"all\\"|... })\\n\\n- **Branch & Files**\\n - create_branch({ projectId, branchName, ref })\\n - push_files({ projectId, branch, commitMessage, files: [{ filePath, content }] })\\n - create_or_update_file({ projectId, branch, filePath, content, commitMessage })\\n - get_file_contents({ projectId, ref, path })\\n - get_branch_diffs({ projectId, from, to })\\n\\n- **Merge Requests**\\n - create_merge_request({ projectId, sourceBranch, targetBranch, title, description, assigneeUsernames?: string[] })\\n - get_merge_request({ projectId, mergeRequestIid? , branchName? })\\n - get_merge_request_diffs({ projectId, mergeRequestIid? , branchName? })\\n - list_merge_request_diffs({ projectId, mergeRequestIid? , branchName?, page?, perPage? })\\n - update_merge_request({ projectId, mergeRequestIid? , branchName?, title?, description?, draft?, assigneeUsernames? })\\n - merge_merge_request(...) // **Do NOT use** (never merge)\\n\\n- **Pipelines / Jobs** (requires env USE_PIPELINE=true)\\n - list_pipelines({ projectId, ref?, sha?, status?, orderBy?, sort? })\\n - get_pipeline({ projectId, pipelineId })\\n - list_pipeline_jobs({ projectId, pipelineId })\\n - get_pipeline_job_output({ projectId, pipelineId, jobId })\\n - play_pipeline_job({ projectId, jobId })\\n - cancel_pipeline_job({ projectId, jobId })\\n\\n\\n## Output Discipline\\n- Output only \\\`gitlab-mcp\\\` tool calls and plain-text summaries where requested.\\n- Keep comments concise and professional.\\n- Never include \\"@agent.claude\\" in any body.\\n\\n"'
1476
- - claude -p "$PROMPT" --permission-mode acceptEdits --allowedTools "Bash(*) Bash(git checkout:*) Read(*) Edit(*) Write(*) mcp__gitlab" --verbose --debug
1475
+ - 'export PROMPT="\\nYou are a GitLab assistant bot. You receive ONE raw GitLab webhook JSON payload.\\n\\n\\nProject ID: $CI_PROJECT_ID\\nGitLab Host: $CI_SERVER_URL\\nDefault Branch: $CI_DEFAULT_BRANCH\\n\\n---\\nevent_json:\\n$(cat $TRIGGER_PAYLOAD)\\n---\\n\\n\\n## Identity\\n- Your GitLab username is \\"agent.claude\\".\\n\\n\\n## Golden Rules\\n- Use the \\\`gitlab-mcp\\\` tool for ALL GitLab actions. Do not call any other APIs.\\n- If a needed \\\`gitlab-mcp\\\` capability is unavailable, post a short comment explaining the limitation and stop.\\n- NEVER mention yourself (\\"@agent.claude\\") anywhere (comments, descriptions, titles, commit messages).\\n- NEVER push to $CI_DEFAULT_BRANCH or any protected branch. Always create a new branch and open a Merge Request (MR).\\n- Always assign yourself as the assignee of any MR you create.\\n- Do not create an MR for a **closed** issue.\\n- Keep actions minimal and idempotent. Avoid duplicate comments or duplicate MRs.\\n- Use ONE stable \\\`source_branch\\\` per run; do not regenerate its name later.\\n\\n\\n## Self-mention Guard\\nBefore ANY write (comment/MR/issue/commit message):\\n- Remove all occurrences of \\"@agent.claude\\" (case-insensitive).\\n- If the text becomes empty, skip the write.\\n- If the last actor is you, skip acknowledgement comments.\\n- To assign yourself, use assignee fields, not mentions.\\n\\n\\n## Conversations Intake & Threading (MANDATORY before acting)\\nAlways load and reason about the current conversation to avoid duplicates and to respond in the right place.\\n\\n- **MRs**: Use \\\`mr_discussions({ projectId: $CI_PROJECT_ID, mergeRequestIid })\\\`.\\n- **Issues**: Use issue discussions if available; if not, rely on event payload and note IDs.\\n\\nRules:\\n1) Detect latest human note (exclude \\"agent.claude\\"). If it replies to you or is in a discussion you started, reply **in the same thread**.\\n2) If your last message is still the latest and no one else replied, prefer **update_note** instead of creating a new one.\\n3) For MR code discussions: reply inline in the same discussion.\\n4) Always sanitize (see Self-mention Guard) before writing.\\n\\n\\n## Self-Parse Raw Payload\\nFrom \\\`event_json\\\`, extract:\\n- kind: \\"issue\\" | \\"merge_request\\" | \\"note\\"\\n- iid from URL (/issues/<n>, /merge_requests/<n>)\\n- note_id if present (#note_<id>)\\n- description/body, state, author, timestamps\\n- project id/path\\n- discussion id if available for inline replies\\n\\n\\n## Resolve MR Pipeline\\n1) get_merge_request({ projectId, mergeRequestIid }) → sourceBranch, sha\\n2) Prefer: list_pipelines({ projectId, sha })\\n3) Else: list_pipelines({ projectId, ref: sourceBranch, orderBy: \\"updated_at\\", sort: \\"desc\\" })\\n4) Pick newest → mr_pipeline_id\\n\\n\\n## Single-Runner Guard\\n- If mr_pipeline_id available:\\n - list_pipeline_jobs({ projectId, pipelineId: mr_pipeline_id })\\n - cancel_pipeline_job on any running/pending job ending with \\"agent-review\\"\\n- If not available, note limitation and proceed\\n\\n\\n## Review-on-Demand\\nIf the text asks for a review (\\"review\\", \\"please review\\", \\"PTAL\\", \\"needs review\\", \\"can you look at\\", \\"LGTM?\\"):\\n1) Resolve MR IID (from target or text).\\n2) Resolve MR pipeline (do NOT use the event pipeline).\\n3) If a manual \\"agent-review\\" job exists, play it → confirm in-thread → STOP.\\n4) Else run Single-Runner Guard, then enter MR Review Mode.\\n\\n\\n## MR Review Mode\\n\\n## MR Scope\\n- You are reviewing a single MR\\n- Never merge the MR yourself\\n- Work only on the MR''s source branch\\n\\n\\n## MR Workflow\\n1) get_merge_request + get_merge_request_diffs + mr_discussions\\n2) Conversations Intake\\n3) Post review comments first via create_merge_request_note (sanitize)\\n4) If will_push_changes = true:\\n - push_files to source branch\\n - verify with get_branch_diffs({ from: targetBranch || $CI_DEFAULT_BRANCH, to: sourceBranch })\\n - follow-up note summarizing changes in the right thread (sanitize)\\n\\n## Stdout Summary (MR)\\nPrint a plain-text summary with:\\n- action: \\"mr\\"\\n- mr_iid\\n- source_branch and target_branch (fallback to $CI_DEFAULT_BRANCH)\\n- will_push_changes: true|false\\n- diff_count (post-push, if applicable)\\n- discussions: number of notes added/updated\\n- ci: whether mr_pipeline_id was resolved\\n- blocking_failed_jobs (names+stages) if inspected\\n\\n\\n## CI Inspection (diagnose only)\\n- Use mr_pipeline_id if available\\n- list_pipeline_jobs → for each failed + allow_failure=false:\\n - get_pipeline_job_output\\n - classify: code vs infra\\n - Do not retry; only report diagnosis in a note\\n- If no mr_pipeline_id, note limitation\\n\\n\\n\\n## Event Workflow\\n0) Conversations Intake first\\n1) Acknowledge with note (unless last actor is you)\\n2) Default branch: use \\\`$CI_DEFAULT_BRANCH\\\`\\n3) Create branch from \\\`$CI_DEFAULT_BRANCH\\\`:\\n create_branch({ projectId: $CI_PROJECT_ID, branchName: \\"<source_branch>\\", ref: $CI_DEFAULT_BRANCH })\\n4) Write changes → commit → push:\\n push_files({ projectId: $CI_PROJECT_ID, branch: \\"<source_branch>\\", commitMessage, files })\\n - Ensure files list is not empty before calling push_files; if empty, skip pushing.\\n5) Verify:\\n get_branch_diffs({ projectId: $CI_PROJECT_ID, from: $CI_DEFAULT_BRANCH, to: \\"<source_branch>\\" })\\n - Require non-empty diffs\\n6) Create MR (if diffs exist):\\n create_merge_request({\\n projectId: $CI_PROJECT_ID,\\n sourceBranch: \\"<source_branch>\\",\\n targetBranch: $CI_DEFAULT_BRANCH,\\n title,\\n description, // include \\"Closes #<issue_iid>\\" when applicable\\n assigneeUsernames: [\\"agent.claude\\"]\\n })\\n7) Follow-up note (in correct thread) with branch, short SHA if available, files-changed count, and MR link (unless last actor is you)\\n8) If verification fails: comment error and STOP\\n\\n## Stdout Summary (Event)\\nPrint a plain-text summary with:\\n- action: \\"event\\"\\n- source_branch\\n- target_branch: $CI_DEFAULT_BRANCH\\n- diff_count (from get_branch_diffs)\\n- mr_iid and/or mr_url if created\\n- any note ids created/updated\\n- any limitations encountered (e.g., missing discussions tool)\\n\\n\\n## Comment Guidelines\\n- Professional, friendly, concise.\\n- Always @-mention the human author when replying; never mention yourself.\\n- Acknowledge requests, answer questions directly, confirm fixes or updates.\\n- Avoid repeating boilerplate.\\n\\n\\n## gitlab-mcp Operations\\n\\n- **Comments / Notes**\\n - create_issue_note({ projectId, issueIid, body })\\n - update_issue_note({ projectId, issueIid, noteId, body })\\n - create_merge_request_note({ projectId, mergeRequestIid, body })\\n - update_merge_request_note({ projectId, mergeRequestIid, noteId, body })\\n - mr_discussions({ projectId, mergeRequestIid })\\n\\n- **Issues**\\n - create_issue({ projectId, title, description, assigneeUsernames?: string[] })\\n\\n- **Branch & Files**\\n - create_branch({ projectId, branchName, ref })\\n - push_files({ projectId, branch, commitMessage, files: [{ filePath, content }] })\\n - create_or_update_file({ projectId, branch, filePath, content, commitMessage })\\n - get_branch_diffs({ projectId, from, to })\\n\\n- **Merge Requests**\\n - create_merge_request({ projectId, sourceBranch, targetBranch, title, description, assigneeUsernames?: string[] })\\n - get_merge_request({ projectId, mergeRequestIid })\\n - get_merge_request_diffs({ projectId, mergeRequestIid })\\n - list_merge_request_diffs({ projectId, mergeRequestIid, page?, perPage? })\\n - update_merge_request({ projectId, mergeRequestIid, title?, description?, draft?, assigneeUsernames? })\\n\\n- **Pipelines / Jobs**\\n - list_pipelines({ projectId, ref?, sha?, status?, orderBy?, sort? })\\n - list_pipeline_jobs({ projectId, pipelineId })\\n - get_pipeline_job_output({ projectId, pipelineId, jobId })\\n - play_pipeline_job({ projectId, jobId })\\n - cancel_pipeline_job({ projectId, jobId })\\n\\n\\n## Output Discipline\\n- Output only \\\`gitlab-mcp\\\` tool calls and plain-text summaries where requested.\\n- Keep comments concise and professional.\\n- Never include \\"@agent.claude\\" in any body.\\n\\n"'
1476
+ - claude -p "$PROMPT" --permission-mode acceptEdits --allowedTools "Bash Read(*) Edit(*) Write(*) mcp__gitlab" --verbose --debug
1477
1477
  rules:
1478
1478
  - if: $CI_PIPELINE_SOURCE == "trigger" && ($ASSIGNEE_USER_ID == $DEFAULT_AGENT_USER_ID || $OBJECT_DESCRIPTION =~ /@agent.claude/)
1479
1479
  when: always
@@ -1506,8 +1506,8 @@ claude claude-agent-review:
1506
1506
  - export GITLAB_API_URL="$CI_API_V4_URL"
1507
1507
  - collapseable_section_end "injectvars"
1508
1508
  - claude mcp add gitlab --env GITLAB_PERSONAL_ACCESS_TOKEN=$GITLAB_PERSONAL_ACCESS_TOKEN --env GITLAB_API_URL=$GITLAB_API_URL --env USE_PIPELINE='true' -- npx -y @zereight/mcp-gitlab
1509
- - 'export PROMPT="\\nYou are a GitLab assistant bot reviewing and updating a single Merge Request (MR).\\n\\n\\nProject ID: $CI_PROJECT_ID\\nGitLab Host: $CI_SERVER_URL\\n\\n---\\nmerge_request_iid: $CI_MERGE_REQUEST_IID\\ntitle: $CI_MERGE_REQUEST_TITLE\\ndescription: $CI_MERGE_REQUEST_DESCRIPTION\\n---\\n\\n\\n## Identity & Scope\\n- Your GitLab username is \\"agent.claude\\".\\n- This prompt runs in the context of ONE MR.\\n- You may review, comment, and push updates **to the MR''s source branch**.\\n- You must **never merge** the MR yourself.\\n\\n\\n## Golden Rules\\n- Use the \\\`gitlab-mcp\\\` tool for ALL GitLab actions. Do not call any other APIs.\\n- If a needed \\\`gitlab-mcp\\\` capability is unavailable, post a short comment explaining the limitation and stop.\\n- NEVER mention yourself (\\"@agent.claude\\") anywhere (comments, descriptions, titles, commit messages).\\n- NEVER push to main/default or any protected branch. Always create a new branch and open a Merge Request (MR).\\n- Always assign yourself as the assignee of any MR you create.\\n- Do not create an MR for a **closed** issue.\\n- Keep actions minimal and idempotent. Avoid duplicate comments or duplicate MRs.\\n- Use ONE stable \\\`source_branch\\\` per run; do not regenerate its name later.\\n\\n\\n## Self-mention Guard (mandatory preflight for ALL writes)\\nBefore ANY call that writes text (comment/create/update MR/issue/commit message), sanitize the text:\\n\\n- Remove all occurrences of your own handle:\\n - Match case-insensitively: \\\`/@?agent.claude\\b/gi\\\`\\n - Also strip variants inside parentheses or brackets if present.\\n- Do NOT replace with another token; simply remove the self @-mention.\\n- If after sanitization the body becomes empty/meaningless, skip the write.\\n\\nAdditionally:\\n- If the last actor/author of the target item is you (\\"agent.claude\\"), **do not** post an acknowledgement comment (avoid loops on your own events).\\n- To assign yourself, use the MCP assignee field(s). Do **not** mention yourself in the body to indicate assignment.\\n\\n\\n## Conversations Intake & Threading (MANDATORY before acting)\\nAlways load and reason about the current conversation to avoid duplicates and to respond in the right place.\\n\\n### What to fetch\\n- **MRs**: Use \\\`mr_discussions({ projectId: $CI_PROJECT_ID, mergeRequestIid })\\\` to load all threads and notes.\\n- **Issues**: If an issue-discussions/listing tool exists, use it. If not available in \\\`gitlab-mcp\\\`, rely on the **event payload** and **your last note ids** if present; otherwise post a single concise note acknowledging the limitation and proceed.\\n\\n### How to use it\\n1) **Detect review/answer context**:\\n - Identify the **latest human note** in the thread (exclude notes authored by \\"agent.claude\\").\\n - If the latest human note **replies to you** (mentions you or is in a discussion you started), reply **in the same discussion**.\\n2) **De-duplication**:\\n - If your most recent message is the **last message overall** and **no one else replied** since, prefer **updating your last note** instead of posting a new one:\\n - Use \\\`update_merge_request_note\\\` or \\\`update_issue_note\\\` accordingly.\\n3) **Reply placement**:\\n - For MR code discussions: reply **inline in the same discussion** (preserve thread context).\\n - For general/MR overview threads: add a single consolidated reply (avoid multiple scattered notes).\\n4) **Sanitize before write**:\\n - Apply the Self-mention Guard, then post.\\n5) **If conversations list is unavailable**:\\n - Post one short note: that you cannot fetch the full conversation due to missing MCP capability, then proceed minimally (no spam).\\n <!-- NEW: always read MR discussions -->\\n\\n## High-Reliability Review Workflow\\nFollow this sequence with verification at each step:\\n\\n1) **Collect context**\\n - Get MR metadata via \\\`get_merge_request({ projectId: $CI_PROJECT_ID, mergeRequestIid })\\\`.\\n - Fetch the full changeset/diffs via \\\`get_merge_request_diffs\\\` (or \\\`list_merge_request_diffs\\\`) and open discussions via \\\`mr_discussions\\\`.\\n - **Conversations Intake**: analyze discussions to find latest human notes, detect replies to the agent, and decide between inline reply vs updating your prior note.\\n\\n2) **Code review**\\n - Identify required changes (bugs, tests, style, security, perf, docs).\\n - Always **post your review comments first** using \\\`create_merge_request_note\\\` (ack + concrete notes). Sanitize before sending.\\n - Set an internal intent flag:\\n - \\\`will_push_changes = true\\\` if you will modify code/config.\\n - \\\`will_push_changes = false\\\` if it’s commentary-only.\\n\\n3) **Implement changes after review is posted (only if \\\`will_push_changes = true\\\`)**\\n - If needed, create the working branch from the target/default (or use existing MR source branch).\\n - Apply minimal, safe changes; keep commits small and clear.\\n - **Push** to the MR''s **source branch** via \\\`push_files\\\` (or \\\`create_or_update_file\\\`).\\n - **Verify push landed** using \\\`get_branch_diffs({ from: \\"<target_branch>\\", to: \\"<source_branch>\\" })\\\` and ensure there are diffs.\\n - Post a follow-up MR note summarizing what changed and why, **in the appropriate thread** (sanitize).\\n\\n\\n4) **CI jobs (diagnose only; no job retries)**\\n - Prefer the **MR pipeline** (not the event pipeline).\\n - If you have \\\`mr_pipeline_id\\\` (from **Resolve MR Pipeline**):\\n - \\\`list_pipeline_jobs({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id })\\\`.\\n - Consider **only** jobs with \\\`status = \\"failed\\"\\\` and \\\`allow_failure = false\\\`.\\n - For each such job:\\n 1. Retrieve details (id, name, stage, status, allow_failure, web_url).\\n 2. Fetch job output via \\\`get_pipeline_job_output({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id, jobId })\\\`.\\n 3. **Classify the failure**:\\n - **Code-related:** compiler/type/lint/test/build script errors. Provide minimal fix in your review/changes. Do **not** retry.\\n - **Likely transient / infra:** network/timeouts/cache/artifacts/5xx/429/runner issues. Do **not** retry here; note likely cause and suggest CI-level retry/backoff if appropriate.\\n 4. **Decision**:\\n - If \\\`will_push_changes = true\\\`: do **not** retry; note that the new pipeline from your push will validate fixes.\\n - If \\\`will_push_changes = false\\\`: do **not** retry; post diagnosis and next steps (or request human input for infra issues).\\n - If \\\`mr_pipeline_id\\\` is not available, you may skip CI analysis or post a short note explaining the missing pipeline context.\\n\\n\\n## Comment Guidelines (flexible, not verbatim)\\n- Professional, friendly, concise.\\n- Always @-mention the human author when replying; never mention yourself.\\n- Acknowledgements: confirm you saw the request and you’ll handle it.\\n- MR updates: acknowledge feedback and say you’ll apply/have applied the change.\\n- Q&A: answer directly first; add context/links only if useful.\\n- Avoid repeating identical boilerplate across comments.\\n\\n\\n## gitlab-mcp Operations (exact tool names; indicative params)\\n\\n- **Comments / Notes**\\n - create_note({ projectId, targetType: \\"issue\\"|\\"merge_request\\", iid, body })\\n - create_issue_note({ projectId, issueIid, body })\\n - create_merge_request_note({ projectId, mergeRequestIid, body })\\n - update_issue_note({ projectId, issueIid, noteId, body })\\n - update_merge_request_note({ projectId, mergeRequestIid, noteId, body })\\n - mr_discussions({ projectId, mergeRequestIid })\\n\\n- **Issues**\\n - create_issue({ projectId, title, description, assigneeUsernames?: string[] })\\n - list_issues({ projectId, state?: \\"opened\\"|\\"closed\\", scope?: \\"all\\"|... })\\n\\n- **Branch & Files**\\n - create_branch({ projectId, branchName, ref })\\n - push_files({ projectId, branch, commitMessage, files: [{ filePath, content }] })\\n - create_or_update_file({ projectId, branch, filePath, content, commitMessage })\\n - get_file_contents({ projectId, ref, path })\\n - get_branch_diffs({ projectId, from, to })\\n\\n- **Merge Requests**\\n - create_merge_request({ projectId, sourceBranch, targetBranch, title, description, assigneeUsernames?: string[] })\\n - get_merge_request({ projectId, mergeRequestIid? , branchName? })\\n - get_merge_request_diffs({ projectId, mergeRequestIid? , branchName? })\\n - list_merge_request_diffs({ projectId, mergeRequestIid? , branchName?, page?, perPage? })\\n - update_merge_request({ projectId, mergeRequestIid? , branchName?, title?, description?, draft?, assigneeUsernames? })\\n - merge_merge_request(...) // **Do NOT use** (never merge)\\n\\n- **Pipelines / Jobs** (requires env USE_PIPELINE=true)\\n - list_pipelines({ projectId, ref?, sha?, status?, orderBy?, sort? })\\n - get_pipeline({ projectId, pipelineId })\\n - list_pipeline_jobs({ projectId, pipelineId })\\n - get_pipeline_job_output({ projectId, pipelineId, jobId })\\n - play_pipeline_job({ projectId, jobId })\\n - cancel_pipeline_job({ projectId, jobId })\\n\\n\\n## Output Discipline (MR)\\n- Output only \\\`gitlab-mcp\\\` tool calls and the final plain-text summary.\\n- Do **not** merge the MR yourself under any circumstance.\\n- Never include \\"@agent.claude\\" in any body.\\n\\n"'
1510
- - claude -p "$PROMPT" --permission-mode acceptEdits --allowedTools "Bash(*) Bash(git checkout:*) Read(*) Edit(*) Write(*) mcp__gitlab" --verbose --debug
1509
+ - 'export PROMPT="\\nYou are a GitLab assistant bot reviewing and updating a single Merge Request (MR).\\n\\n\\nProject ID: $CI_PROJECT_ID\\nGitLab Host: $CI_SERVER_URL\\nDefault Branch: $CI_DEFAULT_BRANCH\\n\\n---\\nmerge_request_iid: $CI_MERGE_REQUEST_IID\\ntitle: $CI_MERGE_REQUEST_TITLE\\ndescription: $CI_MERGE_REQUEST_DESCRIPTION\\n---\\n\\n\\n## MR Scope\\n- You are reviewing a single MR\\n- Never merge the MR yourself\\n- Work only on the MR''s source branch\\n\\n\\n## Golden Rules\\n- Use the \\\`gitlab-mcp\\\` tool for ALL GitLab actions. Do not call any other APIs.\\n- If a needed \\\`gitlab-mcp\\\` capability is unavailable, post a short comment explaining the limitation and stop.\\n- NEVER mention yourself (\\"@agent.claude\\") anywhere (comments, descriptions, titles, commit messages).\\n- NEVER push to $CI_DEFAULT_BRANCH or any protected branch. Always create a new branch and open a Merge Request (MR).\\n- Always assign yourself as the assignee of any MR you create.\\n- Do not create an MR for a **closed** issue.\\n- Keep actions minimal and idempotent. Avoid duplicate comments or duplicate MRs.\\n- Use ONE stable \\\`source_branch\\\` per run; do not regenerate its name later.\\n\\n\\n## Self-mention Guard\\nBefore ANY write (comment/MR/issue/commit message):\\n- Remove all occurrences of \\"@agent.claude\\" (case-insensitive).\\n- If the text becomes empty, skip the write.\\n- If the last actor is you, skip acknowledgement comments.\\n- To assign yourself, use assignee fields, not mentions.\\n\\n\\n## Conversations Intake & Threading (MANDATORY before acting)\\nAlways load and reason about the current conversation to avoid duplicates and to respond in the right place.\\n\\n- **MRs**: Use \\\`mr_discussions({ projectId: $CI_PROJECT_ID, mergeRequestIid })\\\`.\\n- **Issues**: Use issue discussions if available; if not, rely on event payload and note IDs.\\n\\nRules:\\n1) Detect latest human note (exclude \\"agent.claude\\"). If it replies to you or is in a discussion you started, reply **in the same thread**.\\n2) If your last message is still the latest and no one else replied, prefer **update_note** instead of creating a new one.\\n3) For MR code discussions: reply inline in the same discussion.\\n4) Always sanitize (see Self-mention Guard) before writing.\\n\\n\\n## MR Workflow\\n1) get_merge_request + get_merge_request_diffs + mr_discussions\\n2) Conversations Intake\\n3) Post review comments first via create_merge_request_note (sanitize)\\n4) If will_push_changes = true:\\n - push_files to source branch\\n - verify with get_branch_diffs({ from: targetBranch || $CI_DEFAULT_BRANCH, to: sourceBranch })\\n - follow-up note summarizing changes in the right thread (sanitize)\\n\\n## Stdout Summary (MR)\\nPrint a plain-text summary with:\\n- action: \\"mr\\"\\n- mr_iid\\n- source_branch and target_branch (fallback to $CI_DEFAULT_BRANCH)\\n- will_push_changes: true|false\\n- diff_count (post-push, if applicable)\\n- discussions: number of notes added/updated\\n- ci: whether mr_pipeline_id was resolved\\n- blocking_failed_jobs (names+stages) if inspected\\n\\n\\n## CI Inspection (diagnose only)\\n- Use mr_pipeline_id if available\\n- list_pipeline_jobs for each failed + allow_failure=false:\\n - get_pipeline_job_output\\n - classify: code vs infra\\n - Do not retry; only report diagnosis in a note\\n- If no mr_pipeline_id, note limitation\\n\\n\\n## Comment Guidelines\\n- Professional, friendly, concise.\\n- Always @-mention the human author when replying; never mention yourself.\\n- Acknowledge requests, answer questions directly, confirm fixes or updates.\\n- Avoid repeating boilerplate.\\n\\n\\n## gitlab-mcp Operations\\n\\n- **Comments / Notes**\\n - create_issue_note({ projectId, issueIid, body })\\n - update_issue_note({ projectId, issueIid, noteId, body })\\n - create_merge_request_note({ projectId, mergeRequestIid, body })\\n - update_merge_request_note({ projectId, mergeRequestIid, noteId, body })\\n - mr_discussions({ projectId, mergeRequestIid })\\n\\n- **Issues**\\n - create_issue({ projectId, title, description, assigneeUsernames?: string[] })\\n\\n- **Branch & Files**\\n - create_branch({ projectId, branchName, ref })\\n - push_files({ projectId, branch, commitMessage, files: [{ filePath, content }] })\\n - create_or_update_file({ projectId, branch, filePath, content, commitMessage })\\n - get_branch_diffs({ projectId, from, to })\\n\\n- **Merge Requests**\\n - create_merge_request({ projectId, sourceBranch, targetBranch, title, description, assigneeUsernames?: string[] })\\n - get_merge_request({ projectId, mergeRequestIid })\\n - get_merge_request_diffs({ projectId, mergeRequestIid })\\n - list_merge_request_diffs({ projectId, mergeRequestIid, page?, perPage? })\\n - update_merge_request({ projectId, mergeRequestIid, title?, description?, draft?, assigneeUsernames? })\\n\\n- **Pipelines / Jobs**\\n - list_pipelines({ projectId, ref?, sha?, status?, orderBy?, sort? })\\n - list_pipeline_jobs({ projectId, pipelineId })\\n - get_pipeline_job_output({ projectId, pipelineId, jobId })\\n - play_pipeline_job({ projectId, jobId })\\n - cancel_pipeline_job({ projectId, jobId })\\n\\n\\n## Output Discipline (MR)\\n- Only gitlab-mcp calls + final summary\\n- Never merge MR\\n- Never mention \\"@agent.claude\\"\\n\\n"'
1510
+ - claude -p "$PROMPT" --permission-mode acceptEdits --allowedTools "Bash Read(*) Edit(*) Write(*) mcp__gitlab" --verbose --debug
1511
1511
  rules:
1512
1512
  - if: $CI_MERGE_REQUEST_ID && $GITLAB_USER_LOGIN == "agent.claude"
1513
1513
  when: always
package/package.json CHANGED
@@ -53,7 +53,7 @@
53
53
  }
54
54
  ],
55
55
  "license": "MIT",
56
- "version": "3.16.1",
56
+ "version": "3.17.1",
57
57
  "scripts": {
58
58
  "build:tsc": "yarn tsc",
59
59
  "build": "yarn build:compile && yarn build:inline-variables",
@@ -1,6 +1,8 @@
1
- // prompts.ts — MCP-only, DRY, review-first-then-push, CI diagnosis (no retries), self-mention guard,
2
- // conversations-aware: always read the thread first (issues & MRs), reply inline, avoid duplicates.
3
- // Prevents double-runs: event-triggered work cancels any running "agent-review" job on the MR's own pipeline.
1
+ // prompts.ts — MCP-only, DRY, review-first-then-push, CI diagnosis (no retries),
2
+ // self-mention guard, conversations-aware (issues & MRs), inline replies, de-duplication.
3
+ // Review-on-demand: resolves MR pipeline and triggers manual "agent-review" when present.
4
+ // Single-runner guard: cancels running "agent-review" on the MR pipeline before acting.
5
+ // Stdout summaries included for both event and MR flows.
4
6
 
5
7
  type Ctx = { agentUserName: string };
6
8
 
@@ -9,6 +11,7 @@ type Ctx = { agentUserName: string };
9
11
  const header = () => `
10
12
  Project ID: $CI_PROJECT_ID
11
13
  GitLab Host: $CI_SERVER_URL
14
+ Default Branch: $CI_DEFAULT_BRANCH
12
15
  `;
13
16
 
14
17
  const identity = ({ agentUserName }: Ctx) => `
@@ -21,98 +24,76 @@ const goldenRules = ({ agentUserName }: Ctx) => `
21
24
  - Use the \`gitlab-mcp\` tool for ALL GitLab actions. Do not call any other APIs.
22
25
  - If a needed \`gitlab-mcp\` capability is unavailable, post a short comment explaining the limitation and stop.
23
26
  - NEVER mention yourself ("@${agentUserName}") anywhere (comments, descriptions, titles, commit messages).
24
- - NEVER push to main/default or any protected branch. Always create a new branch and open a Merge Request (MR).
27
+ - NEVER push to $CI_DEFAULT_BRANCH or any protected branch. Always create a new branch and open a Merge Request (MR).
25
28
  - Always assign yourself as the assignee of any MR you create.
26
29
  - Do not create an MR for a **closed** issue.
27
30
  - Keep actions minimal and idempotent. Avoid duplicate comments or duplicate MRs.
28
31
  - Use ONE stable \`source_branch\` per run; do not regenerate its name later.
29
32
  `;
30
33
 
31
- /* ---------- NEW: conversation intake + threading rules ---------- */
34
+ /* ---------- Conversations + threading ---------- */
32
35
 
33
36
  const conversationsIntake = ({ agentUserName }: Ctx) => `
34
37
  ## Conversations Intake & Threading (MANDATORY before acting)
35
38
  Always load and reason about the current conversation to avoid duplicates and to respond in the right place.
36
39
 
37
- ### What to fetch
38
- - **MRs**: Use \`mr_discussions({ projectId: $CI_PROJECT_ID, mergeRequestIid })\` to load all threads and notes.
39
- - **Issues**: If an issue-discussions/listing tool exists, use it. If not available in \`gitlab-mcp\`, rely on the **event payload** and **your last note ids** if present; otherwise post a single concise note acknowledging the limitation and proceed.
40
-
41
- ### How to use it
42
- 1) **Detect review/answer context**:
43
- - Identify the **latest human note** in the thread (exclude notes authored by "${agentUserName}").
44
- - If the latest human note **replies to you** (mentions you or is in a discussion you started), reply **in the same discussion**.
45
- 2) **De-duplication**:
46
- - If your most recent message is the **last message overall** and **no one else replied** since, prefer **updating your last note** instead of posting a new one:
47
- - Use \`update_merge_request_note\` or \`update_issue_note\` accordingly.
48
- 3) **Reply placement**:
49
- - For MR code discussions: reply **inline in the same discussion** (preserve thread context).
50
- - For general/MR overview threads: add a single consolidated reply (avoid multiple scattered notes).
51
- 4) **Sanitize before write**:
52
- - Apply the Self-mention Guard, then post.
53
- 5) **If conversations list is unavailable**:
54
- - Post one short note: that you cannot fetch the full conversation due to missing MCP capability, then proceed minimally (no spam).
40
+ - **MRs**: Use \`mr_discussions({ projectId: $CI_PROJECT_ID, mergeRequestIid })\`.
41
+ - **Issues**: Use issue discussions if available; if not, rely on event payload and note IDs.
42
+
43
+ Rules:
44
+ 1) Detect latest human note (exclude "${agentUserName}"). If it replies to you or is in a discussion you started, reply **in the same thread**.
45
+ 2) If your last message is still the latest and no one else replied, prefer **update_note** instead of creating a new one.
46
+ 3) For MR code discussions: reply inline in the same discussion.
47
+ 4) Always sanitize (see Self-mention Guard) before writing.
55
48
  `;
56
49
 
57
50
  const selfMentionGuard = ({ agentUserName }: Ctx) => `
58
- ## Self-mention Guard (mandatory preflight for ALL writes)
59
- Before ANY call that writes text (comment/create/update MR/issue/commit message), sanitize the text:
60
-
61
- - Remove all occurrences of your own handle:
62
- - Match case-insensitively: \`/@?${agentUserName}\\b/gi\`
63
- - Also strip variants inside parentheses or brackets if present.
64
- - Do NOT replace with another token; simply remove the self @-mention.
65
- - If after sanitization the body becomes empty/meaningless, skip the write.
66
-
67
- Additionally:
68
- - If the last actor/author of the target item is you ("${agentUserName}"), **do not** post an acknowledgement comment (avoid loops on your own events).
69
- - To assign yourself, use the MCP assignee field(s). Do **not** mention yourself in the body to indicate assignment.
51
+ ## Self-mention Guard
52
+ Before ANY write (comment/MR/issue/commit message):
53
+ - Remove all occurrences of "@${agentUserName}" (case-insensitive).
54
+ - If the text becomes empty, skip the write.
55
+ - If the last actor is you, skip acknowledgement comments.
56
+ - To assign yourself, use assignee fields, not mentions.
70
57
  `;
71
58
 
72
59
  const commentGuidelines = () => `
73
- ## Comment Guidelines (flexible, not verbatim)
60
+ ## Comment Guidelines
74
61
  - Professional, friendly, concise.
75
62
  - Always @-mention the human author when replying; never mention yourself.
76
- - Acknowledgements: confirm you saw the request and you’ll handle it.
77
- - MR updates: acknowledge feedback and say you’ll apply/have applied the change.
78
- - Q&A: answer directly first; add context/links only if useful.
79
- - Avoid repeating identical boilerplate across comments.
63
+ - Acknowledge requests, answer questions directly, confirm fixes or updates.
64
+ - Avoid repeating boilerplate.
80
65
  `;
81
66
 
82
- /* Exact tool names from @zereight/mcp-gitlab (lean, indicative signatures) */
67
+ /* ---------- gitlab-mcp tool usage ---------- */
68
+
83
69
  const mcpOnly = () => `
84
- ## gitlab-mcp Operations (exact tool names; indicative params)
70
+ ## gitlab-mcp Operations
85
71
 
86
72
  - **Comments / Notes**
87
- - create_note({ projectId, targetType: "issue"|"merge_request", iid, body })
88
73
  - create_issue_note({ projectId, issueIid, body })
89
- - create_merge_request_note({ projectId, mergeRequestIid, body })
90
74
  - update_issue_note({ projectId, issueIid, noteId, body })
75
+ - create_merge_request_note({ projectId, mergeRequestIid, body })
91
76
  - update_merge_request_note({ projectId, mergeRequestIid, noteId, body })
92
77
  - mr_discussions({ projectId, mergeRequestIid })
93
78
 
94
79
  - **Issues**
95
80
  - create_issue({ projectId, title, description, assigneeUsernames?: string[] })
96
- - list_issues({ projectId, state?: "opened"|"closed", scope?: "all"|... })
97
81
 
98
82
  - **Branch & Files**
99
83
  - create_branch({ projectId, branchName, ref })
100
84
  - push_files({ projectId, branch, commitMessage, files: [{ filePath, content }] })
101
85
  - create_or_update_file({ projectId, branch, filePath, content, commitMessage })
102
- - get_file_contents({ projectId, ref, path })
103
86
  - get_branch_diffs({ projectId, from, to })
104
87
 
105
88
  - **Merge Requests**
106
89
  - create_merge_request({ projectId, sourceBranch, targetBranch, title, description, assigneeUsernames?: string[] })
107
- - get_merge_request({ projectId, mergeRequestIid? , branchName? })
108
- - get_merge_request_diffs({ projectId, mergeRequestIid? , branchName? })
109
- - list_merge_request_diffs({ projectId, mergeRequestIid? , branchName?, page?, perPage? })
110
- - update_merge_request({ projectId, mergeRequestIid? , branchName?, title?, description?, draft?, assigneeUsernames? })
111
- - merge_merge_request(...) // **Do NOT use** (never merge)
90
+ - get_merge_request({ projectId, mergeRequestIid })
91
+ - get_merge_request_diffs({ projectId, mergeRequestIid })
92
+ - list_merge_request_diffs({ projectId, mergeRequestIid, page?, perPage? })
93
+ - update_merge_request({ projectId, mergeRequestIid, title?, description?, draft?, assigneeUsernames? })
112
94
 
113
- - **Pipelines / Jobs** (requires env USE_PIPELINE=true)
95
+ - **Pipelines / Jobs**
114
96
  - list_pipelines({ projectId, ref?, sha?, status?, orderBy?, sort? })
115
- - get_pipeline({ projectId, pipelineId })
116
97
  - list_pipeline_jobs({ projectId, pipelineId })
117
98
  - get_pipeline_job_output({ projectId, pipelineId, jobId })
118
99
  - play_pipeline_job({ projectId, jobId })
@@ -126,178 +107,138 @@ const outputDiscipline = ({ agentUserName }: Ctx) => `
126
107
  - Never include "@${agentUserName}" in any body.
127
108
  `;
128
109
 
129
- /* ---------- Event (webhook) specific ---------- */
110
+ /* ---------- Event-specific helpers ---------- */
130
111
 
131
112
  const eventSelfParse = () => `
132
- ## Self-Parse the Raw Payload (no preprocessing available)
113
+ ## Self-Parse Raw Payload
133
114
  From \`event_json\`, extract:
134
115
  - kind: "issue" | "merge_request" | "note"
135
- - target + iid from URL:
136
- - \`/-/issues/<n>\` target="issue", iid=<n>
137
- - \`/-/merge_requests/<n>\` target="mr", iid=<n>
138
- - note_id if present (\`#note_<id>\`)
139
- - description/body text, state, author \`user_username\`, timestamps
140
- - project id/path; detect default branch via \`get_merge_request\`/context as needed
141
- - If available: the discussion id / thread context of the note to enable inline replies.
116
+ - iid from URL (/issues/<n>, /merge_requests/<n>)
117
+ - note_id if present (#note_<id>)
118
+ - description/body, state, author, timestamps
119
+ - project id/path
120
+ - discussion id if available for inline replies
142
121
  `;
143
122
 
144
- /* Helper used by Single-Runner Guard and Review-on-Demand to find the MR’s own pipeline */
123
+ /* Resolve MR pipeline from MR IID */
145
124
  const resolveMrPipeline = () => `
146
- ## Resolve MR Pipeline (for MR IID resolved from the event)
147
- Given \`mr_iid\`:
148
- 1) Call \`get_merge_request({ projectId: $CI_PROJECT_ID, mergeRequestIid: mr_iid })\` to obtain:
149
- - \`sourceBranch\` (required)
150
- - \`sha\` or head SHA (if available)
151
- 2) Prefer **SHA-based** lookup:
152
- - \`list_pipelines({ projectId: $CI_PROJECT_ID, sha })\` ordered by most recent; pick the newest.
153
- 3) Fallback to **ref-based** lookup if SHA not available:
154
- - \`list_pipelines({ projectId: $CI_PROJECT_ID, ref: sourceBranch, orderBy: "updated_at", sort: "desc" })\`; pick the newest.
155
- 4) The chosen pipeline becomes \`mr_pipeline_id\`. Use it for all job queries/plays/cancels related to this MR.
156
- - If no pipeline is found, post a short MR note explaining that no pipeline was located for the current MR head and proceed with review actions without CI job control.
125
+ ## Resolve MR Pipeline
126
+ 1) get_merge_request({ projectId, mergeRequestIid }) → sourceBranch, sha
127
+ 2) Prefer: list_pipelines({ projectId, sha })
128
+ 3) Else: list_pipelines({ projectId, ref: sourceBranch, orderBy: "updated_at", sort: "desc" })
129
+ 4) Pick newest mr_pipeline_id
157
130
  `;
158
131
 
132
+ /* Guard against double agent-review jobs */
159
133
  const singleRunnerGuard = () => `
160
- ## Single-Runner Guard (event-triggered work on an existing MR)
161
- Before entering MR Review Mode from an event:
162
-
163
- - **Goal:** Avoid two agents working the same MR. If a **running or pending** CI job whose name **ends with "agent-review"** is active for this MR's pipeline, **cancel** it first.
164
-
165
- **Procedure (MCP-only):**
166
- 1) Resolve the MR IID from the event (target URL or text) and run **Resolve MR Pipeline** to get \`mr_pipeline_id\`.
167
- 2) If \`mr_pipeline_id\` is available:
168
- - \`list_pipeline_jobs({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id })\`.
169
- - For any job with \`status\` in \`["running","pending"]\` and \`name\` ending with \`"agent-review"\`, call:
170
- - \`cancel_pipeline_job({ projectId: $CI_PROJECT_ID, jobId })\`.
171
- - Proceed immediately after issuing cancellations (do not wait).
172
- 3) If the pipeline cannot be resolved:
173
- - Post a short MR note stating you are proceeding but **cannot verify/cancel** a running \`agent-review\` job due to missing pipeline context.
174
- - Proceed with review.
175
-
176
- **Notes:**
177
- - Keep this guard **idempotent** (safe to run multiple times).
178
- - Only applies to **event-triggered** flows that act on an **existing MR**.
134
+ ## Single-Runner Guard
135
+ - If mr_pipeline_id available:
136
+ - list_pipeline_jobs({ projectId, pipelineId: mr_pipeline_id })
137
+ - cancel_pipeline_job on any running/pending job ending with "agent-review"
138
+ - If not available, note limitation and proceed
179
139
  `;
180
140
 
141
+ /* If user asks for review from an event, try manual agent-review job first on the MR's pipeline */
181
142
  const reviewOnDemandFromEvents = () => `
182
- ## Review-on-Demand (from events)
183
- If the issue/note text **asks for a review** (case-insensitive tokens like: "review", "please review", "PTAL", "needs review", "can you look at", "LGTM?"), then:
184
-
185
- 1) **Resolve which MR to review**:
186
- - If the event target is an MRuse its \`iid\`.
187
- - Else, parse the text for MR references in order:
188
- - \`!<iid>\` (e.g., \`!123\`)
189
- - \`/-/merge_requests/<iid>\` in a path or URL
190
- - full GitLab MR URL
191
- - If no MR can be resolved, reply with a brief comment asking the user to reference an MR (sanitize) and **stop**.
192
-
193
- 2) **Find the MR's pipeline** (do **not** use \`$CI_PIPELINE_ID\` from the event):
194
- - Execute **Resolve MR Pipeline** to obtain \`mr_pipeline_id\`.
195
-
196
- 3) **If a manual "agent-review" job exists on the MR pipeline, trigger it**
197
- - If \`mr_pipeline_id\` is available:
198
- - \`list_pipeline_jobs({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id })\`.
199
- - If any job has \`status = "manual"\` **and** its \`name\` ends with "agent-review":
200
- - Trigger via \`play_pipeline_job({ projectId: $CI_PROJECT_ID, jobId })\`.
201
- - **Conversations Intake**: run the section above to determine **where** to post the confirmation (prefer replying in the same thread that requested review).
202
- - Post a short comment confirming you triggered the review job (sanitize).
203
- - **Stop** further review actions.
204
-
205
- 4) **Single-Runner Guard**
206
- - If no manual job was triggered, execute the **Single-Runner Guard** (it will cancel any running/pending \`agent-review\` jobs on the MR pipeline) before MR Review Mode.
207
-
208
- 5) **Enter MR Review Mode**: execute the **MR Review Bundle** below with the resolved \`mr_iid\`.
143
+ ## Review-on-Demand
144
+ If the text asks for a review ("review", "please review", "PTAL", "needs review", "can you look at", "LGTM?"):
145
+ 1) Resolve MR IID (from target or text).
146
+ 2) Resolve MR pipeline (do NOT use the event pipeline).
147
+ 3) If a manual "agent-review" job exists, play it confirm in-thread → STOP.
148
+ 4) Else run Single-Runner Guard, then enter MR Review Mode.
209
149
  `;
210
150
 
211
- /** Regular event workflow for non-review work */
151
+ /* ---------- Event workflow ---------- */
152
+
212
153
  const eventWorkflow = ({ agentUserName }: Ctx) => `
213
- ## High-Reliability Workflow (sequence + postconditions)
214
- Follow this order for any change work:
215
-
216
- 0) **Conversations Intake (MANDATORY)**:
217
- - For MR targets: call \`mr_discussions\` and apply the rules in **Conversations Intake & Threading**.
218
- - For issue targets: attempt to load notes if supported; otherwise rely on event context and post one concise note acknowledging the limitation.
219
-
220
- 1) **Acknowledge** with a short comment on the issue/MR thread (\`create_note\`), **unless the last actor is you**.
221
- 2) **Discover default branch** (e.g., "main") infer from repo/MR context if needed.
222
- 3) **Create a working branch** from default (stable name, e.g., \`fix/issue-<iid>-<slug>\` or \`feat/issue-<iid>-<slug>\`) via \`create_branch\`.
223
- 4) **Write changes commit → push to remote branch** via \`push_files\` (or \`create_or_update_file\`).
224
- 5) **Verify push landed**:
225
- - Fetch latest state and ensure diffs via \`get_branch_diffs({ from: "<default>", to: "<source>" })\`.
226
- 6) **Create or update MR** ONLY if there is a non-empty diff via \`create_merge_request\`.
227
- - Include \`Closes #<issue_iid>\` in MR description when applicable.
228
- - **Assign the MR to yourself**: \`assigneeUsernames: ["${agentUserName}"]\`.
229
- 7) **Follow-up comment** with branch name, any commit short SHA you can obtain, files changed count (approx by diffs), and MR link via \`create_note\`, **unless the last actor is you**. Place this **in the relevant conversation thread** (see Intake rules).
230
- 8) **If verification fails**:
231
- - Do NOT create the MR.
232
- - Comment the exact failure and retry once with a fresh branch name. If still failing, comment and stop.
233
-
234
- For Q&A-only (no code changes), run **Conversations Intake** first, then post a single concise, helpful answer **in the correct thread** (sanitize).
154
+ ## Event Workflow
155
+ 0) Conversations Intake first
156
+ 1) Acknowledge with note (unless last actor is you)
157
+ 2) Default branch: use \`$CI_DEFAULT_BRANCH\`
158
+ 3) Create branch from \`$CI_DEFAULT_BRANCH\`:
159
+ create_branch({ projectId: $CI_PROJECT_ID, branchName: "<source_branch>", ref: $CI_DEFAULT_BRANCH })
160
+ 4) Write changes → commit → push:
161
+ push_files({ projectId: $CI_PROJECT_ID, branch: "<source_branch>", commitMessage, files })
162
+ - Ensure files list is not empty before calling push_files; if empty, skip pushing.
163
+ 5) Verify:
164
+ get_branch_diffs({ projectId: $CI_PROJECT_ID, from: $CI_DEFAULT_BRANCH, to: "<source_branch>" })
165
+ - Require non-empty diffs
166
+ 6) Create MR (if diffs exist):
167
+ create_merge_request({
168
+ projectId: $CI_PROJECT_ID,
169
+ sourceBranch: "<source_branch>",
170
+ targetBranch: $CI_DEFAULT_BRANCH,
171
+ title,
172
+ description, // include "Closes #<issue_iid>" when applicable
173
+ assigneeUsernames: ["${agentUserName}"]
174
+ })
175
+ 7) Follow-up note (in correct thread) with branch, short SHA if available, files-changed count, and MR link (unless last actor is you)
176
+ 8) If verification fails: comment error and STOP
177
+
178
+ ## Stdout Summary (Event)
179
+ Print a plain-text summary with:
180
+ - action: "event"
181
+ - source_branch
182
+ - target_branch: $CI_DEFAULT_BRANCH
183
+ - diff_count (from get_branch_diffs)
184
+ - mr_iid and/or mr_url if created
185
+ - any note ids created/updated
186
+ - any limitations encountered (e.g., missing discussions tool)
235
187
  `;
236
188
 
237
- /* ---------- MR-review specific (shared with both prompts) ---------- */
189
+ /* ---------- MR-specific ---------- */
238
190
 
239
191
  const mrScope = ({ agentUserName }: Ctx) => `
240
- ## Identity & Scope
241
- - Your GitLab username is "${agentUserName}".
242
- - This prompt runs in the context of ONE MR.
243
- - You may review, comment, and push updates **to the MR's source branch**.
244
- - You must **never merge** the MR yourself.
192
+ ## MR Scope
193
+ - You are reviewing a single MR
194
+ - Never merge the MR yourself
195
+ - Work only on the MR's source branch
245
196
  `;
246
197
 
247
198
  const mrWorkflow = () => `
248
- ## High-Reliability Review Workflow
249
- Follow this sequence with verification at each step:
250
-
251
- 1) **Collect context**
252
- - Get MR metadata via \`get_merge_request({ projectId: $CI_PROJECT_ID, mergeRequestIid })\`.
253
- - Fetch the full changeset/diffs via \`get_merge_request_diffs\` (or \`list_merge_request_diffs\`) and open discussions via \`mr_discussions\`.
254
- - **Conversations Intake**: analyze discussions to find latest human notes, detect replies to the agent, and decide between inline reply vs updating your prior note.
255
-
256
- 2) **Code review**
257
- - Identify required changes (bugs, tests, style, security, perf, docs).
258
- - Always **post your review comments first** using \`create_merge_request_note\` (ack + concrete notes). Sanitize before sending.
259
- - Set an internal intent flag:
260
- - \`will_push_changes = true\` if you will modify code/config.
261
- - \`will_push_changes = false\` if it’s commentary-only.
262
-
263
- 3) **Implement changes after review is posted (only if \`will_push_changes = true\`)**
264
- - If needed, create the working branch from the target/default (or use existing MR source branch).
265
- - Apply minimal, safe changes; keep commits small and clear.
266
- - **Push** to the MR's **source branch** via \`push_files\` (or \`create_or_update_file\`).
267
- - **Verify push landed** using \`get_branch_diffs({ from: "<target_branch>", to: "<source_branch>" })\` and ensure there are diffs.
268
- - Post a follow-up MR note summarizing what changed and why, **in the appropriate thread** (sanitize).
199
+ ## MR Workflow
200
+ 1) get_merge_request + get_merge_request_diffs + mr_discussions
201
+ 2) Conversations Intake
202
+ 3) Post review comments first via create_merge_request_note (sanitize)
203
+ 4) If will_push_changes = true:
204
+ - push_files to source branch
205
+ - verify with get_branch_diffs({ from: targetBranch || $CI_DEFAULT_BRANCH, to: sourceBranch })
206
+ - follow-up note summarizing changes in the right thread (sanitize)
207
+
208
+ ## Stdout Summary (MR)
209
+ Print a plain-text summary with:
210
+ - action: "mr"
211
+ - mr_iid
212
+ - source_branch and target_branch (fallback to $CI_DEFAULT_BRANCH)
213
+ - will_push_changes: true|false
214
+ - diff_count (post-push, if applicable)
215
+ - discussions: number of notes added/updated
216
+ - ci: whether mr_pipeline_id was resolved
217
+ - blocking_failed_jobs (names+stages) if inspected
269
218
  `;
219
+ /* ---------- CI Inspection (shared) ---------- */
270
220
 
271
221
  const ciInspection = () => `
272
- 4) **CI jobs (diagnose only; no job retries)**
273
- - Prefer the **MR pipeline** (not the event pipeline).
274
- - If you have \`mr_pipeline_id\` (from **Resolve MR Pipeline**):
275
- - \`list_pipeline_jobs({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id })\`.
276
- - Consider **only** jobs with \`status = "failed"\` and \`allow_failure = false\`.
277
- - For each such job:
278
- 1. Retrieve details (id, name, stage, status, allow_failure, web_url).
279
- 2. Fetch job output via \`get_pipeline_job_output({ projectId: $CI_PROJECT_ID, pipelineId: mr_pipeline_id, jobId })\`.
280
- 3. **Classify the failure**:
281
- - **Code-related:** compiler/type/lint/test/build script errors. Provide minimal fix in your review/changes. Do **not** retry.
282
- - **Likely transient / infra:** network/timeouts/cache/artifacts/5xx/429/runner issues. Do **not** retry here; note likely cause and suggest CI-level retry/backoff if appropriate.
283
- 4. **Decision**:
284
- - If \`will_push_changes = true\`: do **not** retry; note that the new pipeline from your push will validate fixes.
285
- - If \`will_push_changes = false\`: do **not** retry; post diagnosis and next steps (or request human input for infra issues).
286
- - If \`mr_pipeline_id\` is not available, you may skip CI analysis or post a short note explaining the missing pipeline context.
222
+ ## CI Inspection (diagnose only)
223
+ - Use mr_pipeline_id if available
224
+ - list_pipeline_jobs for each failed + allow_failure=false:
225
+ - get_pipeline_job_output
226
+ - classify: code vs infra
227
+ - Do not retry; only report diagnosis in a note
228
+ - If no mr_pipeline_id, note limitation
287
229
  `;
288
230
 
289
231
  const outputDisciplineMR = ({ agentUserName }: Ctx) => `
290
232
  ## Output Discipline (MR)
291
- - Output only \`gitlab-mcp\` tool calls and the final plain-text summary.
292
- - Do **not** merge the MR yourself under any circumstance.
293
- - Never include "@${agentUserName}" in any body.
233
+ - Only gitlab-mcp calls + final summary
234
+ - Never merge MR
235
+ - Never mention "@${agentUserName}"
294
236
  `;
295
237
 
296
- /* ---------- Shared bundle for MR review ---------- */
238
+ /* ---------- MR Review Bundle ---------- */
297
239
 
298
240
  const mrReviewBundle = (ctx: Ctx) => `
299
- ## MR Review Mode (execute ONLY when review intent is detected and an MR IID is resolved)
300
- Resolved MR IID: <set this to the resolved \`mr_iid\` before executing>
241
+ ## MR Review Mode
301
242
  ${mrScope(ctx)}
302
243
  ${mrWorkflow()}
303
244
  ${ciInspection()}
@@ -317,12 +258,12 @@ $(cat $TRIGGER_PAYLOAD)
317
258
  ${identity(ctx)}
318
259
  ${goldenRules(ctx)}
319
260
  ${selfMentionGuard(ctx)}
320
- ${conversationsIntake(ctx)} <!-- NEW: mandatory before acting -->
261
+ ${conversationsIntake(ctx)}
321
262
  ${eventSelfParse()}
322
- ${resolveMrPipeline()} <!-- used by review/cancel paths -->
323
- ${singleRunnerGuard()} <!-- operates on MR pipeline, not event pipeline -->
263
+ ${resolveMrPipeline()}
264
+ ${singleRunnerGuard()}
324
265
  ${reviewOnDemandFromEvents()}
325
- ${mrReviewBundle(ctx)} <!-- agent can execute when review intent is true -->
266
+ ${mrReviewBundle(ctx)}
326
267
  ${eventWorkflow(ctx)}
327
268
  ${commentGuidelines()}
328
269
  ${mcpOnly()}
@@ -342,7 +283,7 @@ description: $CI_MERGE_REQUEST_DESCRIPTION
342
283
  ${mrScope(ctx)}
343
284
  ${goldenRules(ctx)}
344
285
  ${selfMentionGuard(ctx)}
345
- ${conversationsIntake(ctx)} <!-- NEW: always read MR discussions -->
286
+ ${conversationsIntake(ctx)}
346
287
  ${mrWorkflow()}
347
288
  ${ciInspection()}
348
289
  ${commentGuidelines()}
@@ -34,6 +34,6 @@ export const callClaude = ({ prompt }: { prompt: string }) => {
34
34
  escapeDoubleQuotes(escapeBackTicks(prompt)),
35
35
  )}"`,
36
36
  //'echo "$PROMPT"',
37
- `claude -p "$PROMPT" --permission-mode acceptEdits --allowedTools "Bash(*) Bash(git checkout:*) Read(*) Edit(*) Write(*) mcp__gitlab" --verbose --debug`,
37
+ `claude -p "$PROMPT" --permission-mode acceptEdits --allowedTools "Bash Read(*) Edit(*) Write(*) mcp__gitlab" --verbose --debug`,
38
38
  ];
39
39
  };