@catladder/cli 0.0.0-main-a99d8000 → 0.0.0-main-bbb9c8e8

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.
@@ -9074,20 +9074,46 @@ __exportStar(__nccwpck_require__(22990), exports);
9074
9074
  "use strict";
9075
9075
 
9076
9076
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9077
- exports.createAgentContext = void 0;
9078
- const createAgentContext = async (ctx) => {
9079
- var _a;
9080
- const agentConfig = (_a = ctx.config.agents) === null || _a === void 0 ? void 0 : _a[ctx.agentName];
9081
- if (!agentConfig) {
9082
- throw new Error(`Agent ${ctx.agentName} not found in config`);
9077
+ exports.createAgentContext = exports.AgentContext = void 0;
9078
+ class AgentContext {
9079
+ constructor(agentName, config) {
9080
+ this.agentName = agentName;
9081
+ this.config = config;
9082
+ this.type = "agent";
9083
9083
  }
9084
- return {
9085
- type: "agent",
9086
- name: ctx.agentName,
9087
- //env: ctx.env,
9088
- fullConfig: ctx.config,
9089
- agentConfig: agentConfig,
9090
- };
9084
+ get name() {
9085
+ return this.agentName;
9086
+ }
9087
+ get agentConfig() {
9088
+ var _a;
9089
+ const agentConfig = (_a = this.config.agents) === null || _a === void 0 ? void 0 : _a[this.agentName];
9090
+ if (!agentConfig) {
9091
+ throw new Error(`Agent ${this.agentName} not found in config`);
9092
+ }
9093
+ return agentConfig;
9094
+ }
9095
+ get agentUser() {
9096
+ var _a, _b, _c, _d;
9097
+ return {
9098
+ username: (_b = (_a = this.agentConfig.agentUser) === null || _a === void 0 ? void 0 : _a.username) !== null && _b !== void 0 ? _b : "agent.claude",
9099
+ userId: (_d = (_c = this.agentConfig.agentUser) === null || _c === void 0 ? void 0 : _c.userId) !== null && _d !== void 0 ? _d : "$DEFAULT_AGENT_USER_ID",
9100
+ };
9101
+ }
9102
+ get reviews() {
9103
+ var _a, _b;
9104
+ return {
9105
+ byUser: (_b = (_a = this.agentConfig.reviews) === null || _a === void 0 ? void 0 : _a.byUser) !== null && _b !== void 0 ? _b : {
9106
+ [this.agentUser.username]: { automatic: true },
9107
+ },
9108
+ };
9109
+ }
9110
+ get fullConfig() {
9111
+ return this.config;
9112
+ }
9113
+ }
9114
+ exports.AgentContext = AgentContext;
9115
+ const createAgentContext = async (ctx) => {
9116
+ return new AgentContext(ctx.agentName, ctx.config);
9091
9117
  };
9092
9118
  exports.createAgentContext = createAgentContext;
9093
9119
  //# sourceMappingURL=createAgentContext.js.map
@@ -9152,13 +9178,28 @@ const utils_1 = __nccwpck_require__(89133);
9152
9178
  const createAgentReviewJob = (context) => {
9153
9179
  const baseJob = (0, shared_1.createBaseAgentJob)(context);
9154
9180
  const agentUserName = (0, utils_1.getAgentUserName)(context);
9181
+ const rules = context.reviews.byUser === "all-automatic"
9182
+ ? [
9183
+ {
9184
+ ...rules_1.RULE_IS_MERGE_REQUEST,
9185
+ when: "always",
9186
+ },
9187
+ ]
9188
+ : Object.entries(context.reviews.byUser)
9189
+ .filter(([_, { automatic }]) => automatic)
9190
+ .map(([username]) => ({
9191
+ // GITLAB_USER_LOGIN is the username of the user who created the pipeline
9192
+ if: `${rules_1.RULE_IS_MERGE_REQUEST.if} && $GITLAB_USER_LOGIN == "${username}"`,
9193
+ when: "always",
9194
+ }));
9155
9195
  return {
9156
9196
  ...baseJob,
9157
9197
  name: context.name + "-agent-review",
9158
9198
  rules: [
9199
+ ...rules,
9159
9200
  {
9160
9201
  ...rules_1.RULE_IS_MERGE_REQUEST,
9161
- when: "always",
9202
+ when: "manual",
9162
9203
  },
9163
9204
  {
9164
9205
  when: "never",
@@ -9201,8 +9242,12 @@ exports.createJobsForAgentContext = createJobsForAgentContext;
9201
9242
 
9202
9243
  "use strict";
9203
9244
 
9245
+ // prompts.ts — MCP-only, DRY, review-first-then-push, CI logic, self-mention guard,
9246
+ // event prompt supports review-on-demand via manual "agent-review" job or fallback MR review.
9247
+ // Prevents double-runs: event-triggered work cancels any running "agent-review" job on the same MR.
9204
9248
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9205
9249
  exports.getMergeRequestPrompt = exports.getEventPrompt = void 0;
9250
+ /* ---------- Shared blocks ---------- */
9206
9251
  const header = () => `
9207
9252
  Project ID: $CI_PROJECT_ID
9208
9253
  GitLab Host: $CI_SERVER_URL
@@ -9213,84 +9258,84 @@ const identity = ({ agentUserName }) => `
9213
9258
  `;
9214
9259
  const goldenRules = ({ agentUserName }) => `
9215
9260
  ## Golden Rules
9216
- - Use the \`gitlab-mcp\` tool for ALL GitLab actions. If a needed action is missing, use GitLab REST/GraphQL API directly as a fallback.
9217
- - NEVER mention yourself ("@${agentUserName}").
9261
+ - Use the \`gitlab-mcp\` tool for ALL GitLab actions. Do not call any other APIs.
9262
+ - If a needed \`gitlab-mcp\` capability is unavailable, post a short comment explaining the limitation and stop.
9263
+ - NEVER mention yourself ("@${agentUserName}") anywhere (comments, descriptions, titles, commit messages).
9218
9264
  - NEVER push to main/default or any protected branch. Always create a new branch and open a Merge Request (MR).
9265
+ - Always assign yourself as the assignee of any MR you create.
9219
9266
  - Do not create an MR for a **closed** issue.
9220
9267
  - Keep actions minimal and idempotent. Avoid duplicate comments or duplicate MRs.
9221
9268
  - Use ONE stable \`source_branch\` per run; do not regenerate its name later.
9222
9269
  `;
9270
+ const selfMentionGuard = ({ agentUserName }) => `
9271
+ ## Self-mention Guard (mandatory preflight for ALL writes)
9272
+ Before ANY call that writes text (comment/create/update MR/issue/commit message), sanitize the text:
9273
+
9274
+ - Remove all occurrences of your own handle:
9275
+ - Match case-insensitively: \`/@?${agentUserName}\\b/gi\`
9276
+ - Also strip variants inside parentheses or brackets if present.
9277
+ - Do NOT replace with another token; simply remove the self @-mention.
9278
+ - If after sanitization the body becomes empty/meaningless, skip the write.
9279
+
9280
+ Additionally:
9281
+ - If the last actor/author of the target item is you ("${agentUserName}"), **do not** post an acknowledgement comment (avoid loops on your own events).
9282
+ - To assign yourself, use the MCP assignee field(s). Do **not** mention yourself in the body to indicate assignment.
9283
+ `;
9223
9284
  const commentGuidelines = () => `
9224
9285
  ## Comment Guidelines (flexible, not verbatim)
9225
- - Keep tone professional, friendly, and concise.
9286
+ - Professional, friendly, concise.
9226
9287
  - Always @-mention the human author when replying; never mention yourself.
9227
9288
  - Acknowledgements: confirm you saw the request and you’ll handle it.
9228
9289
  - MR updates: acknowledge feedback and say you’ll apply/have applied the change.
9229
9290
  - Q&A: answer directly first; add context/links only if useful.
9230
9291
  - Avoid repeating identical boilerplate across comments.
9231
9292
  `;
9232
- const mcpAndApi = () => `
9233
- ## Tools & API (MCP-first, REST/GraphQL fallback)
9234
- Use these \`gitlab-mcp\` capabilities when available (names illustrative—match the actual tool schema):
9235
-
9236
- - **Comments**
9237
- - \`gitlab-mcp.comment.create({ project_id: $CI_PROJECT_ID, target: "issue"|"mr", iid, body })\`
9238
-
9239
- - **Branch**
9240
- - \`gitlab-mcp.branch.create({ project_id: $CI_PROJECT_ID, from: "<default_branch>", name: "<source_branch>" })\`
9241
-
9242
- - **Commits & push**
9243
- - \`gitlab-mcp.commit.push({ project_id: $CI_PROJECT_ID, branch: "<source_branch>", message, files: [{ path, content | patch }] })\`
9293
+ /* Exact tool names from @zereight/mcp-gitlab (lean, indicative signatures) */
9294
+ const mcpOnly = () => `
9295
+ ## gitlab-mcp Operations (exact tool names; indicative params)
9296
+
9297
+ - **Comments / Notes**
9298
+ - create_note({ projectId, targetType: "issue"|"merge_request", iid, body })
9299
+ - create_issue_note({ projectId, issueIid, body })
9300
+ - create_merge_request_note({ projectId, mergeRequestIid, body })
9301
+ - update_issue_note({ projectId, issueIid, noteId, body })
9302
+ - update_merge_request_note({ projectId, mergeRequestIid, noteId, body })
9303
+ - mr_discussions({ projectId, mergeRequestIid })
9304
+
9305
+ - **Issues**
9306
+ - create_issue({ projectId, title, description, assigneeUsernames?: string[] })
9307
+ - list_issues({ projectId, state?: "opened"|"closed", scope?: "all"|... })
9308
+
9309
+ - **Branch & Files**
9310
+ - create_branch({ projectId, branchName, ref }) // ref = default branch or SHA
9311
+ - push_files({ projectId, branch, commitMessage, files: [{ filePath, content }] })
9312
+ - create_or_update_file({ projectId, branch, filePath, content, commitMessage })
9313
+ - get_file_contents({ projectId, ref, path })
9314
+ - get_branch_diffs({ projectId, from, to }) // compare refs
9244
9315
 
9245
9316
  - **Merge Requests**
9246
- - \`gitlab-mcp.merge_request.create({ project_id: $CI_PROJECT_ID, source_branch, target_branch: "<default_branch>", title, description, assign_to_self: true })\`
9247
- - \`gitlab-mcp.merge_request.update({ project_id: $CI_PROJECT_ID, mr_iid, ... })\`
9248
- - \`gitlab-mcp.merge_request.rebase({ project_id: $CI_PROJECT_ID, mr_iid, onto: "<default_branch>" })\`
9249
-
9250
- - **Read/verify**
9251
- - \`gitlab-mcp.project.get({ project_id: $CI_PROJECT_ID })\` → default branch
9252
- - \`gitlab-mcp.repo.compare({ project_id: $CI_PROJECT_ID, from: "<default_branch>", to: "<source_branch>" })\`
9253
- - \`gitlab-mcp.repo.branch.get({ project_id: $CI_PROJECT_ID, name: "<source_branch>" })\`
9254
- - \`gitlab-mcp.repo.commits.list({ project_id: $CI_PROJECT_ID, ref_name: "<source_branch>", per_page: 1 })\`
9255
-
9256
- ### Fallback: Direct GitLab API
9257
- If MCP lacks an operation, call GitLab’s REST/GraphQL API directly.
9258
-
9259
- - **Authentication**
9260
- Use the environment variable \`GITLAB_PERSONAL_ACCESS_TOKEN\`.
9261
- Send it in the HTTP header:
9262
- \`\`\`
9263
- Private-Token: $GITLAB_PERSONAL_ACCESS_TOKEN
9264
- \`\`\`
9265
-
9266
- - **Host & project variables**
9267
- - API base URL: \`$CI_SERVER_URL/api/v4\`
9268
- - Project ID: \`$CI_PROJECT_ID\`
9269
-
9270
- - **Examples**
9271
- - Get project (default branch):
9272
- \`GET $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID\`
9273
- - Get/create branch:
9274
- \`GET|POST $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/repository/branches\`
9275
- - Compare refs:
9276
- \`GET $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/repository/compare?from=<default>&to=<source>\`
9277
- - List commits:
9278
- \`GET $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/repository/commits?ref_name=<branch>&per_page=1\`
9279
- - Create comment on issue/MR:
9280
- \`POST $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/issues/:iid/notes\`
9281
- \`POST $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/merge_requests/:iid/notes\`
9282
- - Create MR:
9283
- \`POST $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/merge_requests\`
9284
- - Get MR changes:
9285
- \`GET $CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/merge_requests/:iid/changes\`
9317
+ - create_merge_request({ projectId, sourceBranch, targetBranch, title, description, assigneeUsernames?: string[] })
9318
+ - get_merge_request({ projectId, mergeRequestIid? , branchName? })
9319
+ - get_merge_request_diffs({ projectId, mergeRequestIid? , branchName? })
9320
+ - list_merge_request_diffs({ projectId, mergeRequestIid? , branchName?, page?, perPage? })
9321
+ - update_merge_request({ projectId, mergeRequestIid? , branchName?, title?, description?, draft?, assigneeUsernames? })
9322
+ - merge_merge_request(...) // **Do NOT use** (never merge)
9323
+
9324
+ - **Pipelines / Jobs** (requires env USE_PIPELINE=true)
9325
+ - list_pipeline_jobs({ projectId, pipelineId })
9326
+ - get_pipeline_job_output({ projectId, pipelineId, jobId })
9327
+ - retry_pipeline({ projectId, pipelineId })
9328
+ - retry_pipeline_job({ projectId, jobId })
9329
+ - play_pipeline_job({ projectId, jobId })
9330
+ - cancel_pipeline_job({ projectId, jobId })
9286
9331
  `;
9287
9332
  const outputDiscipline = ({ agentUserName }) => `
9288
9333
  ## Output Discipline
9289
- - Prefer \`gitlab-mcp\` tool calls. If unavailable, output direct API requests (endpoint, method, headers, JSON body).
9334
+ - Output only \`gitlab-mcp\` tool calls and plain-text summaries where requested.
9290
9335
  - Keep comments concise and professional.
9291
9336
  - Never include "@${agentUserName}" in any body.
9292
9337
  `;
9293
- // --- Event-specific sections ---
9338
+ /* ---------- Event (webhook) specific ---------- */
9294
9339
  const eventSelfParse = () => `
9295
9340
  ## Self-Parse the Raw Payload (no preprocessing available)
9296
9341
  From \`event_json\`, extract:
@@ -9300,37 +9345,84 @@ From \`event_json\`, extract:
9300
9345
  - \`/-/merge_requests/<n>\` → target="mr", iid=<n>
9301
9346
  - note_id if present (\`#note_<id>\`)
9302
9347
  - description/body text, state, author \`user_username\`, timestamps
9303
- - project id/path; detect default branch via tool/API when needed
9348
+ - project id/path; detect default branch via \`get_merge_request\`/context as needed
9304
9349
 
9305
9350
  If any key is missing, choose the safest minimal action or briefly explain via a comment.
9306
9351
  `;
9307
- const eventWorkflow = () => `
9352
+ // NEW: Single-runner guard (event-triggered → existing MR)
9353
+ const singleRunnerGuard = () => `
9354
+ ## Single-Runner Guard (event-triggered work on an existing MR)
9355
+ Before entering MR Review Mode from an event:
9356
+
9357
+ - **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, **cancel** it first.
9358
+
9359
+ **Best-effort procedure (MCP-only):**
9360
+ 1) If \`$CI_PIPELINE_ID\` is available (this event is executing inside a CI context for the same MR):
9361
+ - Call \`list_pipeline_jobs({ projectId: $CI_PROJECT_ID, pipelineId: $CI_PIPELINE_ID })\`.
9362
+ - Identify any job where \`status\` is \`"running"\` or \`"pending"\` **and** \`name\` **endsWith** \`"agent-review"\`.
9363
+ - For each match, call \`cancel_pipeline_job({ projectId: $CI_PROJECT_ID, jobId })\`.
9364
+ - Proceed with review immediately after issuing cancellations (do not wait).
9365
+
9366
+ 2) If \`$CI_PIPELINE_ID\` is **not** available, or jobs for this MR cannot be listed with available MCP calls:
9367
+ - Post a short MR note stating you are proceeding but **cannot verify/cancel** a running \`agent-review\` job due to missing capabilities.
9368
+ - Proceed with review.
9369
+
9370
+ **Notes:**
9371
+ - Keep this guard **idempotent** (safe to run multiple times).
9372
+ - This guard only applies to **event-triggered** flows that decide to act on an **existing MR**.
9373
+ `;
9374
+ const reviewOnDemandFromEvents = () => `
9375
+ ## Review-on-Demand (from events)
9376
+ 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:
9377
+
9378
+ 1) **Check for pipeline review job**
9379
+ - List jobs for the current pipeline \`$CI_PIPELINE_ID\` via \`list_pipeline_jobs\`.
9380
+ - If any job has \`status = "manual"\` **and** its \`name\` ends with "agent-review":
9381
+ - Trigger it via \`play_pipeline_job({ projectId: $CI_PROJECT_ID, jobId })\`.
9382
+ - Post a short comment confirming you triggered the review job (sanitize).
9383
+ - **Stop** further review actions.
9384
+
9385
+ 2) **If no such job exists, resolve which MR to review**:
9386
+ - If the event target is an MR → use its \`iid\`.
9387
+ - Else, parse the text for MR references in order:
9388
+ - \`!<iid>\` (e.g., \`!123\`)
9389
+ - \`/-/merge_requests/<iid>\` in a path or URL
9390
+ - full GitLab MR URL
9391
+ - If no MR can be resolved, reply with a brief comment asking the user to reference an MR (sanitize) and **stop**.
9392
+
9393
+ 3) **Single-Runner Guard (cancel any running "agent-review" job)** // NEW: Single-runner guard
9394
+ - Execute the **Single-Runner Guard** steps above **before** MR Review Mode.
9395
+
9396
+ 4) **Enter MR Review Mode**: execute the **MR Review Bundle** below with the resolved \`mr_iid\`.
9397
+ `;
9398
+ /** Regular event workflow for non-review work */
9399
+ const eventWorkflow = ({ agentUserName }) => `
9308
9400
  ## High-Reliability Workflow (sequence + postconditions)
9309
9401
  Follow this order for any change work:
9310
9402
 
9311
- 1) **Acknowledge** with a short comment on the issue/MR thread.
9312
- 2) **Discover default branch** (e.g., "main") via MCP or API.
9313
- 3) **Create a working branch** from default (stable name, e.g., \`fix/issue-<iid>-<slug>\` or \`feat/issue-<iid>-<slug>\`).
9314
- 4) **Write changes → commit → push to remote branch.**
9403
+ 1) **Acknowledge** with a short comment on the issue/MR thread (\`create_note\`), **unless the last actor is you**.
9404
+ 2) **Discover default branch** (e.g., "main") — infer from repo/MR context if needed.
9405
+ 3) **Create a working branch** from default (stable name, e.g., \`fix/issue-<iid>-<slug>\` or \`feat/issue-<iid>-<slug>\`) via \`create_branch\`.
9406
+ 4) **Write changes → commit → push to remote branch** via \`push_files\` (or \`create_or_update_file\`).
9315
9407
  5) **Verify push landed**:
9316
- - Fetch latest commit on \`source_branch\`; record its short SHA.
9317
- - Compare default vs \`source_branch\` and ensure \`diffs.length > 0\`.
9318
- 6) **Create or update MR** ONLY if there is a non-empty diff.
9408
+ - Fetch latest state (optional: \`get_file_contents\`/log) and capture a short SHA from the branch head if exposed by the host.
9409
+ - Compare default vs \`source_branch\` via \`get_branch_diffs({ from: "<default>", to: "<source>" })\` and ensure there are diffs.
9410
+ 6) **Create or update MR** ONLY if there is a non-empty diff via \`create_merge_request\`.
9319
9411
  - Include \`Closes #<issue_iid>\` in MR description when applicable.
9320
- - Assign yourself to the MR.
9321
- 7) **Follow-up comment** with branch name, commit short SHA, files changed count, and MR link.
9412
+ - **Assign the MR to yourself**: \`assigneeUsernames: ["${agentUserName}"]\`.
9413
+ 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**.
9322
9414
  8) **If verification fails**:
9323
9415
  - Do NOT create the MR.
9324
9416
  - Comment the exact failure and retry once with a fresh branch name. If still failing, comment and stop.
9325
9417
 
9326
- For Q&A-only (no code changes), just post a concise, helpful answer on the same issue/MR.
9418
+ For Q&A-only (no code changes), just post a concise, helpful answer on the same issue/MR (sanitize first).
9327
9419
  `;
9328
- // --- MR-specific sections ---
9420
+ /* ---------- MR-review specific (shared with both prompts) ---------- */
9329
9421
  const mrScope = ({ agentUserName }) => `
9330
9422
  ## Identity & Scope
9331
9423
  - Your GitLab username is "${agentUserName}".
9332
- - This prompt runs in the context of ONE MR (no webhook).
9333
- - You may review, comment, rebase, and push updates **to the MR's source branch**.
9424
+ - This prompt runs in the context of ONE MR.
9425
+ - You may review, comment, and push updates **to the MR's source branch**.
9334
9426
  - You must **never merge** the MR yourself.
9335
9427
  `;
9336
9428
  const mrWorkflow = () => `
@@ -9338,44 +9430,66 @@ const mrWorkflow = () => `
9338
9430
  Follow this sequence with verification at each step:
9339
9431
 
9340
9432
  1) **Collect context**
9341
- - Get MR metadata (source_branch, target_branch, state, draft/WIP).
9342
- - Fetch the full changeset/diffs and open discussions (notes, threads, unresolved discussions).
9343
- - Read existing reviews/comments to avoid duplication.
9344
- - (Optional) Fetch recent CI pipeline(s) for this MR SHA/branch).
9433
+ - Get MR metadata via \`get_merge_request({ projectId: $CI_PROJECT_ID, mergeRequestIid })\`.
9434
+ - Fetch the full changeset/diffs via \`get_merge_request_diffs\` (or \`list_merge_request_diffs\`) and open discussions via \`mr_discussions\`.
9435
+ - Read existing notes to avoid duplication.
9345
9436
 
9346
9437
  2) **Code review**
9347
9438
  - Identify required changes (bugs, tests, style, security, perf, docs).
9348
- - If no meaningful changes are needed:
9349
- - Post a concise review comment summarizing findings.
9350
- - Ask for review by a **recent active human contributor** (not you).
9351
-
9352
- 3) **If changes are needed**
9353
- - Post a short acknowledgment comment on the MR.
9354
- - **Rebase** the MR onto the target/default branch (resolve trivial conflicts).
9355
- - Implement minimal, safe changes; keep commits small and clear.
9356
- - **Push** to the MR's **source_branch**.
9357
- - **Verify push landed** (latest commit short SHA; compare target vs source shows diffs > 0).
9358
- - Comment summarizing what changed and why.
9359
-
9360
- 4) **CI pipeline**
9361
- - Check pipeline status for the new commit on the MR branch.
9362
- - Retry/re-run if allowed on flaky failures; fix minimal issues; push again if needed.
9363
- - If still failing, comment with failure summary and next steps.
9439
+ - Always **post your review comments first** using \`create_merge_request_note\` (ack + concrete notes). Sanitize before sending.
9440
+ - Set an internal intent flag:
9441
+ - \`will_push_changes = true\` if you will modify code/config.
9442
+ - \`will_push_changes = false\` if it’s commentary-only.
9443
+
9444
+ 3) **Implement changes after review is posted (only if \`will_push_changes = true\`)**
9445
+ - If needed, create the working branch from the target/default (or use existing MR source branch).
9446
+ - Apply minimal, safe changes; keep commits small and clear.
9447
+ - **Push** to the MR's **source branch** via \`push_files\` (or \`create_or_update_file\`).
9448
+ - **Verify push landed** using \`get_branch_diffs({ from: "<target_branch>", to: "<source_branch>" })\` and ensure there are diffs.
9449
+ - Post a follow-up MR note summarizing what changed and why (sanitize).
9450
+ `;
9451
+ const ciInspection = () => `
9452
+ 4) **CI jobs (current pipeline focus: diagnose first, retry only when useful)**
9453
+ - Inspect jobs for the **current pipeline**: \`$CI_PIPELINE_ID\` via \`list_pipeline_jobs\`.
9454
+ - Consider **only** jobs with \`status = "failed"\` and \`allow_failure = false\`.
9455
+ - For each such job:
9456
+ 1. Retrieve details (id, name, stage, status, allow_failure, web_url).
9457
+ 2. Fetch job output via \`get_pipeline_job_output({ projectId: $CI_PROJECT_ID, pipelineId: $CI_PIPELINE_ID, jobId })\`.
9458
+ 3. **Classify the failure**:
9459
+ - **Code-related (do not retry):** compiler/type/lint/test/build script errors.
9460
+ - **Likely transient (may retry):** network/timeouts/infra/cache/artifacts/5xx/429/etc.
9461
+ 4. **Decision**:
9462
+ - If \`will_push_changes = true\`:
9463
+ - **Do not retry** current pipeline (upcoming push will trigger a new one).
9464
+ - Post an MR note: brief diagnosis per failed job; note a new pipeline will validate the fix (sanitize).
9465
+ - If \`will_push_changes = false\`:
9466
+ - If transient ⇒ \`retry_pipeline_job({ projectId: $CI_PROJECT_ID, jobId })\` (or \`retry_pipeline\` if job-level retry not available).
9467
+ Post a note stating you retried and why (sanitize).
9468
+ - If code-related ⇒ do not retry; post a note with diagnosis and suggested fix (sanitize).
9469
+ - Retry-once policy: at most **one** retry per job in this run.
9364
9470
 
9365
9471
  5) **Assign human reviewer if ready**
9366
- - If discussions are resolved and CI is passing (or running), request review from a recent active human contributor (not you).
9472
+ - If discussions are resolved and blocking CI issues are addressed or clearly triaged, request review from a recent active human contributor (not you), if supported by your environment.
9367
9473
 
9368
9474
  6) **Stdout summary**
9369
- - Print concise summary: commits pushed (short SHAs), files changed count, discussions resolved/left, CI status, and requested reviewers.
9475
+ - Print concise summary: branch used, files changed count (approx by diffs), discussions resolved/left, **blocking failed jobs (names + stages)** with classification (code vs transient), which jobs were retried (if any), and requested reviewers.
9370
9476
  `;
9371
- const fallbackApiAuth = () => `
9372
- ## Fallback API Auth (if MCP lacks a method)
9373
- - Base URL: \`$CI_SERVER_URL/api/v4\`
9374
- - Project: \`$CI_PROJECT_ID\`
9375
- - Header: \`Private-Token: $GITLAB_PERSONAL_ACCESS_TOKEN\`
9477
+ const outputDisciplineMR = ({ agentUserName }) => `
9478
+ ## Output Discipline (MR)
9479
+ - Output only \`gitlab-mcp\` tool calls and the final plain-text summary.
9480
+ - Do **not** merge the MR yourself under any circumstance.
9481
+ - Never include "@${agentUserName}" in any body.
9376
9482
  `;
9377
- // ---------- Public builders ----------
9378
- const getEventPrompt = ({ agentUserName }) => `
9483
+ /* ---------- Shared bundle for MR review ---------- */
9484
+ const mrReviewBundle = (ctx) => `
9485
+ ## MR Review Mode (execute ONLY when review intent is detected and an MR IID is resolved)
9486
+ Resolved MR IID: <set this to the resolved \`mr_iid\` before executing>
9487
+ ${mrScope(ctx)}
9488
+ ${mrWorkflow()}
9489
+ ${ciInspection()}
9490
+ `;
9491
+ /* ---------- Public builders ---------- */
9492
+ const getEventPrompt = (ctx) => `
9379
9493
  You are a GitLab assistant bot. You receive ONE raw GitLab webhook JSON payload.
9380
9494
 
9381
9495
  ${header()}
@@ -9384,16 +9498,20 @@ event_json:
9384
9498
  $(cat $TRIGGER_PAYLOAD)
9385
9499
  ---
9386
9500
 
9387
- ${identity({ agentUserName })}
9388
- ${goldenRules({ agentUserName })}
9501
+ ${identity(ctx)}
9502
+ ${goldenRules(ctx)}
9503
+ ${selfMentionGuard(ctx)}
9389
9504
  ${eventSelfParse()}
9390
- ${eventWorkflow()}
9505
+ ${singleRunnerGuard()} <!-- NEW: included so the agent can run it when acting on an existing MR -->
9506
+ ${reviewOnDemandFromEvents()}
9507
+ ${mrReviewBundle(ctx)} <!-- Included so the agent can execute it when review intent is true -->
9508
+ ${eventWorkflow(ctx)}
9391
9509
  ${commentGuidelines()}
9392
- ${mcpAndApi()}
9393
- ${outputDiscipline({ agentUserName })}
9510
+ ${mcpOnly()}
9511
+ ${outputDiscipline(ctx)}
9394
9512
  `;
9395
9513
  exports.getEventPrompt = getEventPrompt;
9396
- const getMergeRequestPrompt = ({ agentUserName }) => `
9514
+ const getMergeRequestPrompt = (ctx) => `
9397
9515
  You are a GitLab assistant bot reviewing and updating a single Merge Request (MR).
9398
9516
 
9399
9517
  ${header()}
@@ -9403,21 +9521,14 @@ title: $CI_MERGE_REQUEST_TITLE
9403
9521
  description: $CI_MERGE_REQUEST_DESCRIPTION
9404
9522
  ---
9405
9523
 
9406
- ${mrScope({ agentUserName })}
9407
- ${goldenRules({ agentUserName })}
9524
+ ${mrScope(ctx)}
9525
+ ${goldenRules(ctx)}
9526
+ ${selfMentionGuard(ctx)}
9408
9527
  ${mrWorkflow()}
9528
+ ${ciInspection()}
9409
9529
  ${commentGuidelines()}
9410
- ${mcpAndApi()}
9411
- ${fallbackApiAuth()}
9412
- ## Output Discipline (MR)
9413
- - Prefer \`gitlab-mcp\` tool calls; if unavailable, provide explicit REST calls (method, url, headers, body).
9414
- - At the end, print a **plain-text** summary to STDOUT including:
9415
- - \`source_branch\` and \`target_branch\`
9416
- - commits pushed (short SHAs)
9417
- - number of files changed
9418
- - CI status/result
9419
- - reviewers requested (if any)
9420
- - Do **not** merge the MR yourself under any circumstance.
9530
+ ${mcpOnly()}
9531
+ ${outputDisciplineMR(ctx)}
9421
9532
  `;
9422
9533
  exports.getMergeRequestPrompt = getMergeRequestPrompt;
9423
9534
  //# sourceMappingURL=prompts.js.map
@@ -9432,10 +9543,12 @@ exports.getMergeRequestPrompt = getMergeRequestPrompt;
9432
9543
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9433
9544
  exports.callClaude = exports.baseSetupScript = exports.createBaseAgentJob = void 0;
9434
9545
  const bashEscape_1 = __nccwpck_require__(12463);
9546
+ const runner_1 = __nccwpck_require__(4058);
9435
9547
  const createBaseAgentJob = (context) => ({
9436
9548
  stage: "agents",
9437
9549
  envMode: "none",
9438
- image: "node:24-alpine3.21",
9550
+ // image: "node:24-alpine3.21",
9551
+ image: (0, runner_1.getRunnerImage)("agent-claude"),
9439
9552
  variables: {
9440
9553
  MAX_MCP_OUTPUT_TOKENS: "75000",
9441
9554
  GITLAB_PERSONAL_ACCESS_TOKEN: "$AGENT_GITLAB_PERSONAL_ACCESS_TOKEN", // TODO: we don't have global secret keys to configure yet
@@ -9444,16 +9557,17 @@ const createBaseAgentJob = (context) => ({
9444
9557
  });
9445
9558
  exports.createBaseAgentJob = createBaseAgentJob;
9446
9559
  exports.baseSetupScript = [
9447
- "apk update",
9448
- "apk add --no-cache git curl bash",
9449
- "npm install -g @anthropic-ai/claude-code",
9560
+ // these are done in the image already
9561
+ // "apk update",
9562
+ // "apk add --no-cache git curl bash",
9563
+ //"npm install -g @anthropic-ai/claude-code",
9450
9564
  "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",
9451
9565
  ];
9452
9566
  const callClaude = ({ prompt }) => {
9453
9567
  return [
9454
9568
  `export PROMPT="${(0, bashEscape_1.escapeNewlines)((0, bashEscape_1.escapeDoubleQuotes)((0, bashEscape_1.escapeBackTicks)(prompt)))}"`,
9455
9569
  //'echo "$PROMPT"',
9456
- `claude -p "$PROMPT" --permission-mode acceptEdits --allowedTools "Bash(*) Read(*) Edit(*) Write(*) mcp__gitlab" --verbose --debug`,
9570
+ `claude -p "$PROMPT" --permission-mode acceptEdits --allowedTools "Bash(*) Bash(git checkout:*) Read(*) Edit(*) Write(*) mcp__gitlab" --verbose --debug`,
9457
9571
  ];
9458
9572
  };
9459
9573
  exports.callClaude = callClaude;
@@ -1,5 +1,22 @@
1
1
  import type { Config } from "../../types";
2
- import type { AgentContext } from "../../types/context";
2
+ export declare class AgentContext {
3
+ private readonly agentName;
4
+ private readonly config;
5
+ constructor(agentName: string, config: Config);
6
+ readonly type = "agent";
7
+ get name(): string;
8
+ get agentConfig(): import("../../types").AgentConfig;
9
+ get agentUser(): {
10
+ username: string;
11
+ userId: string;
12
+ };
13
+ get reviews(): {
14
+ byUser: Record<string, {
15
+ automatic: boolean;
16
+ }> | "all-automatic";
17
+ };
18
+ get fullConfig(): Config;
19
+ }
3
20
  export declare const createAgentContext: (ctx: {
4
21
  agentName: string;
5
22
  config: Config;
@@ -1,19 +1,45 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createAgentContext = void 0;
4
- const createAgentContext = async (ctx) => {
5
- var _a;
6
- const agentConfig = (_a = ctx.config.agents) === null || _a === void 0 ? void 0 : _a[ctx.agentName];
7
- if (!agentConfig) {
8
- throw new Error(`Agent ${ctx.agentName} not found in config`);
3
+ exports.createAgentContext = exports.AgentContext = void 0;
4
+ class AgentContext {
5
+ constructor(agentName, config) {
6
+ this.agentName = agentName;
7
+ this.config = config;
8
+ this.type = "agent";
9
+ }
10
+ get name() {
11
+ return this.agentName;
12
+ }
13
+ get agentConfig() {
14
+ var _a;
15
+ const agentConfig = (_a = this.config.agents) === null || _a === void 0 ? void 0 : _a[this.agentName];
16
+ if (!agentConfig) {
17
+ throw new Error(`Agent ${this.agentName} not found in config`);
18
+ }
19
+ return agentConfig;
20
+ }
21
+ get agentUser() {
22
+ var _a, _b, _c, _d;
23
+ return {
24
+ username: (_b = (_a = this.agentConfig.agentUser) === null || _a === void 0 ? void 0 : _a.username) !== null && _b !== void 0 ? _b : "agent.claude",
25
+ userId: (_d = (_c = this.agentConfig.agentUser) === null || _c === void 0 ? void 0 : _c.userId) !== null && _d !== void 0 ? _d : "$DEFAULT_AGENT_USER_ID",
26
+ };
9
27
  }
10
- return {
11
- type: "agent",
12
- name: ctx.agentName,
13
- //env: ctx.env,
14
- fullConfig: ctx.config,
15
- agentConfig: agentConfig,
16
- };
28
+ get reviews() {
29
+ var _a, _b;
30
+ return {
31
+ byUser: (_b = (_a = this.agentConfig.reviews) === null || _a === void 0 ? void 0 : _a.byUser) !== null && _b !== void 0 ? _b : {
32
+ [this.agentUser.username]: { automatic: true },
33
+ },
34
+ };
35
+ }
36
+ get fullConfig() {
37
+ return this.config;
38
+ }
39
+ }
40
+ exports.AgentContext = AgentContext;
41
+ const createAgentContext = async (ctx) => {
42
+ return new AgentContext(ctx.agentName, ctx.config);
17
43
  };
18
44
  exports.createAgentContext = createAgentContext;
19
45
  //# sourceMappingURL=createAgentContext.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createAgentContext.js","sourceRoot":"","sources":["../../../../../../pipeline/src/pipeline/agent/createAgentContext.ts"],"names":[],"mappings":";;;AAGO,MAAM,kBAAkB,GAAG,KAAK,EAAE,GAGxC,EAAyB,EAAE;;IAC1B,MAAM,WAAW,GAAG,MAAA,GAAG,CAAC,MAAM,CAAC,MAAM,0CAAG,GAAG,CAAC,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC,SAAS,sBAAsB,CAAC,CAAC;IAChE,CAAC;IACD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,GAAG,CAAC,SAAS;QACnB,eAAe;QACf,UAAU,EAAE,GAAG,CAAC,MAAM;QACtB,WAAW,EAAE,WAAW;KACzB,CAAC;AACJ,CAAC,CAAC;AAfW,QAAA,kBAAkB,sBAe7B"}
1
+ {"version":3,"file":"createAgentContext.js","sourceRoot":"","sources":["../../../../../../pipeline/src/pipeline/agent/createAgentContext.ts"],"names":[],"mappings":";;;AAEA,MAAa,YAAY;IACvB,YACmB,SAAiB,EACjB,MAAc;QADd,cAAS,GAAT,SAAS,CAAQ;QACjB,WAAM,GAAN,MAAM,CAAQ;QAGjB,SAAI,GAAG,OAAO,CAAC;IAF5B,CAAC;IAIJ,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,IAAI,WAAW;;QACb,MAAM,WAAW,GAAG,MAAA,IAAI,CAAC,MAAM,CAAC,MAAM,0CAAG,IAAI,CAAC,SAAS,CAAC,CAAC;QACzD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,SAAS,sBAAsB,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,IAAI,SAAS;;QACX,OAAO;YACL,QAAQ,EAAE,MAAA,MAAA,IAAI,CAAC,WAAW,CAAC,SAAS,0CAAE,QAAQ,mCAAI,cAAc;YAChE,MAAM,EAAE,MAAA,MAAA,IAAI,CAAC,WAAW,CAAC,SAAS,0CAAE,MAAM,mCAAI,wBAAwB;SACvE,CAAC;IACJ,CAAC;IAED,IAAI,OAAO;;QACT,OAAO;YACL,MAAM,EAAE,MAAA,MAAA,IAAI,CAAC,WAAW,CAAC,OAAO,0CAAE,MAAM,mCAAI;gBAC1C,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;aAC/C;SACF,CAAC;IACJ,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF;AAtCD,oCAsCC;AAEM,MAAM,kBAAkB,GAAG,KAAK,EAAE,GAGxC,EAAE,EAAE;IACH,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;AACrD,CAAC,CAAC;AALW,QAAA,kBAAkB,sBAK7B"}