{"nodes":[{"name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[880,540],"parameters":{"content":"## Edit your own prompt ⬇️\n"},"typeVersion":1,"id":"node-0a48ef0d"},{"name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[-380,580],"parameters":{"content":"## Filter comments and customize your trigger words ⬇️"},"typeVersion":1,"id":"node-4d7fbf82"},{"name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[-120,560],"parameters":{"content":"## Replace your gitlab URL and token ⬇️"},"typeVersion":1,"id":"node-281e70f1"},{"name":"Webhook","type":"n8n-nodes-base.webhook","position":[-540,760],"webhookId":"6cfd2f23-6f45-47d4-9fe0-8f6f1c05829a","parameters":{"path":"e21095c0-1876-4cd9-9e92-a2eac737f03e","options":{},"httpMethod":"POST"},"typeVersion":1.1,"id":"node-b71228ac"},{"name":"Code","type":"n8n-nodes-base.code","position":[720,540],"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 {\n originalCode:originalCode,\n newCode:newCode\n};\n\n"},"typeVersion":2,"id":"node-8c29c20e"},{"name":"Split Out1","type":"n8n-nodes-base.splitOut","position":[140,740],"parameters":{"options":{},"fieldToSplitOut":"changes"},"typeVersion":1,"id":"node-fe635f1d"},{"name":"OpenAI Chat Model1","type":"n8n-nodes-base.noOp","position":[900,860],"parameters":{"options":{"baseURL":""}},"typeVersion":1,"id":"node-c4e95eba"},{"name":"Get Changes1","type":"n8n-nodes-base.httpRequest","position":[-60,740],"parameters":{"url":"{{ $env.BASE_URL }}","options":{},"sendHeaders":true,"headerParameters":{"parameters":[{"name":"PRIVATE-TOKEN"}]}},"typeVersion":4.1,"id":"node-42c47fb7"},{"name":"Skip File Change1","type":"n8n-nodes-base.if","position":[340,740],"parameters":{"options":{},"conditions":{"options":{"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"operator":{"type":"boolean","operation":"false","singleValue":true},"leftValue":"={{ $json.renamed_file }}","rightValue":""},{"operator":{"type":"boolean","operation":"false","singleValue":true},"leftValue":"={{ $json.deleted_file }}","rightValue":""},{"operator":{"type":"string","operation":"startsWith"},"leftValue":"={{ $json.diff }}","rightValue":"@@"}]}},"typeVersion":2,"id":"node-043f2b79"},{"name":"Parse Last Diff Line1","type":"n8n-nodes-base.code","position":[540,540],"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\nreturn parseLastDiff($input.item.json.diff)\n"},"typeVersion":2,"id":"node-83b7c9f1"},{"name":"Post Discussions1","type":"n8n-nodes-base.httpRequest","position":[1280,720],"parameters":{"url":"{{ $env.BASE_URL }}","method":"POST","options":{},"sendBody":true,"contentType":"multipart-form-data","sendHeaders":true,"bodyParameters":{"parameters":[{"name":"body","value":"={{ $('Basic LLM Chain1').item.json[\"text\"] }}"},{"name":"position[position_type]","value":"text"},{"name":"position[old_path]","value":"={{ $('Split Out1').item.json.old_path }}"},{"name":"position[new_path]","value":"={{ $('Split Out1').item.json.new_path }}"},{"name":"position[start_sha]","value":"={{ $('Get Changes1').item.json.diff_refs.start_sha }}"},{"name":"position[head_sha]","value":"={{ $('Get Changes1').item.json.diff_refs.head_sha }}"},{"name":"position[base_sha]","value":"={{ $('Get Changes1').item.json.diff_refs.base_sha }}"},{"name":"position[new_line]","value":"={{ $('Parse Last Diff Line1').item.json.lastNewLine || '' }}"},{"name":"position[old_line]","value":"={{ $('Parse Last Diff Line1').item.json.lastOldLine || '' }}"}]},"headerParameters":{"parameters":[{"name":"PRIVATE-TOKEN"}]}},"typeVersion":4.1,"id":"node-0d308613"},{"name":"Need Review1","type":"n8n-nodes-base.if","position":[-320,760],"parameters":{"options":{},"conditions":{"options":{"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"operator":{"name":"filter.operator.equals","type":"string","operation":"equals"},"leftValue":"={{ $json.body.object_attributes.note }}","rightValue":"+0"}]}},"typeVersion":2,"id":"node-06901d3f"},{"name":"Basic LLM Chain1","type":"n8n-nodes-base.noOp","position":[880,720],"parameters":{"prompt":"=File path：{{ $('Skip File Change1').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":"# Overview:\n You are a senior programming expert Bot, responsible for reviewing code changes and providing review recommendations.\n At the beginning of the suggestion, it is necessary to clearly make a decision to \"reject\" or \"accept\" the code change, and rate the change in the format \"Change Score: Actual Score\", with a score range of 0-100 points.\n Then, point out the existing problems in concise language and a stern tone.\n If you feel it is necessary, you can directly provide the modified content.\n Your review proposal must use rigorous Markdown format."}]}},"typeVersion":1,"id":"node-85026d80"},{"name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[1200,540],"parameters":{"content":"## Replace your gitlab URL and token ⬇️"},"typeVersion":1,"id":"node-f6e35f37"},{"id":"error-76c729ac","name":"Error Handler","type":"n8n-nodes-base.stopAndError","typeVersion":1,"position":[1000,400],"parameters":{"message":"Workflow execution error","options":{}}}],"connections":{},"name":"Production Workflow","settings":{"executionOrder":"v1","saveManualExecutions":true,"callerPolicy":"workflowsFromSameOwner","errorWorkflow":null,"timezone":"UTC","executionTimeout":3600,"maxExecutions":1000,"retryOnFail":true,"retryCount":3},"meta":{"instanceId":"workflow-9f26a395","versionId":"1.0.0","createdAt":"2025-09-29T07:07:53.844165","updatedAt":"2025-09-29T07:07:53.844178","owner":"n8n-user","license":"MIT","category":"automation","status":"active","priority":"high","environment":"production"},"description":"Production-ready workflow: Production Workflow. This workflow has been optimized for production use with comprehensive error handling, security, and documentation."}