{
  "name": "C: Jenkins \ube4c\ub4dc \uc54c\ub9bc + AI \uc5d0\ub7ec \uc608\uce21",
  "nodes": [
    {
      "parameters": {
        "content": "## \u2699\ufe0f C: Jenkins \ube4c\ub4dc \uc54c\ub9bc \u2014 \uc124\uc815 \uac00\uc774\ub4dc\n\n### 1\ufe0f\u20e3 Config \ub178\ub4dc\uc5d0\uc11c \uc218\uc815\n- `mm_webhook_url` \u2014 Mattermost Incoming Webhook URL\n  - Mattermost \u2192 \ucc44\ub110 \uba54\ub274 \u2192 Integrations \u2192 Incoming Webhooks\uc5d0\uc11c \uc0dd\uc131\n- `gitlab_base_url` \u2014 GitLab \uc11c\ubc84 \uc8fc\uc18c (\uae30\ubcf8\uac12 `https://lab.ssafy.com`)\n- `job_display_names` \u2014 Jenkins \uc7a1 \uc774\ub984 \u2192 \uc54c\ub9bc\uc5d0 \ud45c\uc2dc\ud560 \uc774\ub984 JSON\n  - \uc608: `{\"my-backend-job\": \"Backend Spring\"}`\n- `link_[\uc7a1\uc774\ub984]_[\ube0c\ub79c\uce58]` \u2014 \ube4c\ub4dc \uc131\uacf5 \uc2dc \uc54c\ub9bc\uc5d0 \ud3ec\ud568\ud560 \uc11c\ube44\uc2a4 \ub9c1\ud06c\n  - \uc7a1\uc774\ub984\uc758 `-`\ub294 `_`\ub85c \ubcc0\ud658 (\uc608: `link_dev_backend_spring_develop`)\n  - \ub9c1\ud06c\uac00 \ud544\uc694\uc5c6\ub294 \uacbd\uc6b0 \ube48 \ubb38\uc790\uc5f4 `\"\"` \uc720\uc9c0\n\n### 2\ufe0f\u20e3 n8n Credentials \ub4f1\ub85d\n- **Google Gemini(PaLM) API**: Gemini API Key\n  - `AI Agent` \u2192 `Google Gemini Chat Model` \ub178\ub4dc\uc5d0 \uc801\uc6a9\n- **GitLab API**: Personal Access Token (`api` \uc2a4\ucf54\ud504)\n  - `Get MR by Commit`, `Get MR Changes` \ub178\ub4dc\uc5d0 \uc801\uc6a9\n\n### 3\ufe0f\u20e3 Jenkinsfile \uc124\uc815\nJenkins `post` \ube14\ub85d\uc5d0\uc11c n8n Webhook\uc73c\ub85c \uc544\ub798 \ud544\ub4dc\ub97c \uc804\uc1a1\ud574\uc57c \ud569\ub2c8\ub2e4:\n```\nstatus, branch, build_title, build_url, author,\nbuild_number, job_name, git_commit,\ngit_project_path, error_log (\uc2e4\ud328 \uc2dc\ub9cc)\n```\n\n### 4\ufe0f\u20e3 Jenkins Webhook URL\n`https://YOUR_N8N_HOST/webhook/jenkins-build`",
        "height": 440,
        "width": 460,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        19616,
        6640
      ],
      "id": "6f1d6290-995f-4b02-b1cb-4720de1cab4d",
      "name": "Setup Guide"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-c-1",
              "name": "mm_webhook_url",
              "value": "https://YOUR_MATTERMOST_DOMAIN/hooks/YOUR_WEBHOOK_TOKEN",
              "type": "string"
            },
            {
              "id": "cfg-c-4",
              "name": "job_display_names",
              "value": "{\"dev-backend-spring\": \"DEV Backend Spring\", \"dev-backend-fastapi\": \"DEV Backend FastAPI\", \"dev-frontend-dashboard\": \"DEV Frontend Dashboard\", \"dev-frontend-app\": \"DEV Frontend App (PWA)\"}",
              "type": "string"
            },
            {
              "id": "cfg-c-5",
              "name": "gitlab_base_url",
              "value": "https://lab.ssafy.com",
              "type": "string"
            },
            {
              "id": "cfg-link-1",
              "name": "link_dev_backend_spring_develop",
              "value": "[ :springboot: **Backend Spring Test Server**](https://YOUR_BACKEND_URL/swagger-ui/index.html)",
              "type": "string"
            },
            {
              "id": "cfg-link-2",
              "name": "link_dev_backend_spring_master",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-link-3",
              "name": "link_dev_backend_fastapi_develop",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-link-4",
              "name": "link_dev_backend_fastapi_master",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-link-5",
              "name": "link_dev_frontend_dashboard_develop",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-link-6",
              "name": "link_dev_frontend_dashboard_master",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-link-7",
              "name": "link_dev_frontend_app_develop",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-link-8",
              "name": "link_dev_frontend_app_master",
              "value": "[:electron: **\uc571 \uc774\ub984 Desktop App Download**](https://YOUR_CDN_URL/app-download)\n\n[:react: **\uc571 \uc774\ub984 Web Service**](https://YOUR_WEB_SERVICE_URL)",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        19920,
        7152
      ],
      "id": "07871fb4-eea6-4ede-888d-65f746cd8df4",
      "name": "Config"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "c-if-cond",
              "leftValue": "={{ $json.body.status }}",
              "rightValue": "success",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        20144,
        7152
      ],
      "id": "39ceb366-75db-4d80-9ff5-ff765c0d7331",
      "name": "IF: Build Success?"
    },
    {
      "parameters": {
        "jsCode": "const webhookBody = $input.item.json.body;\nconst config = $('Config').item.json;\n\nconst branch = webhookBody?.branch || 'unknown';\nconst buildTitle = webhookBody?.build_title || '';\nconst author = webhookBody?.author || '';\nconst buildUrl = webhookBody?.build_url || '';\nconst buildNumber = webhookBody?.build_number || '';\nconst jobName = webhookBody?.job_name || '';\n\nlet jobDisplayMap = {};\ntry { jobDisplayMap = JSON.parse(config.job_display_names || '{}'); } catch(e) {}\nconst jobDisplayName = jobDisplayMap[jobName] || jobName;\n\nreturn [{\n  json: {\n    branch,\n    buildTitle,\n    author,\n    buildUrl,\n    buildNumber,\n    jobName,\n    jobDisplayName\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        20368,
        7056
      ],
      "id": "91c6a551-11bd-4a4c-bc0a-c08e5439b993",
      "name": "Success Context"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Config').item.json.mm_webhook_url }}",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "={{ $('Build Success Message').item.json.text }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        20816,
        7056
      ],
      "id": "34a9dcb5-3193-45db-932d-59effa10c09a",
      "name": "MM: \ube4c\ub4dc \uc131\uacf5 \uc54c\ub9bc"
    },
    {
      "parameters": {
        "jsCode": "const webhookBody = $input.item.json.body;\nconst config = $('Config').item.json;\n\nconst errorLog = webhookBody?.error_log || '\uc5d0\ub7ec \ub85c\uadf8 \uc5c6\uc74c';\nconst branch = webhookBody?.branch || 'unknown';\nconst buildTitle = webhookBody?.build_title || '';\nconst author = webhookBody?.author || '';\nconst buildUrl = webhookBody?.build_url || '';\nconst buildNumber = webhookBody?.build_number || '';\nconst jobName = webhookBody?.job_name || '';\nconst gitCommit = webhookBody?.git_commit || '';\nconst gitProjectPath = webhookBody?.git_project_path || '';\n\nlet jobDisplayMap = {};\ntry { jobDisplayMap = JSON.parse(config.job_display_names || '{}'); } catch(e) {}\nconst jobDisplayName = jobDisplayMap[jobName] || jobName;\n\nconst prompt = `Jenkins \ube4c\ub4dc\uac00 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \uc5d0\ub7ec \ub85c\uadf8\ub97c \ubd84\uc11d\ud574\uc11c \uc2e4\ud328 \uc6d0\uc778\uc744 \uc608\uce21\ud558\uace0 \ud574\uacb0 \ubc29\ubc95\uc744 \ud55c\uad6d\uc5b4\ub85c \uac04\uacb0\ud558\uac8c \uc81c\uc548\ud574\uc8fc\uc138\uc694.\\n\\n## \ube4c\ub4dc \uc815\ubcf4\\n- \ube0c\ub79c\uce58: ${branch}\\n- \uc791\uc131\uc790: ${author}\\n- MR/\ucee4\ubc0b: ${buildTitle}\\n\\n## \uc5d0\ub7ec \ub85c\uadf8\\n\\`\\`\\`\\n${errorLog.substring(0, 5000)}\\n\\`\\`\\``;\n\nreturn [{\n  json: {\n    prompt,\n    branch,\n    buildTitle,\n    author,\n    buildUrl,\n    buildNumber,\n    jobName,\n    jobDisplayName,\n    gitCommit,\n    gitProjectPath,\n    errorLog: errorLog.substring(0, 3000)\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        20368,
        7248
      ],
      "id": "7477f0a3-b41e-4534-afd5-91bcccea890c",
      "name": "Build Analysis Prompt"
    },
    {
      "parameters": {
        "url": "={{ $('Config').item.json.gitlab_base_url + '/api/v4/projects/' + $json.projectPath + '/merge_requests/' + $json.mrIid + '/changes' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "gitlabApi",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        21264,
        7184
      ],
      "id": "65fd8e32-1655-413d-b4f7-37e98e966022",
      "name": "Get MR Changes",
      "credentials": {
        "gitlabApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const changes = $input.item.json.changes || [];\nlet processedChanges = [];\n\nconst skipExtensions = [\n  '.jpg', '.jpeg', '.png', '.gif', '.svg', '.ico', '.webp', '.bmp', '.tiff',\n  '.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a',\n  '.mp4', '.webm', '.avi', '.mov', '.mkv', '.flv',\n  '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',\n  '.zip', '.rar', '.tar', '.gz', '.7z',\n  '.bin', '.exe', '.dll', '.so', '.dylib',\n  '.ttf', '.otf', '.woff', '.woff2',\n  '.psd', '.ai', '.sketch'\n];\n\nfunction shouldProcessFile(filePath) {\n  const lowerPath = filePath.toLowerCase();\n  for (const ext of skipExtensions) {\n    if (lowerPath.endsWith(ext)) return false;\n  }\n  const skipDirs = ['node_modules/', 'dist/', 'build/'];\n  for (const dir of skipDirs) {\n    if (lowerPath.includes(dir)) return false;\n  }\n  return true;\n}\n\nfunction isBinaryContent(diff) {\n  if (diff.includes('Binary files') && diff.includes('differ')) return true;\n  if (diff.includes('\\0')) return true;\n  const base64Pattern = /^[A-Za-z0-9+/=]{20,}$/m;\n  const lines = diff.split('\\n');\n  let base64Lines = 0;\n  for (let i = 0; i < Math.min(lines.length, 10); i++) {\n    if (base64Pattern.test(lines[i])) base64Lines++;\n  }\n  return base64Lines > 5;\n}\n\nfunction getChangeType(change) {\n  const { new_path, old_path, diff } = change;\n  if (new_path === '/dev/null' || diff.startsWith('deleted file mode')) return 'delete';\n  if (old_path !== new_path && old_path !== '/dev/null') {\n    const actualDiffLines = diff.split('\\n')\n      .filter(line => line.startsWith('+') || line.startsWith('-'))\n      .filter(line => !line.startsWith('+++') && !line.startsWith('---'));\n    if (actualDiffLines.length === 0) return 'rename';\n    return 'rename+modify';\n  }\n  if (old_path === '/dev/null' || diff.startsWith('new file mode')) return 'add';\n  return 'modify';\n}\n\nfor (const change of changes) {\n  const { old_path, new_path, diff } = change;\n  const changeType = getChangeType(change);\n  if ((changeType === 'add' || changeType === 'modify' || changeType === 'rename+modify') &&\n      shouldProcessFile(new_path) &&\n      !isBinaryContent(diff)) {\n    const isTooLarge = diff.length > 10000;\n    const processedDiff = isTooLarge\n      ? `${diff.substring(0, 5000)}... (\ub0b4\uc6a9 \uc0dd\ub7b5) ...${diff.substring(diff.length - 5000)}`\n      : diff;\n    processedChanges.push({\n      file_path: new_path,\n      old_path: old_path !== new_path ? old_path : null,\n      change_type: changeType,\n      is_summarized: isTooLarge,\n      diff: processedDiff\n    });\n  }\n}\n\nreturn { json: { changes: processedChanges } };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        21488,
        7184
      ],
      "id": "2d047c1d-8ef2-420d-bd5e-8a98ef6c0bcc",
      "name": "MR Preprocessing"
    },
    {
      "parameters": {
        "jsCode": "const base = $('Build Analysis Prompt').item.json;\nconst changes = $json.changes || [];\n\n// Set MR Info\uac00 MR URL\uc744 \ucc3e\uc558\uc73c\uba74 \uadf8\uac78 \uc6b0\uc120 \uc0ac\uc6a9\nlet resolvedBuildUrl = base.buildUrl;\ntry {\n  const mrUrl = $('Set MR Info').item.json.buildUrl;\n  if (mrUrl) resolvedBuildUrl = mrUrl;\n} catch(e) {}\n\nlet mrSection = '';\nif (changes.length > 0) {\n  mrSection = '\\n\\n## \uad00\ub828 MR \ubcc0\uacbd \ucf54\ub4dc\\n(\ube4c\ub4dc \uc2e4\ud328 \uc2dc\uc810 MR \ucf54\ub4dc \ubcc0\uacbd\uc0ac\ud56d)\\n\\n';\n  for (const change of changes.slice(0, 5)) {\n    mrSection += `### ${change.file_path}\\n\\`\\`\\`\\n${change.diff.substring(0, 1500)}\\n\\`\\`\\`\\n\\n`;\n  }\n}\n\nreturn [{\n  json: {\n    ...base,\n    buildUrl: resolvedBuildUrl,\n    prompt: base.prompt + mrSection\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        21712,
        7248
      ],
      "id": "ab9cda42-d996-4398-a530-10083e6cdf6e",
      "name": "Enrich AI Prompt"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Config').item.json.mm_webhook_url }}",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "={{ $('Build Failure Message').item.json.text }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        22512,
        7248
      ],
      "id": "1076cd43-05e2-421d-b65d-7808179b3020",
      "name": "MM: \ube4c\ub4dc \uc2e4\ud328 + AI \uc608\uce21"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.prompt }}",
        "options": {
          "systemMessage": "\ub2f9\uc2e0\uc740 CI/CD \ube4c\ub4dc \uc624\ub958 \ubd84\uc11d \uc804\ubb38\uac00\uc785\ub2c8\ub2e4. Jenkins \ube4c\ub4dc \uc2e4\ud328 \ub85c\uadf8\ub97c \ubd84\uc11d\ud558\uc5ec \uc6d0\uc778\uc744 \uc9c4\ub2e8\ud558\uace0 \ud574\uacb0 \ubc29\ubc95\uc744 \uc81c\uc548\ud569\ub2c8\ub2e4.\n\n\uc791\uc131 \uac00\uc774\ub4dc:\n- \ud55c\uad6d\uc5b4\ub85c \uc791\uc131\ud558\uc138\uc694\n- \uc5d0\ub7ec\uc758 \ud575\uc2ec \uc6d0\uc778\uc744 \uba3c\uc800 \uc124\uba85\ud558\uc138\uc694\n- MR \ubcc0\uacbd \ucf54\ub4dc\uac00 \uc81c\uacf5\ub41c \uacbd\uc6b0, \ucf54\ub4dc\uc758 \uc5b4\ub290 \ubd80\ubd84\uc774 \ubb38\uc81c\uc778\uc9c0 \uad6c\uccb4\uc801\uc73c\ub85c \uc9c0\uc801\ud558\uc138\uc694\n- \uad6c\uccb4\uc801\uc778 \ud574\uacb0 \ubc29\ubc95\uc744 \uc81c\uc548\ud558\uc138\uc694\n- \uac04\uacb0\ud558\uac8c \uc791\uc131\ud558\uc138\uc694 (500\uc790 \uc774\ub0b4)\n- \ub9c8\ud06c\ub2e4\uc6b4 \ud615\uc2dd(#, ##, -, * \ub4f1)\uc744 \uc0ac\uc6a9\ud558\uc9c0 \ub9c8\uc138\uc694"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.8,
      "position": [
        21936,
        7248
      ],
      "id": "338999c2-8f15-4f3c-bbce-b02402f1b066",
      "name": "AI Agent"
    },
    {
      "parameters": {
        "options": {
          "temperature": 0.2
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        21936,
        7440
      ],
      "id": "65ca9842-e08d-422c-987b-4ceccce7fc72",
      "name": "Google Gemini Chat Model",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "={{ $('Config').item.json.gitlab_base_url + '/api/v4/projects/' + encodeURIComponent($('Build Analysis Prompt').item.json.gitProjectPath) + '/repository/commits/' + $('Build Analysis Prompt').item.json.gitCommit + '/merge_requests' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "gitlabApi",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        20592,
        7248
      ],
      "id": "b143b247-44d4-488f-b2e8-b6dd57e60012",
      "name": "Get MR by Commit",
      "credentials": {
        "gitlabApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const base = $('Build Analysis Prompt').item.json;\nconst response = $input.item.json;\n\n// GitLab API returns array; n8n may parse as array or single object\nconst mrList = Array.isArray(response) ? response : [response];\nconst mr = mrList[0];\n\nif (!mr || !mr.iid) {\n  return [{ json: { ...base, mrIid: '', projectPath: '', buildUrl: base.buildUrl } }];\n}\n\nconst mrIid = String(mr.iid);\nconst mrWebUrl = mr.web_url || '';\nconst gitlabBaseUrl = $('Config').item.json.gitlab_base_url;\nlet projectPath = '';\nconst pathMatch = mrWebUrl.replace(gitlabBaseUrl, '').match(/^\\/(.+?)\\/-\\/merge_requests/);\nif (pathMatch) {\n  projectPath = encodeURIComponent(pathMatch[1]);\n}\n\nreturn [{\n  json: {\n    ...base,\n    mrIid,\n    projectPath,\n    buildUrl: mrWebUrl || base.buildUrl\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        20816,
        7248
      ],
      "id": "871a52b9-14e4-4679-a7d3-804c77d84f11",
      "name": "Set MR Info"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "c-got-mr-cond",
              "leftValue": "={{ $json.mrIid }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEquals",
                "name": "filter.operator.notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        21040,
        7248
      ],
      "id": "be1f2743-610a-42b3-9842-35e7c4c66cb6",
      "name": "IF: Got MR?"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "jenkins-build",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        19696,
        7152
      ],
      "id": "b7d59aa9-65fa-4ce1-89a9-53aaf4640949",
      "name": "Jenkins Webhook"
    },
    {
      "parameters": {
        "jsCode": "const b = $('Build Analysis Prompt').item.json;\nconst buildUrl = $('Enrich AI Prompt').item.json.buildUrl;\nconst aiOutput = $json.output || '';\n\nlet text = '@here\\n#### :jenkins5: Jenkins Pipeline Failure :jenkins5:\\n';\ntext += '- **Target Branch:** `' + b.branch + '`\\n';\ntext += '- **Target Build:** [**' + b.jobName + '#' + b.buildNumber + '**](' + $('Jenkins Webhook').first().json.body.build_url + ')\\n';\ntext += '- [**\uad00\ub828 MR \ubc14\ub85c \uac00\uae30 :animal_bingo_head:**](' + buildUrl + ')\\n';\ntext += '---\\n';\ntext += '##### :gunpang_question: AI \uc608\uce21 \uc6d0\uc778\\n```\\n';\ntext += aiOutput + '\\n```\\n';\ntext += '##### :gunpang_paper_log: \uc5d0\ub7ec\ub85c\uadf8\\n```\\n';\ntext += b.errorLog + '\\n```';\n\nreturn [{ json: { text } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        22288,
        7248
      ],
      "id": "70dbb401-c539-483a-827c-4f5b0d448f7d",
      "name": "Build Failure Message"
    },
    {
      "parameters": {
        "jsCode": "const branch         = $json.branch;\nconst buildNumber    = $json.buildNumber;\nconst jobName        = $json.jobName;\n\nconst linkKey = 'link_' + jobName.replace(/-/g, '_') + '_' + branch;\nconst serviceLinks = $('Config').item.json[linkKey] || '';\n\nlet text = '@here\\n';\ntext += '#### :shinchan_dance: Jenkins Pipeline Success :shinchan_dance:\\n';\ntext += '- **\ub300\uc0c1 \ube0c\ub79c\uce58:** `' + branch + '`\\n';\ntext += '- **Target Build:** [**' + jobName + '#' + buildNumber + '**](' + $input.first().json.buildUrl + ')\\n';\nif (serviceLinks) {\n  text += '\\n---\\n##### \uc11c\ube44\uc2a4 \uc810\uac80\ud558\ub7ec \uac00\uae30 :shin_hyeong_man:\\n';\n  text += serviceLinks;\n}\n\nreturn [{ json: { text } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        20592,
        7056
      ],
      "id": "e06fb760-fc32-43ae-8d21-dc5dd07b756c",
      "name": "Build Success Message"
    },
    {
      "parameters": {
        "content": "## \u2728 \uc774 \uc6cc\ud06c\ud50c\ub85c\uc6b0\uac00 \ud558\ub294 \uc77c\n\n**Jenkins \ube4c\ub4dc \uc131\uacf5 \uc2dc**\n\u2192 MM \ucc44\ub110\uc5d0 \uc131\uacf5 \uc54c\ub9bc \uc804\uc1a1\n\u2192 \ube0c\ub79c\uce58 + \ube4c\ub4dc \ubc88\ud638 + \uc11c\ube44\uc2a4 \ub9c1\ud06c \ud3ec\ud568\n\u2192 job\ubcc4/\ube0c\ub79c\uce58\ubcc4 \uc11c\ube44\uc2a4 \ub9c1\ud06c\ub294 Config \ub178\ub4dc\uc5d0\uc11c \ucee4\uc2a4\ud140 \uac00\ub2a5\n\n**Jenkins \ube4c\ub4dc \uc2e4\ud328 \uc2dc**\n\u2192 MM \ucc44\ub110\uc5d0 \uc2e4\ud328 \uc54c\ub9bc + AI \uc5d0\ub7ec \uc6d0\uc778 \uc608\uce21 \ud568\uaed8 \uc804\uc1a1\n\u2192 \uc5d0\ub7ec \ub85c\uadf8\ub97c Google Gemini AI\uac00 \ubd84\uc11d\ud574 \uc6d0\uc778\uacfc \ud574\uacb0 \ubc29\ubc95 \uc81c\uc548\n\u2192 \ube4c\ub4dc \uc2e4\ud328 \uc2dc\uc810\uc758 MR \ucf54\ub4dc \ubcc0\uacbd\uc0ac\ud56d\ub3c4 AI\uc5d0\uac8c \ud568\uaed8 \uc804\ub2ec (\ub354 \uc815\ud655\ud55c \ubd84\uc11d)\n\u2192 \uad00\ub828 MR \ub9c1\ud06c \ud3ec\ud568\n\n**AI \uc5d0\ub7ec \ubd84\uc11d \ud750\ub984**\n1. Jenkins\uac00 `git_commit` \uac12\uc744 \ud568\uaed8 \uc804\uc1a1\n2. GitLab API\ub85c \ud574\ub2f9 \ucee4\ubc0b\uc774 \ud3ec\ud568\ub41c MR \uc790\ub3d9 \uc870\ud68c\n3. MR \ucf54\ub4dc \ubcc0\uacbd\uc0ac\ud56d + \uc5d0\ub7ec \ub85c\uadf8\ub97c AI\uc5d0\uac8c \uc804\ub2ec\n4. AI \ubd84\uc11d \uacb0\uacfc\ub97c MM \uc54c\ub9bc\uc5d0 \ud3ec\ud568",
        "height": 340,
        "width": 460,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        19616,
        6180
      ],
      "id": "feature-guide-c-jenkins-001",
      "name": "Feature Guide Jenkins"
    }
  ],
  "connections": {
    "Jenkins Webhook": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "IF: Build Success?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Build Success?": {
      "main": [
        [
          {
            "node": "Success Context",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Analysis Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Success Context": {
      "main": [
        [
          {
            "node": "Build Success Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Analysis Prompt": {
      "main": [
        [
          {
            "node": "Get MR by Commit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get MR by Commit": {
      "main": [
        [
          {
            "node": "Set MR Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set MR Info": {
      "main": [
        [
          {
            "node": "IF: Got MR?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Got MR?": {
      "main": [
        [
          {
            "node": "Get MR Changes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Enrich AI Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get MR Changes": {
      "main": [
        [
          {
            "node": "MR Preprocessing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MR Preprocessing": {
      "main": [
        [
          {
            "node": "Enrich AI Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enrich AI Prompt": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Build Failure Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Build Failure Message": {
      "main": [
        [
          {
            "node": "MM: \ube4c\ub4dc \uc2e4\ud328 + AI \uc608\uce21",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Success Message": {
      "main": [
        [
          {
            "node": "MM: \ube4c\ub4dc \uc131\uacf5 \uc54c\ub9bc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false,
    "timeSavedMode": "fixed",
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": "YOUR_ERROR_WORKFLOW_ID"
  },
  "versionId": "262a414d-71d4-454a-b47c-daf2e7b0937b",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "L5I6CbWcD4e3sfNNhZzrB",
  "tags": []
}