GitLab コードレビューテンプレート
上級
これはAI Summarization, Multimodal AI分野の自動化ワークフローで、41個のノードを含みます。主にIf, Set, Code, Jira, Mergeなどのノードを使用。 Gemini AIとJIRAコンテキストを使用したGitLabマージンリクエストコードレビューの自動化
前提条件
- •HTTP Webhookエンドポイント(n8nが自動生成)
- •ターゲットAPIの認証情報が必要な場合あり
- •Google Gemini API Key
使用ノード (41)
ワークフロープレビュー
ノード接続関係を可視化、ズームとパンをサポート
ワークフローをエクスポート
以下のJSON設定をn8nにインポートして、このワークフローを使用できます
{
"id": "Rwpn5OG2ql8rIOzH",
"meta": {
"instanceId": "ebde2b8e011b15f5db59a3b84e187c4795dbe1d4b2c695e9940aa4dbcd102f98",
"templateCredsSetupCompleted": true
},
"name": "Gitlab Code Review Template",
"tags": [],
"nodes": [
{
"id": "c3fedf3c-ed52-49d7-b9d5-30050d4324f2",
"name": "レビュー待ち",
"type": "n8n-nodes-base.if",
"position": [
-4256,
1260
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "617eb2c5-dd4b-4e28-b533-0c32ea6ca961",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.object_attributes.note }}",
"rightValue": "coro-bot-review"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "d30412f1-f714-41cb-a867-3a45f202c378",
"name": "ファイル変更をスキップ",
"type": "n8n-nodes-base.if",
"position": [
-2016,
1408
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c6e1430b-84a7-47ce-8fe9-7b94da0f2d31",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.changes.renamed_file }}",
"rightValue": ""
},
{
"id": "bf6e9eb9-d72d-459c-a722-9614bab8842c",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.changes.deleted_file }}",
"rightValue": ""
},
{
"id": "03200577-a262-4f46-ad25-9c15b0c8146d",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.changes.diff }}",
"rightValue": "@@"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "17752d5e-75d2-43ff-8e1b-ff439e6329b8",
"name": "基本LLMチェーン",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
-1344,
1104
],
"parameters": {
"text": "=First, consider the context from the associated JIRA ticket:\n------------\n{{ $json.jiraParentContext || $json.jiraContext || 'No JIRA context was provided.' }}\n------------\n\nFile path:{{ $('Skip File Changes').item.json.new_path }}\n\n```Original code\n {{ $json.originalCode }}\n```\nchange to\n```New code\n {{ $json.newCode }}\n```\nPlease review the code changes in this section:",
"messages": {
"messageValues": [
{
"message": "=You are an automated code review bot. Your primary goal is to identify high-value issues in code.\n\n**Instructions:**\n\n1. **Analysis Focus:** Concentrate only on business logic, correctness, security, and performance. Ignore style, naming, filenames, version bumps, and non-source files. Assume placeholders for yml files will be filled with correct values.\n2. **Output for Issues:** When you find an issue, begin your response with the prefix `🤖 **AI Review:** `. Follow it with a concise, actionable explanation. Prefer one-liners that are immediately actionable - what or where or why and how to fix.\n3. **Output for No Issues:** If your analysis finds **zero** issues worth reporting, your **ENTIRE** response must be the single keyword: `ALL_CLEAR`. Do not add any other text or explanation.\n4. ** Findings to ignore in yaml files:**\n - Ignore if a value for vaultSecretPath in a values file or a yaml contains a double slash (//)\n - Ignore errors where tag appears to be very or too specific\n\n**Important Rules:**\n\n* NEVER write praise, summaries, or conversational phrases like \"I have no findings.\"\n* If you find issues, start every comment with the `🤖 **AI Review:** ` prefix.\n* If you find no issues, don't output anything."
}
]
},
"promptType": "define"
},
"typeVersion": 1.5
},
{
"id": "483ec727-0b0b-46a7-9bd9-fbd52f2bb51a",
"name": "Google Gemini チャットモデル",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-1272,
1328
],
"parameters": {
"options": {
"topK": 1,
"temperature": 0
},
"modelName": "models/gemini-2.5-pro"
},
"credentials": {
"googlePalmApi": {
"id": "GeKFUmcR07GOvVpv",
"name": "Google Gemini(PaLM) Api account 2"
}
},
"typeVersion": 1
},
{
"id": "06884594-3ad3-4329-b357-21359eb13503",
"name": "JIRA 課題IDを抽出",
"type": "n8n-nodes-base.code",
"position": [
-3584,
1304
],
"parameters": {
"jsCode": "// Get the MR description from the previous node's input\nconst description = $input.first().json.description\n// Regex to find a JIRA issue key (e.g., PROJ-123).\n// The \\b ensures it matches a whole word.\nconst jiraRegex = /\\b([A-Z]+-\\d+)\\b/;\nconst match = description.match(jiraRegex);\n\n// If a key is found, return it.\nif (match && match[0]) {\n // We name the output 'jiraIssueKey' for clarity.\n return [{\n json: {\n jiraIssueKey: match[0]\n }\n }];\n} else {\n // Return an empty array to stop this path if no key is found.\n return [];\n}"
},
"typeVersion": 2
},
{
"id": "24204fee-6c47-4026-a6a6-12ced75a49da",
"name": "JIRA 課題を取得",
"type": "n8n-nodes-base.jira",
"position": [
-3360,
1304
],
"parameters": {
"issueKey": "={{ $json.jiraIssueKey }}",
"operation": "get",
"additionalFields": {}
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "T58uHg2XK8A2NA7s",
"name": "Jira SW Cloud account"
}
},
"typeVersion": 1
},
{
"id": "7b1da623-5452-4783-ba8f-77896d3c8ea9",
"name": "JIRA コンテキストを整形",
"type": "n8n-nodes-base.set",
"position": [
-2688,
1452
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "59f4091a-0260-4f24-aa8d-c211f7c243e2",
"name": "jiraContext",
"type": "string",
"value": "=JIRA Ticket Context: \nTitle: {{ $json.fields.summary }} \nType: {{ $json.fields.issuetype.name }} \nDescription: {{ $json.fields.description }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "2ac7ced8-60b5-4fab-902f-bd1360e41a3a",
"name": "MR詳細を抽出",
"type": "n8n-nodes-base.set",
"position": [
-3808,
1356
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "59f4091a-0260-4f24-aa8d-c211f7c243e2",
"name": "projectId",
"type": "string",
"value": "={{ $json.body.project_id }}"
},
{
"id": "cc5bdd4b-812f-461d-b3a9-059994176291",
"name": "iid",
"type": "string",
"value": "={{ $json.body.merge_request.iid }}"
},
{
"id": "7afeacf0-888f-4e75-be3c-d024cd223e3b",
"name": "description",
"type": "string",
"value": "={{ $json.body.merge_request.description }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "9aeed83e-1f0b-461a-8e2b-75efb7be5ca5",
"name": "JIRA サブタスクの場合",
"type": "n8n-nodes-base.if",
"position": [
-3136,
1260
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5338bc5b-9f30-4b66-b6a2-5b85dc012765",
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.fields.parent }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "bf6f14f1-b684-4100-9085-f97399c8911f",
"name": "JIRA 親課題を取得",
"type": "n8n-nodes-base.jira",
"position": [
-2912,
1260
],
"parameters": {
"issueKey": "={{ $json.fields.parent.key }}",
"operation": "get",
"additionalFields": {}
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "T58uHg2XK8A2NA7s",
"name": "Jira SW Cloud account"
}
},
"typeVersion": 1
},
{
"id": "5e547d2f-c978-42bc-acb2-25c194308154",
"name": "JIRA 親コンテキストを整形",
"type": "n8n-nodes-base.set",
"position": [
-2688,
1260
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "59f4091a-0260-4f24-aa8d-c211f7c243e2",
"name": "jiraParentContext",
"type": "string",
"value": "=JIRA Parent Ticket Context: \nTitle: {{ $json.fields.summary }} \nType: {{ $json.fields.issuetype.name }} \nDescription: {{ $json.fields.description }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "d5e3ffd7-03d0-4b54-ab34-67fe43830531",
"name": "マージ",
"type": "n8n-nodes-base.merge",
"position": [
-2464,
1392
],
"parameters": {
"mode": "combine",
"options": {
"includeUnpaired": true
},
"combineBy": "combineByPosition",
"numberInputs": 3
},
"typeVersion": 3.2
},
{
"id": "a49bf76b-1a4b-4aba-b392-419ddc07e110",
"name": "MR変更を取得",
"type": "n8n-nodes-base.httpRequest",
"position": [
-2688,
1644
],
"parameters": {
"url": "=https://gitlab.com/api/v4/projects/{{ $json.projectId }}/merge_requests/{{ $json.iid }}/changes",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "PRIVATE-TOKEN",
"value": "={{$env.GITLAB_TOKEN}}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "8a3beea4-640b-4ddc-a801-719ace2643d0",
"name": "コード変更を準備",
"type": "n8n-nodes-base.code",
"position": [
-1568,
1408
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nvar diff = $input.item.json.gitDiff\n\nlet lines = diff.trimEnd().split('\\n');\n\nlet originalCode = '';\nlet newCode = '';\n\nlines.forEach(line => {\n console.log(line)\n if (line.startsWith('-')) {\n originalCode += line + \"\\n\";\n } else if (line.startsWith('+')) {\n newCode += line + \"\\n\";\n } else {\n originalCode += line + \"\\n\";\n newCode += line + \"\\n\";\n }\n});\n\nreturn { ...$json, originalCode, newCode };\n\n"
},
"typeVersion": 2
},
{
"id": "057832e9-fa3b-4fe5-a561-78b69e60c813",
"name": "エラートリガー",
"type": "n8n-nodes-base.errorTrigger",
"position": [
352,
1692
],
"parameters": {},
"executeOnce": false,
"retryOnFail": false,
"typeVersion": 1,
"alwaysOutputData": false
},
{
"id": "aeba4982-b0fc-4d97-a06b-35b559135361",
"name": "集約",
"type": "n8n-nodes-base.aggregate",
"position": [
-768,
1408
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "8ff7eb74-fbcc-4d4f-8b53-50ed4d543a82",
"name": "LLM出力と入力をマージ",
"type": "n8n-nodes-base.merge",
"position": [
-992,
1408
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "39f6aa6b-3296-47e6-b71d-59bcc7efd34c",
"name": "無関係なフィールドをフィルタリング",
"type": "n8n-nodes-base.set",
"position": [
-1280,
1504
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "f40092dc-5c3a-4882-b33c-29e8c50f90a6",
"name": "iid",
"type": "string",
"value": "={{$json.iid}}"
},
{
"id": "38bd9645-0f83-4b66-9850-403e675a360d",
"name": "project_id",
"type": "string",
"value": "={{$json.project_id}}"
},
{
"id": "6b432472-ad72-4eaf-977d-9b2c80cbe2bf",
"name": "diff_refs",
"type": "object",
"value": "={{$json.diff_refs}}"
},
{
"id": "d24732f4-3f12-4b77-8df1-f34edb0d7f4d",
"name": "lastNewLine",
"type": "string",
"value": "={{ $json.lastNewLine }}"
},
{
"id": "4baecc19-56b2-472e-a015-bfc2e36727ce",
"name": "lastOldLine",
"type": "string",
"value": "={{ $json.lastOldLine }}"
},
{
"id": "12489e32-3521-4fd1-af78-dcaaba028ee1",
"name": "new_path",
"type": "string",
"value": "={{ $json.changes.new_path }}"
},
{
"id": "602b5de5-aeab-43bf-8ccb-2fce3853278c",
"name": "old_path",
"type": "string",
"value": "={{ $json.changes.old_path }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "ebe94dfb-7395-4788-896d-14c3d5380c18",
"name": "問題は見つかりましたか?",
"type": "n8n-nodes-base.if",
"position": [
-320,
1408
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "70dd215a-813b-4bcf-9fa6-db4581ccad77",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.noIssuesFound }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "21e1a59a-3af6-45ba-83d8-73260736b92a",
"name": "変更を分割",
"type": "n8n-nodes-base.splitOut",
"position": [
-2240,
1408
],
"parameters": {
"include": "selectedOtherFields",
"options": {
"includeBinary": true
},
"fieldToSplitOut": "changes",
"fieldsToInclude": "jiraParentContext, jiraContext,iid,project_id,diff_refs"
},
"typeVersion": 1
},
{
"id": "43acb79d-02c5-406b-9ae0-288e365518fa",
"name": "コメントを分割",
"type": "n8n-nodes-base.splitOut",
"position": [
-96,
1428
],
"parameters": {
"include": "selectedOtherFields",
"options": {
"includeBinary": true
},
"fieldToSplitOut": "commentsToPost",
"fieldsToInclude": "iid,project_id,diff_refs"
},
"typeVersion": 1
},
{
"id": "95e75bb0-94c4-492d-af8f-e6d79f442d92",
"name": "リクエストを準備",
"type": "n8n-nodes-base.code",
"position": [
128,
1428
],
"parameters": {
"jsCode": "const item = $input.first().json;\n\n// Reference the nested 'commentsToPost' object\nconst postData = item.commentsToPost;\n\n// Create the nested position object\nconst position = {\n position_type: 'text',\n old_path: postData.old_path,\n new_path: postData.new_path,\n base_sha: postData.diff_refs.base_sha,\n start_sha: postData.diff_refs.start_sha,\n head_sha: postData.diff_refs.head_sha,\n};\n\n// Conditionally add the line numbers to the nested position object\nif (postData.lastNewLine !== null && postData.lastNewLine !== '') {\n position.new_line = parseInt(postData.lastNewLine, 10);\n}\nif (postData.lastOldLine !== null && postData.lastOldLine !== '') {\n position.old_line = parseInt(postData.lastOldLine, 10);\n}\n\n// Build the final request body with the nested 'position' key\nconst requestBody = {\n body: postData.text,\n position: position,\n};\n\n// Attach the final body for the next node\nitem.requestBody = requestBody;\n\nreturn item;"
},
"typeVersion": 2
},
{
"id": "4e2ab9f1-0b12-4a25-8f2c-981500889660",
"name": "位置情報なしでリクエストを準備",
"type": "n8n-nodes-base.code",
"position": [
576,
1500
],
"parameters": {
"jsCode": "const item = $input.first().json;\n\n// Reference the nested 'commentsToPost' object\nconst postData = item.commentsToPost;\n\n// Build the final request body with the nested 'position' key\nconst requestBody = {\n body: postData.text,\n};\n\n// Attach the final body for the next node\nitem.requestBody = requestBody;\n\nreturn item;"
},
"typeVersion": 2
},
{
"id": "975691b1-9667-4913-aa56-bc177e202dae",
"name": "ワークフロー実行情報を設定",
"type": "n8n-nodes-base.code",
"position": [
-4032,
1260
],
"parameters": {
"jsCode": "// initialize staticData object\nconst workflowStaticData = $getWorkflowStaticData('global');\nconst executionId = $execution.id\nconst projectId = $input.first().json[\"body\"][\"project_id\"]\nconst mrId = $input.first().json[\"body\"][\"merge_request\"][\"iid\"]\n\nworkflowStaticData[executionId] = {\"projectId\":projectId, \"mrId\": mrId}\n\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "a24362b0-e288-47af-9756-eff4acde1682",
"name": "レビュー開始を投稿",
"type": "n8n-nodes-base.httpRequest",
"position": [
-3808,
1164
],
"parameters": {
"url": "=https://gitlab.com/api/v4/projects/{{ $json.body.project_id }}/merge_requests/{{ $json.body.merge_request.iid }}/notes",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "body",
"value": "🤖 AI code review initiated. This may take up to 30 minutes for large merge requests. I'll post my findings as comments on the relevant files."
}
]
},
"headerParameters": {
"parameters": [
{
"name": "PRIVATE-TOKEN",
"value": "={{$env.GITLAB_TOKEN}}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "81762b68-7a5d-4f03-8479-c209d2696970",
"name": "Gitlab コメントを監視",
"type": "n8n-nodes-base.webhook",
"position": [
-4480,
1260
],
"webhookId": "REPLACE_WITH_UNIQUE_ID",
"parameters": {
"path": "REPLACE_WITH_UNIQUE_PATH",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "7ed4ec35-2b23-4b97-ba7d-f434ff54b93c",
"name": "差分を解析",
"type": "n8n-nodes-base.code",
"position": [
-1792,
1408
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const parseLastDiff = (gitDiff) => {\n gitDiff = gitDiff.replace(/\\n\\\\ No newline at end of file/, '')\n \n const diffList = gitDiff.trimEnd().split('\\n').reverse();\n const lastLineFirstChar = diffList?.[0]?.[0];\n const lastDiff =\n diffList.find((item) => {\n return /^@@ \\-\\d+,\\d+ \\+\\d+,\\d+ @@/g.test(item);\n }) || '';\n\n const [lastOldLineCount, lastNewLineCount] = lastDiff\n .replace(/@@ \\-(\\d+),(\\d+) \\+(\\d+),(\\d+) @@.*/g, ($0, $1, $2, $3, $4) => {\n return `${+$1 + +$2},${+$3 + +$4}`;\n })\n .split(',');\n \n if (!/^\\d+$/.test(lastOldLineCount) || !/^\\d+$/.test(lastNewLineCount)) {\n return {\n lastOldLine: -1,\n lastNewLine: -1,\n gitDiff,\n };\n }\n\n\n const lastOldLine = lastLineFirstChar === '+' ? null : (parseInt(lastOldLineCount) || 0) - 1;\n const lastNewLine = lastLineFirstChar === '-' ? null : (parseInt(lastNewLineCount) || 0) - 1;\n\n return {\n lastOldLine,\n lastNewLine,\n gitDiff,\n };\n};\n\nconst extra = parseLastDiff($json.changes.diff);\nreturn { ...$json, ...extra };"
},
"typeVersion": 2
},
{
"id": "601bc26b-1349-499c-a4eb-f08e3a064503",
"name": "問題なしを投稿",
"type": "n8n-nodes-base.httpRequest",
"position": [
800,
1212
],
"parameters": {
"url": "=https://gitlab.com/api/v4/projects/{{ $json.projectId }}/merge_requests/{{ $json.mrId }}/notes",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "body",
"value": "=🤖 AI review complete. No significant issues were found. LGTM!"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "PRIVATE-TOKEN",
"value": "={{$env.GITLAB_TOKEN}}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "aa200c64-05c9-4437-8f18-980642e4ec6c",
"name": "エラー発生を投稿",
"type": "n8n-nodes-base.httpRequest",
"position": [
800,
1692
],
"parameters": {
"url": "=https://gitlab.com/api/v4/projects/{{ $json.projectId }}/merge_requests/{{ $json.mrId }}/notes",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "body",
"value": "=🤖 An error occurred during the code review: {{ $json.error }}. Please trigger the review again."
}
]
},
"headerParameters": {
"parameters": [
{
"name": "PRIVATE-TOKEN",
"value": "={{$env.GITLAB_TOKEN}}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "e108c8b4-e8bb-4c43-aa01-0aa2cb70e83c",
"name": "エラーを含む静的コンテキストを取得",
"type": "n8n-nodes-base.code",
"position": [
576,
1692
],
"parameters": {
"jsCode": "const workflowStaticData = $getWorkflowStaticData('global');\nconst executionId = $input.first().json.execution.id;\nconst errorMessage = $input.first().json.execution.error.message;\n\nreturn {...workflowStaticData[executionId], error: errorMessage};"
},
"typeVersion": 2
},
{
"id": "1afcd3cf-8d54-4e11-8518-a82f246023ec",
"name": "静的コンテキストを取得",
"type": "n8n-nodes-base.code",
"position": [
576,
1212
],
"parameters": {
"jsCode": "const workflowStaticData = $getWorkflowStaticData('global');\nconst executionId = $execution.id;\nreturn workflowStaticData[executionId];"
},
"typeVersion": 2
},
{
"id": "21c6891d-1a57-4c4b-b7c4-41d84b20b744",
"name": "静的コンテキストをクリーンアップ",
"type": "n8n-nodes-base.code",
"position": [
1024,
1504
],
"parameters": {
"jsCode": "const workflowStaticData = $getWorkflowStaticData('global');\nconst executionId = $execution.id;\ndelete workflowStaticData[executionId];\nreturn [];"
},
"typeVersion": 2
},
{
"id": "1d97e536-a1c5-4520-a078-fb06baa377c4",
"name": "コメントを処理",
"type": "n8n-nodes-base.code",
"position": [
-544,
1408
],
"parameters": {
"jsCode": "const allItems = $input.first().json.data;\n\n// Filter for items that have actual review text\nconst commentsToPost = allItems.filter(item => {\n const text = item.text || '';\n const isNotEmpty = text.trim() !== '';\n const isNotClear = !text.includes('ALL_CLEAR');\n const isNotNoFindings = !text.toLowerCase().includes('no findings');\n const isNotNoIssues = !text.toLowerCase().includes('no issues found');\n return isNotEmpty && isNotClear && isNotNoFindings && isNotNoIssues;\n});\n\nreturn [{\n json: {\n commentsToPost: commentsToPost,\n noIssuesFound: commentsToPost.length === 0\n }\n}];"
},
"typeVersion": 2
},
{
"id": "db40d370-1216-4f36-9d87-86f26524e4cd",
"name": "Gitlab MRコメントを投稿",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
352,
1428
],
"parameters": {
"url": "=https://gitlab.com/api/v4/projects/{{ $json.commentsToPost.project_id }}/merge_requests/{{ $json.commentsToPost.iid }}/discussions",
"method": "POST",
"options": {},
"jsonBody": "={{ $json.requestBody }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "PRIVATE-TOKEN",
"value": "={{$env.GITLAB_TOKEN}}"
}
]
}
},
"retryOnFail": false,
"typeVersion": 4.2
},
{
"id": "eca87aa8-3d57-4513-a2d0-98d8cfcd1174",
"name": "試してみる!",
"type": "n8n-nodes-base.stickyNote",
"position": [
-5264,
1072
],
"parameters": {
"color": 6,
"width": 640,
"height": 288,
"content": "### ⭐️ Try It Out!\n\nWelcome! This template adds AI-powered code reviews to your GitLab Merge Requests. Simply comment **`ai-review`** on any MR and watch the workflow:\n\n- Captures the comment event.\n- Fetches diff & optional JIRA context.\n- Asks an LLM for insights.\n- Posts inline comments or an 'all clear' note.\n\nPerfect for catching logic, security and performance issues fast."
},
"typeVersion": 1
},
{
"id": "9bf7c660-9b1d-430c-95e9-37e89aae852d",
"name": "ステップ1: 監視&キャプチャ",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4480,
1488
],
"parameters": {
"color": 2,
"width": 600,
"height": 232,
"content": "### 🟢 Step 1: Listen & Capture\n\nA Webhook node listens for merge request note events. When the comment text matches your trigger (default: `ai-review`), it:\n\n- Extracts the Project & MR IDs and stores them in a **Static Context** (see below) so later nodes always know where to post results.\n- Posts a short \"AI review started\" message back to the MR to let reviewers know the bot is working."
},
"typeVersion": 1
},
{
"id": "f2e6745f-cb17-4d0c-a5c9-c3031759100a",
"name": "ステップ2: 準備&レビュー",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2432,
960
],
"parameters": {
"color": 4,
"width": 600,
"height": 280,
"content": "### 🟣 Step 2: Prepare & Review\n\nThe workflow fetches the merge request changes via the GitLab API and parses the diff into original vs new code blocks.\n\nIf the MR description contains a Jira key, the workflow fetches the issue summary and, if it's a subtask, also fetches the parent ticket for extra context. Both summaries are combined into the prompt.\n\nAn LLM (Gemini by default) is prompted to find critical issues only—logic bugs, security flaws and performance problems—and to return comments with file and line numbers. If no issues exist, the model returns `ALL_CLEAR`."
},
"typeVersion": 1
},
{
"id": "1bd4a994-6826-4b49-87a8-9214508b94c8",
"name": "ステップ3: 投稿&フォールバック",
"type": "n8n-nodes-base.stickyNote",
"position": [
1232,
1456
],
"parameters": {
"color": 5,
"width": 600,
"height": 184,
"content": "### 🟠 Step 3: Post & Fallback\n\nReview comments are converted into GitLab discussion payloads and posted back to the MR at the correct file and line positions.\n\nIf a comment’s position is not calculated properly, the workflow falls back to posting a comment as a thread at the MR level so nothing is lost."
},
"typeVersion": 1
},
{
"id": "2d50734d-8c63-4a52-8459-2719710ca2a6",
"name": "静的コンテキスト&エラー処理",
"type": "n8n-nodes-base.stickyNote",
"position": [
-96,
1904
],
"parameters": {
"width": 600,
"height": 216,
"content": "### 🔵 Static Context & Error Handling\n\nWorkflow Static Data is used to store persistent values—like the project ID and the MR ID so that they will be available across branches.\n\nThis is critical for the Error Trigger: if something fails, the error path can still post to the correct MR using these stored IDs. Without static context, error comments wouldn’t know where to go."
},
"typeVersion": 1
},
{
"id": "223b57bd-6ea3-4e32-ad29-9fec88550b1b",
"name": "ヘルプ&カスタマイズ",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1504,
1744
],
"parameters": {
"color": 7,
"width": 600,
"height": 168,
"content": "### 🟡 Need Help & Customise\n\n- Change the trigger word in the **IF** node to match your own.\n- Swap in your preferred LLM or adjust the prompt to suit your guidelines.\n- Filter by file type or exclude certain directories in the parsing logic."
},
"typeVersion": 1
},
{
"id": "917539f7-c7a2-48a4-9227-93287eeeded9",
"name": "位置情報なしでGitlab MRコメントを投稿",
"type": "n8n-nodes-base.httpRequest",
"position": [
800,
1500
],
"parameters": {
"url": "=https://gitlab.com/api/v4/projects/{{ $json.commentsToPost.project_id }}/merge_requests/{{ $json.commentsToPost.iid }}/discussions",
"method": "POST",
"options": {},
"jsonBody": "={{ $json.requestBody }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "PRIVATE-TOKEN",
"value": "={{$env.GITLAB_TOKEN}}"
}
]
}
},
"retryOnFail": false,
"typeVersion": 4.2
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "0798cf1c-bb7b-4765-bf7e-7dead605266f",
"connections": {
"d5e3ffd7-03d0-4b54-ab34-67fe43830531": {
"main": [
[
{
"node": "21e1a59a-3af6-45ba-83d8-73260736b92a",
"type": "main",
"index": 0
}
]
]
},
"aeba4982-b0fc-4d97-a06b-35b559135361": {
"main": [
[
{
"node": "1d97e536-a1c5-4520-a078-fb06baa377c4",
"type": "main",
"index": 0
}
]
]
},
"7ed4ec35-2b23-4b97-ba7d-f434ff54b93c": {
"main": [
[
{
"node": "8a3beea4-640b-4ddc-a801-719ace2643d0",
"type": "main",
"index": 0
}
]
]
},
"c3fedf3c-ed52-49d7-b9d5-30050d4324f2": {
"main": [
[
{
"node": "975691b1-9667-4913-aa56-bc177e202dae",
"type": "main",
"index": 0
}
]
]
},
"057832e9-fa3b-4fe5-a561-78b69e60c813": {
"main": [
[
{
"node": "e108c8b4-e8bb-4c43-aa01-0aa2cb70e83c",
"type": "main",
"index": 0
}
]
]
},
"24204fee-6c47-4026-a6a6-12ced75a49da": {
"main": [
[
{
"node": "7b1da623-5452-4783-ba8f-77896d3c8ea9",
"type": "main",
"index": 0
},
{
"node": "9aeed83e-1f0b-461a-8e2b-75efb7be5ca5",
"type": "main",
"index": 0
}
]
]
},
"a49bf76b-1a4b-4aba-b392-419ddc07e110": {
"main": [
[
{
"node": "d5e3ffd7-03d0-4b54-ab34-67fe43830531",
"type": "main",
"index": 2
}
]
]
},
"17752d5e-75d2-43ff-8e1b-ff439e6329b8": {
"main": [
[
{
"node": "8ff7eb74-fbcc-4d4f-8b53-50ed4d543a82",
"type": "main",
"index": 0
}
]
]
},
"9aeed83e-1f0b-461a-8e2b-75efb7be5ca5": {
"main": [
[
{
"node": "bf6f14f1-b684-4100-9085-f97399c8911f",
"type": "main",
"index": 0
}
]
]
},
"95e75bb0-94c4-492d-af8f-e6d79f442d92": {
"main": [
[
{
"node": "db40d370-1216-4f36-9d87-86f26524e4cd",
"type": "main",
"index": 0
}
]
]
},
"1d97e536-a1c5-4520-a078-fb06baa377c4": {
"main": [
[
{
"node": "ebe94dfb-7395-4788-896d-14c3d5380c18",
"type": "main",
"index": 0
}
]
]
},
"ebe94dfb-7395-4788-896d-14c3d5380c18": {
"main": [
[
{
"node": "1afcd3cf-8d54-4e11-8518-a82f246023ec",
"type": "main",
"index": 0
}
],
[
{
"node": "43acb79d-02c5-406b-9ae0-288e365518fa",
"type": "main",
"index": 0
}
]
]
},
"d30412f1-f714-41cb-a867-3a45f202c378": {
"main": [
[
{
"node": "7ed4ec35-2b23-4b97-ba7d-f434ff54b93c",
"type": "main",
"index": 0
}
]
]
},
"21e1a59a-3af6-45ba-83d8-73260736b92a": {
"main": [
[
{
"node": "d30412f1-f714-41cb-a867-3a45f202c378",
"type": "main",
"index": 0
}
]
]
},
"2ac7ced8-60b5-4fab-902f-bd1360e41a3a": {
"main": [
[
{
"node": "a49bf76b-1a4b-4aba-b392-419ddc07e110",
"type": "main",
"index": 0
},
{
"node": "06884594-3ad3-4329-b357-21359eb13503",
"type": "main",
"index": 0
}
]
]
},
"1afcd3cf-8d54-4e11-8518-a82f246023ec": {
"main": [
[
{
"node": "601bc26b-1349-499c-a4eb-f08e3a064503",
"type": "main",
"index": 0
}
]
]
},
"43acb79d-02c5-406b-9ae0-288e365518fa": {
"main": [
[
{
"node": "95e75bb0-94c4-492d-af8f-e6d79f442d92",
"type": "main",
"index": 0
}
]
]
},
"7b1da623-5452-4783-ba8f-77896d3c8ea9": {
"main": [
[
{
"node": "d5e3ffd7-03d0-4b54-ab34-67fe43830531",
"type": "main",
"index": 1
}
]
]
},
"aa200c64-05c9-4437-8f18-980642e4ec6c": {
"main": [
[
{
"node": "21c6891d-1a57-4c4b-b7c4-41d84b20b744",
"type": "main",
"index": 0
}
]
]
},
"601bc26b-1349-499c-a4eb-f08e3a064503": {
"main": [
[
{
"node": "21c6891d-1a57-4c4b-b7c4-41d84b20b744",
"type": "main",
"index": 0
}
]
]
},
"8a3beea4-640b-4ddc-a801-719ace2643d0": {
"main": [
[
{
"node": "17752d5e-75d2-43ff-8e1b-ff439e6329b8",
"type": "main",
"index": 0
},
{
"node": "39f6aa6b-3296-47e6-b71d-59bcc7efd34c",
"type": "main",
"index": 0
}
]
]
},
"bf6f14f1-b684-4100-9085-f97399c8911f": {
"main": [
[
{
"node": "5e547d2f-c978-42bc-acb2-25c194308154",
"type": "main",
"index": 0
}
]
]
},
"db40d370-1216-4f36-9d87-86f26524e4cd": {
"main": [
[
{
"node": "21c6891d-1a57-4c4b-b7c4-41d84b20b744",
"type": "main",
"index": 0
}
],
[
{
"node": "4e2ab9f1-0b12-4a25-8f2c-981500889660",
"type": "main",
"index": 0
}
]
]
},
"39f6aa6b-3296-47e6-b71d-59bcc7efd34c": {
"main": [
[
{
"node": "8ff7eb74-fbcc-4d4f-8b53-50ed4d543a82",
"type": "main",
"index": 1
}
]
]
},
"483ec727-0b0b-46a7-9bd9-fbd52f2bb51a": {
"ai_languageModel": [
[
{
"node": "17752d5e-75d2-43ff-8e1b-ff439e6329b8",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"06884594-3ad3-4329-b357-21359eb13503": {
"main": [
[
{
"node": "24204fee-6c47-4026-a6a6-12ced75a49da",
"type": "main",
"index": 0
}
]
]
},
"5e547d2f-c978-42bc-acb2-25c194308154": {
"main": [
[
{
"node": "d5e3ffd7-03d0-4b54-ab34-67fe43830531",
"type": "main",
"index": 0
}
]
]
},
"81762b68-7a5d-4f03-8479-c209d2696970": {
"main": [
[
{
"node": "c3fedf3c-ed52-49d7-b9d5-30050d4324f2",
"type": "main",
"index": 0
}
]
]
},
"8ff7eb74-fbcc-4d4f-8b53-50ed4d543a82": {
"main": [
[
{
"node": "aeba4982-b0fc-4d97-a06b-35b559135361",
"type": "main",
"index": 0
}
]
]
},
"4e2ab9f1-0b12-4a25-8f2c-981500889660": {
"main": [
[
{
"node": "917539f7-c7a2-48a4-9227-93287eeeded9",
"type": "main",
"index": 0
}
]
]
},
"e108c8b4-e8bb-4c43-aa01-0aa2cb70e83c": {
"main": [
[
{
"node": "aa200c64-05c9-4437-8f18-980642e4ec6c",
"type": "main",
"index": 0
}
]
]
},
"975691b1-9667-4913-aa56-bc177e202dae": {
"main": [
[
{
"node": "a24362b0-e288-47af-9756-eff4acde1682",
"type": "main",
"index": 0
},
{
"node": "2ac7ced8-60b5-4fab-902f-bd1360e41a3a",
"type": "main",
"index": 0
}
]
]
},
"917539f7-c7a2-48a4-9227-93287eeeded9": {
"main": [
[
{
"node": "21c6891d-1a57-4c4b-b7c4-41d84b20b744",
"type": "main",
"index": 0
}
]
]
}
}
}よくある質問
このワークフローの使い方は?
上記のJSON設定コードをコピーし、n8nインスタンスで新しいワークフローを作成して「JSONからインポート」を選択、設定を貼り付けて認証情報を必要に応じて変更してください。
このワークフローはどんな場面に適していますか?
上級 - AI要約, マルチモーダルAI
有料ですか?
このワークフローは完全無料です。ただし、ワークフローで使用するサードパーティサービス(OpenAI APIなど)は別途料金が発生する場合があります。
関連ワークフロー
毎日の WhatsApp グループ スマート分析:GPT-4.1 による分析と音声メッセージの transcrição
毎日の WhatsApp グループ インタラクティブ分析:GPT-4.1 分析と音声メッセージ文字起こし
If
Set
Code
+
If
Set
Code
52 ノードDaniel Lianes
その他
AIを活用した会議調査とデイリーアジェンダ(Googleカレンダー、Attio CRM、Slack)
AIを活用した会議調査とデイリーアジェンダ:Googleカレンダー、Attio CRM、Slackを使用
If
Set
Code
+
If
Set
Code
30 ノードHarry Siggins
AI要約
01 AIメディアバイヤーでFacebook広告のパフォーマンスを分析し、インサイトをGoogle Sheetsへ送信
Gemini AIを使用してFacebook広告を分析し、インサイトをGoogle Sheetsに送信
If
Set
Code
+
If
Set
Code
34 ノードJJ Tham
市場調査
アイデア捕集テンプレート
Reddit、YouTube、Xを分析してコンテンツ戦略レポートを生成するGeminiの使用
If
Set
Code
+
If
Set
Code
34 ノードSheryl
市場調査
競合他社コンテンツギャップ分析ツール:構題マッピングの自動化
Gemini AI、Apify、Google Sheetsを使用して競合企業のコンテンツギャップを分析
If
Set
Code
+
If
Set
Code
30 ノードMychel Garzon
その他
会議準備の自動化
GPT-5 と Gemini でカレンダーから Slack まで、Attio CRM を通じて自動のにミーティングを準備する
If
Set
Code
+
If
Set
Code
39 ノードHarry Siggins
AI要約