The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "jump-section: Comment Fix Pipeline",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "comment-fix",
"responseMode": "onReceived",
"responseData": "noData"
},
"id": "webhook-trigger",
"name": "Webhook: PR Comment Event",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1.1,
"position": [
240,
300
]
},
{
"parameters": {
"conditions": {
"conditions": [
{
"id": "action-check",
"leftValue": "={{ $json.body.action }}",
"rightValue": "created",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
},
"id": "check-action",
"name": "IF: action=created",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
460,
300
]
},
{
"parameters": {
"jsCode": "// GitHub Actions\uc5d0\uc11c \uc624\uba74 payload\uac00 \uc9c1\uc811, \uc9c1\uc811 webhook\uc774\uba74 .body\uc5d0 \ub798\ud551\ub428\nconst raw = $input.first().json;\nconst body = raw.body || raw;\nconst comment = body.comment;\nconst repo = body.repository;\n\nconst commentBody = (comment.body || '').trim();\nlet command = null;\n\nif (commentBody === '/fix-build') command = 'fix-build';\nelse if (commentBody === '/fix-lint') command = 'fix-lint';\nelse if (commentBody === '/fix' && comment.in_reply_to_id) command = 'fix';\n\nif (!command) return [];\n\nlet prNumber, prTitle, prUrl, headBranch, headSha;\n\nif (body.pull_request) {\n // pull_request_review_comment \uc774\ubca4\ud2b8\n const pr = body.pull_request;\n prNumber = pr.number;\n prTitle = pr.title;\n prUrl = pr.html_url;\n headBranch = pr.head.ref;\n headSha = pr.head.sha;\n} else if (body.issue && body.issue.pull_request) {\n // issue_comment \uc774\ubca4\ud2b8 (\uc77c\ubc18 PR \ucf54\uba58\ud2b8)\n prNumber = body.issue.number;\n prTitle = body.issue.title;\n prUrl = body.issue.html_url;\n\n // headBranch/headSha\ub294 API\ub85c \uc870\ud68c\n const token = $env.GITHUB_TOKEN;\n const prData = await new Promise((resolve, reject) => {\n const https = require('https');\n const req = https.request({\n hostname: 'api.github.com',\n path: `/repos/${repo.owner.login}/${repo.name}/pulls/${prNumber}`,\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'User-Agent': 'n8n-jump-section'\n }\n }, (res) => {\n let data = '';\n res.on('data', chunk => data += chunk);\n res.on('end', () => {\n if (res.statusCode >= 400) { reject(new Error(`HTTP ${res.statusCode}: ${data}`)); return; }\n try { resolve(JSON.parse(data)); } catch (e) { reject(new Error('Invalid JSON: ' + data.slice(0, 200))); }\n });\n });\n req.setTimeout(15000, () => { req.destroy(); reject(new Error('Request timeout')); });\n req.on('error', reject);\n req.end();\n });\n\n headBranch = prData.head.ref;\n headSha = prData.head.sha;\n} else {\n return [];\n}\n\nreturn [{\n json: {\n command,\n prNumber,\n prTitle,\n prUrl,\n headBranch,\n headSha,\n repoOwner: repo.owner.login,\n repoName: repo.name,\n commentId: comment.id,\n filePath: comment.path || null,\n inReplyToId: comment.in_reply_to_id || null\n }\n}];"
},
"id": "detect-command",
"name": "\ucee4\ub9e8\ub4dc \uac10\uc9c0",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
300
]
},
{
"parameters": {
"conditions": {
"combinator": "and",
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "is-fix-build",
"leftValue": "={{ $json.command }}",
"rightValue": "fix-build",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
},
"id": "if-fix-build",
"name": "IF: fix-build?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
900,
300
]
},
{
"parameters": {
"conditions": {
"combinator": "and",
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "is-fix-lint",
"leftValue": "={{ $json.command }}",
"rightValue": "fix-lint",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
},
"id": "if-fix-lint",
"name": "IF: fix-lint?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
900,
480
]
},
{
"parameters": {
"jsCode": "const ctx = $input.first().json;\nconst token = $env.GITHUB_TOKEN;\n\nfunction ghGet(path) {\n return new Promise((resolve, reject) => {\n const https = require('https');\n const req = https.request({\n hostname: 'api.github.com',\n path,\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'User-Agent': 'n8n-jump-section'\n }\n }, (res) => {\n let data = '';\n res.on('data', chunk => data += chunk);\n res.on('end', () => {\n if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${data}`));\n else resolve(JSON.parse(data));\n });\n });\n req.setTimeout(15000, () => { req.destroy(); reject(new Error('Request timeout')); });\n req.on('error', reject);\n req.end();\n });\n}\n\nconst filesData = await ghGet(`/repos/${ctx.repoOwner}/${ctx.repoName}/pulls/${ctx.prNumber}/files?per_page=30`);\n\nconst files = await Promise.all(filesData.map(async (f) => {\n const contentData = await ghGet(`/repos/${ctx.repoOwner}/${ctx.repoName}/contents/${f.filename}?ref=${ctx.headBranch}`);\n const content = Buffer.from((contentData.content || '').replace(/\\n/g, ''), 'base64').toString('utf8');\n return { path: f.filename, content, sha: contentData.sha };\n}));\n\nconst checkData = await ghGet(`/repos/${ctx.repoOwner}/${ctx.repoName}/commits/${ctx.headSha}/check-runs?per_page=30`);\nconst failed = (checkData.check_runs || []).filter(c =>\n c.conclusion === 'failure' || c.conclusion === 'timed_out'\n);\n\nconst ciSection = failed.length > 0\n ? failed.map(c => {\n const lines = [`### \u274c ${c.name}`];\n if (c.output?.summary) lines.push(c.output.summary);\n if (c.output?.text) lines.push(c.output.text.slice(0, 3000));\n return lines.join('\\n');\n }).join('\\n\\n')\n : '(CI \uc2e4\ud328 \uc5c6\uc74c)';\n\nconst filesContext = files.map(f =>\n `### ${f.path}\\n\\`\\`\\`typescript\\n${f.content}\\n\\`\\`\\``\n).join('\\n\\n');\n\nconst userPrompt = `## PR #${ctx.prNumber}: ${ctx.prTitle}\\n\ube0c\ub79c\uce58: \\`${ctx.headBranch}\\`\\n\\n## CI \uc2e4\ud328 \uc815\ubcf4\\n\\n${ciSection}\\n\\n---\\n\\n## \ud604\uc7ac \ucf54\ub4dc\\n\\n${filesContext}\\n\\n---\\n\\nCI \ube4c\ub4dc/\ud0c0\uc785 \uc5d0\ub7ec\ub97c \ubd84\uc11d\ud558\uc5ec \uc218\uc815\ud558\uc138\uc694. \uc6d0\uc778\uc774 \uba85\ud655\ud55c \uacbd\uc6b0\uc5d0\ub9cc write_file\ub85c \uc81c\ucd9c\ud569\ub2c8\ub2e4.`;\n\nconst geminiPayload = {\n system_instruction: {\n parts: [{ text: 'TypeScript \uc804\ubb38 \uac1c\ubc1c\uc790\uc785\ub2c8\ub2e4. CI \ube4c\ub4dc \uc5d0\ub7ec\uc640 \ud0c0\uc785 \uc5d0\ub7ec\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.\\n\\n## \ud544\uc218 \uaddc\uce59\\n\\n### Prettier \ud3ec\ub9f7\\n- \ub4e4\uc5ec\uc4f0\uae30: \uc2a4\ud398\uc774\uc2a4 2\uce78, \uc138\ubbf8\ucf5c\ub860 \ud56d\uc0c1, \uc2f1\uae00\ucffc\ud2b8, \ucd5c\ub300 100\uc790, trailing comma \ud56d\uc0c1\\n\\n### \uc218\uc815 \uc6d0\uce59\\n- CI \uc5d0\ub7ec \uc6d0\uc778\uc774 \uba85\ud655\ud558\uace0 \uc218\uc815 \ubc29\ud5a5\uc774 \ud655\uc2e4\ud55c \uacbd\uc6b0\uc5d0\ub9cc write_file\ub85c \uc81c\ucd9c\\n- \ubd88\ud655\uc2e4\ud558\uba74 \ud574\ub2f9 \ud30c\uc77c\uc740 \uac74\ub108\ub700\\n- \uae30\uc874 Public API \ud558\uc704 \ud638\ud658\uc131 \uc720\uc9c0\\n- \uc218\uc815\uc774 \ud544\uc694 \uc5c6\ub294 \ud30c\uc77c\uc740 write_file \uc0dd\ub7b5' }]\n },\n contents: [{ role: 'user', parts: [{ text: userPrompt }] }],\n tools: [{\n functionDeclarations: [{\n name: 'write_file',\n description: '\uc218\uc815\ub41c \ud30c\uc77c\uc758 \uc804\uccb4 \ub0b4\uc6a9\uc744 \uc81c\ucd9c\ud569\ub2c8\ub2e4.',\n parameters: {\n type: 'OBJECT',\n properties: {\n path: { type: 'STRING', description: '\ud30c\uc77c \uacbd\ub85c' },\n content: { type: 'STRING', description: '\ud30c\uc77c \uc804\uccb4 \ub0b4\uc6a9' },\n description: { type: 'STRING', description: '\ubcc0\uacbd \uc124\uba85' }\n },\n required: ['path', 'content', 'description']\n }\n }]\n }],\n toolConfig: { functionCallingConfig: { mode: 'AUTO' } },\n generationConfig: { maxOutputTokens: 8192 }\n};\n\nreturn [{ json: { ...ctx, files, geminiPayload } }];"
},
"id": "prepare-build",
"name": "[build] \ud30c\uc77c + CI \uc870\ud68c \ubc0f \ud504\ub86c\ud504\ud2b8 \uc900\ube44",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
200
]
},
{
"parameters": {
"method": "POST",
"url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={{ $env.GEMINI_API_KEY }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.geminiPayload }}",
"options": {}
},
"id": "gemini-fix-build",
"name": "[build] Gemini: \ucf54\ub4dc \uc218\uc815",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1340,
200
]
},
{
"parameters": {
"jsCode": "const response = $input.first().json;\nconst ctx = $('[build] \ud30c\uc77c + CI \uc870\ud68c \ubc0f \ud504\ub86c\ud504\ud2b8 \uc900\ube44').first().json;\n\nif (!response.candidates || response.candidates.length === 0) {\n return [{ json: { noChanges: true, geminiError: response.promptFeedback || response.error || 'no candidates', ...ctx } }];\n}\n\nconst candidate = response.candidates[0];\nif (!candidate.content || !candidate.content.parts || candidate.finishReason === 'MALFORMED_FUNCTION_CALL') {\n return [{ json: { noChanges: true, geminiError: candidate.finishReason || 'no content', ...ctx } }];\n}\n\nconst parts = candidate.content.parts;\nconst functionCalls = parts.filter(p => p.functionCall && p.functionCall.name === 'write_file');\n\nif (functionCalls.length === 0) {\n return [{ json: { noChanges: true, ...ctx } }];\n}\n\nconst fixedFiles = functionCalls.map(p => {\n const rawContent = p.functionCall.args.content || ''; const content = rawContent.replace(/^```[\\w]*\\n?/, '').replace(/\\n?```$/, '').trim();\n const original = ctx.files.find(f => f.path === p.functionCall.args.path);\n return {\n path: p.functionCall.args.path,\n content,\n description: p.functionCall.args.description,\n sha: original?.sha || null\n };\n});\n\nconst filesSummary = fixedFiles.map(f => `\u2022 \\`${f.path}\\`: ${f.description}`).join('\\n');\n\nreturn [{ json: { noChanges: false, fixedFiles, filesSummary, ...ctx } }];"
},
"id": "parse-build",
"name": "[build] \uc218\uc815 \uacb0\uacfc \ud30c\uc2f1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1560,
200
]
},
{
"parameters": {
"conditions": {
"conditions": [
{
"id": "has-changes",
"leftValue": "={{ $json.noChanges }}",
"rightValue": false,
"operator": {
"type": "boolean",
"operation": "false"
}
}
]
}
},
"id": "check-changes-build",
"name": "[build] IF: \uc218\uc815\ub41c \ud30c\uc77c \uc788\ub294\uc9c0",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1780,
200
]
},
{
"parameters": {
"jsCode": "const data = $input.first().json;\nif (data.noChanges || !data.fixedFiles || data.fixedFiles.length === 0) return [];\nconst fixedFiles = data.fixedFiles.filter(f => f.path && f.content && f.content.trim().length > 0);\nif (fixedFiles.length === 0) return [];\nconst { headBranch, repoOwner, repoName, filesSummary } = data;\nconst token = $env.GITHUB_TOKEN;\n\nconst headers = {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'Content-Type': 'application/json',\n 'User-Agent': 'n8n-jump-section'\n};\n\nconst baseUrl = `https://api.github.com/repos/${repoOwner}/${repoName}`;\n\nfunction request(method, url, body) {\n return new Promise((resolve, reject) => {\n const https = require('https');\n const path = url.replace('https://api.github.com', '');\n const options = { hostname: 'api.github.com', path, method, headers };\n const req = https.request(options, (res) => {\n let d = '';\n res.on('data', chunk => d += chunk);\n res.on('end', () => {\n if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${d}`));\n else resolve(JSON.parse(d));\n });\n });\n req.setTimeout(15000, () => { req.destroy(); reject(new Error('Request timeout')); });\n req.on('error', reject);\n if (body) req.write(JSON.stringify(body));\n req.end();\n });\n}\n\nconst refData = await request('GET', `${baseUrl}/git/refs/heads/${headBranch}`);\nconst baseSha = refData.object.sha;\nconst commitData = await request('GET', `${baseUrl}/git/commits/${baseSha}`);\nconst baseTreeSha = commitData.tree.sha;\n\nconst newTree = await request('POST', `${baseUrl}/git/trees`, {\n base_tree: baseTreeSha,\n tree: fixedFiles.map(f => ({ path: f.path, mode: '100644', type: 'blob', content: f.content }))\n});\n\nconst newCommit = await request('POST', `${baseUrl}/git/commits`, {\n message: `fix: fix build errors\\n\\n${filesSummary}`,\n tree: newTree.sha,\n parents: [baseSha]\n});\n\nawait request('PATCH', `${baseUrl}/git/refs/heads/${headBranch}`, { sha: newCommit.sha });\n\nawait request('POST', `${baseUrl}/actions/workflows/auto-format.yml/dispatches`, {\n ref: headBranch,\n inputs: { branch: headBranch }\n});\n\nreturn [{ json: { ...data, commitSha: newCommit.sha } }];"
},
"id": "commit-build",
"name": "[build] GitHub: \ucee4\ubc0b",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2000,
140
]
},
{
"parameters": {
"method": "POST",
"url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/issues/{{ $json.prNumber }}/comments",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Authorization",
"value": "=Bearer {{ $env.GITHUB_TOKEN }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { body: '\u2705 \ube4c\ub4dc \uc5d0\ub7ec \uc218\uc815 \uc644\ub8cc\\n\\n' + $json.filesSummary + '\\n\\ncommit: `' + $json.commitSha.slice(0, 7) + '`' } }}",
"options": {}
},
"id": "reply-build",
"name": "[build] GitHub: PR \ucf54\uba58\ud2b8",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2220,
140
]
},
{
"parameters": {
"jsCode": "const ctx = $input.first().json;\nconst token = $env.GITHUB_TOKEN;\n\nfunction ghGet(path) {\n return new Promise((resolve, reject) => {\n const https = require('https');\n const req = https.request({\n hostname: 'api.github.com',\n path,\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'User-Agent': 'n8n-jump-section'\n }\n }, (res) => {\n let data = '';\n res.on('data', chunk => data += chunk);\n res.on('end', () => {\n if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${data}`));\n else resolve(JSON.parse(data));\n });\n });\n req.setTimeout(15000, () => { req.destroy(); reject(new Error('Request timeout')); });\n req.on('error', reject);\n req.end();\n });\n}\n\nconst filesData = await ghGet(`/repos/${ctx.repoOwner}/${ctx.repoName}/pulls/${ctx.prNumber}/files?per_page=30`);\n\nconst tsFiles = filesData.filter(f => /\\.(ts|tsx|js|jsx)$/.test(f.filename));\n\nconst files = await Promise.all(tsFiles.map(async (f) => {\n const contentData = await ghGet(`/repos/${ctx.repoOwner}/${ctx.repoName}/contents/${f.filename}?ref=${ctx.headBranch}`);\n const content = Buffer.from((contentData.content || '').replace(/\\n/g, ''), 'base64').toString('utf8');\n return { path: f.filename, content, sha: contentData.sha };\n}));\n\nconst filesContext = files.map(f =>\n `### ${f.path}\\n\\`\\`\\`typescript\\n${f.content}\\n\\`\\`\\``\n).join('\\n\\n');\n\nconst userPrompt = `## PR #${ctx.prNumber}: ${ctx.prTitle}\\n\\n\uc544\ub798 \ud30c\uc77c\ub4e4\uc5d0 Prettier \ud3ec\ub9f7\uc744 \uc815\ud655\ud788 \uc801\uc6a9\ud558\uc138\uc694.\\n\ud3ec\ub9f7\uc774 \uc774\ubbf8 \uc62c\ubc14\ub978 \ud30c\uc77c\uc740 write_file\uc744 \ud638\ucd9c\ud558\uc9c0 \ub9c8\uc138\uc694.\\n\\n${filesContext}`;\n\nconst geminiPayload = {\n system_instruction: {\n parts: [{ text: 'TypeScript \ucf54\ub4dc \ud3ec\ub9f7\ud130\uc785\ub2c8\ub2e4. Prettier \uaddc\uce59\uc744 \uc815\ud655\ud788 \uc801\uc6a9\ud569\ub2c8\ub2e4.\\n\\n## Prettier \uaddc\uce59 (\ubc18\ub4dc\uc2dc \uc900\uc218)\\n- \ub4e4\uc5ec\uc4f0\uae30: \uc2a4\ud398\uc774\uc2a4 2\uce78\\n- \uc138\ubbf8\ucf5c\ub860: \ud56d\uc0c1 \ubd99\uc784\\n- \ub530\uc634\ud45c: \uc2f1\uae00\ucffc\ud2b8\\n- \ud55c \uc904 \ucd5c\ub300 \uae38\uc774: 100\uc790\\n- trailing comma: \ud56d\uc0c1 \ubd99\uc784 (\ud568\uc218 \uc778\uc790 \ub9c8\uc9c0\ub9c9 \ud3ec\ud568)\\n- \uc904\ubc14\uafc8: LF (\\\\n)\\n\\n## \uc218\uc815 \uc6d0\uce59\\n- \ud3ec\ub9f7\ub9cc \uc218\uc815, \ub85c\uc9c1 \ubcc0\uacbd \uae08\uc9c0\\n- \uc774\ubbf8 \uc62c\ubc14\ub978 \ud30c\uc77c\uc740 write_file \uc0dd\ub7b5' }]\n },\n contents: [{ role: 'user', parts: [{ text: userPrompt }] }],\n tools: [{\n functionDeclarations: [{\n name: 'write_file',\n description: '\ud3ec\ub9f7 \uc801\uc6a9\ub41c \ud30c\uc77c\uc758 \uc804\uccb4 \ub0b4\uc6a9\uc744 \uc81c\ucd9c\ud569\ub2c8\ub2e4.',\n parameters: {\n type: 'OBJECT',\n properties: {\n path: { type: 'STRING', description: '\ud30c\uc77c \uacbd\ub85c' },\n content: { type: 'STRING', description: '\ud30c\uc77c \uc804\uccb4 \ub0b4\uc6a9' },\n description: { type: 'STRING', description: '\ubcc0\uacbd \uc124\uba85' }\n },\n required: ['path', 'content', 'description']\n }\n }]\n }],\n toolConfig: { functionCallingConfig: { mode: 'AUTO' } },\n generationConfig: { maxOutputTokens: 8192 }\n};\n\nreturn [{ json: { ...ctx, files, geminiPayload } }];"
},
"id": "prepare-lint",
"name": "[lint] \ud30c\uc77c \uc870\ud68c \ubc0f \ud504\ub86c\ud504\ud2b8 \uc900\ube44",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
400
]
},
{
"parameters": {
"method": "POST",
"url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={{ $env.GEMINI_API_KEY }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.geminiPayload }}",
"options": {}
},
"id": "gemini-fix-lint",
"name": "[lint] Gemini: \ud3ec\ub9f7 \uc218\uc815",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1340,
400
]
},
{
"parameters": {
"jsCode": "const response = $input.first().json;\nconst ctx = $('[lint] \ud30c\uc77c \uc870\ud68c \ubc0f \ud504\ub86c\ud504\ud2b8 \uc900\ube44').first().json;\n\nif (!response.candidates || response.candidates.length === 0) {\n return [{ json: { noChanges: true, geminiError: response.promptFeedback || response.error || 'no candidates', ...ctx } }];\n}\n\nconst candidate = response.candidates[0];\nif (!candidate.content || !candidate.content.parts || candidate.finishReason === 'MALFORMED_FUNCTION_CALL') {\n return [{ json: { noChanges: true, geminiError: candidate.finishReason || 'no content', ...ctx } }];\n}\n\nconst parts = candidate.content.parts;\nconst functionCalls = parts.filter(p => p.functionCall && p.functionCall.name === 'write_file');\n\nif (functionCalls.length === 0) {\n return [{ json: { noChanges: true, ...ctx } }];\n}\n\nconst fixedFiles = functionCalls.map(p => {\n const rawContent = p.functionCall.args.content || ''; const content = rawContent.replace(/^```[\\w]*\\n?/, '').replace(/\\n?```$/, '').trim();\n const original = ctx.files.find(f => f.path === p.functionCall.args.path);\n return {\n path: p.functionCall.args.path,\n content,\n description: p.functionCall.args.description,\n sha: original?.sha || null\n };\n});\n\nconst filesSummary = fixedFiles.map(f => `\u2022 \\`${f.path}\\`: ${f.description}`).join('\\n');\n\nreturn [{ json: { noChanges: false, fixedFiles, filesSummary, ...ctx } }];"
},
"id": "parse-lint",
"name": "[lint] \uc218\uc815 \uacb0\uacfc \ud30c\uc2f1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1560,
400
]
},
{
"parameters": {
"conditions": {
"conditions": [
{
"id": "has-changes",
"leftValue": "={{ $json.noChanges }}",
"rightValue": false,
"operator": {
"type": "boolean",
"operation": "false"
}
}
]
}
},
"id": "check-changes-lint",
"name": "[lint] IF: \uc218\uc815\ub41c \ud30c\uc77c \uc788\ub294\uc9c0",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1780,
400
]
},
{
"parameters": {
"jsCode": "const data = $input.first().json;\nif (data.noChanges || !data.fixedFiles || data.fixedFiles.length === 0) return [];\nconst fixedFiles = data.fixedFiles.filter(f => f.path && f.content && f.content.trim().length > 0);\nif (fixedFiles.length === 0) return [];\nconst { headBranch, repoOwner, repoName, filesSummary } = data;\nconst token = $env.GITHUB_TOKEN;\n\nconst headers = {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'Content-Type': 'application/json',\n 'User-Agent': 'n8n-jump-section'\n};\n\nconst baseUrl = `https://api.github.com/repos/${repoOwner}/${repoName}`;\n\nfunction request(method, url, body) {\n return new Promise((resolve, reject) => {\n const https = require('https');\n const path = url.replace('https://api.github.com', '');\n const options = { hostname: 'api.github.com', path, method, headers };\n const req = https.request(options, (res) => {\n let d = '';\n res.on('data', chunk => d += chunk);\n res.on('end', () => {\n if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${d}`));\n else resolve(JSON.parse(d));\n });\n });\n req.setTimeout(15000, () => { req.destroy(); reject(new Error('Request timeout')); });\n req.on('error', reject);\n if (body) req.write(JSON.stringify(body));\n req.end();\n });\n}\n\nconst refData = await request('GET', `${baseUrl}/git/refs/heads/${headBranch}`);\nconst baseSha = refData.object.sha;\nconst commitData = await request('GET', `${baseUrl}/git/commits/${baseSha}`);\nconst baseTreeSha = commitData.tree.sha;\n\nconst newTree = await request('POST', `${baseUrl}/git/trees`, {\n base_tree: baseTreeSha,\n tree: fixedFiles.map(f => ({ path: f.path, mode: '100644', type: 'blob', content: f.content }))\n});\n\nconst newCommit = await request('POST', `${baseUrl}/git/commits`, {\n message: `chore: apply prettier format\\n\\n${filesSummary}`,\n tree: newTree.sha,\n parents: [baseSha]\n});\n\nawait request('PATCH', `${baseUrl}/git/refs/heads/${headBranch}`, { sha: newCommit.sha });\n\nreturn [{ json: { ...data, commitSha: newCommit.sha } }];"
},
"id": "commit-lint",
"name": "[lint] GitHub: \ucee4\ubc0b",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2000,
340
]
},
{
"parameters": {
"method": "POST",
"url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/issues/{{ $json.prNumber }}/comments",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Authorization",
"value": "=Bearer {{ $env.GITHUB_TOKEN }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { body: '\u2705 Prettier \ud3ec\ub9f7 \uc218\uc815 \uc644\ub8cc\\n\\n' + $json.filesSummary + '\\n\\ncommit: `' + $json.commitSha.slice(0, 7) + '`' } }}",
"options": {}
},
"id": "reply-lint",
"name": "[lint] GitHub: PR \ucf54\uba58\ud2b8",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2220,
340
]
},
{
"parameters": {
"method": "GET",
"url": "=https://api.github.com/repos/{{ $('\ucee4\ub9e8\ub4dc \uac10\uc9c0').first().json.repoOwner }}/{{ $('\ucee4\ub9e8\ub4dc \uac10\uc9c0').first().json.repoName }}/pulls/comments/{{ $('\ucee4\ub9e8\ub4dc \uac10\uc9c0').first().json.inReplyToId }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
},
{
"name": "Authorization",
"value": "=Bearer {{ $env.GITHUB_TOKEN }}"
}
]
},
"options": {}
},
"id": "get-original-comment",
"name": "[fix] GitHub: \uc6d0\ubcf8 \ucf54\uba58\ud2b8 \uc870\ud68c",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1120,
620
]
},
{
"parameters": {
"method": "GET",
"url": "=https://api.github.com/repos/{{ $('\ucee4\ub9e8\ub4dc \uac10\uc9c0').first().json.repoOwner }}/{{ $('\ucee4\ub9e8\ub4dc \uac10\uc9c0').first().json.repoName }}/contents/{{ $('\ucee4\ub9e8\ub4dc \uac10\uc9c0').first().json.filePath }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
},
{
"name": "Authorization",
"value": "=Bearer {{ $env.GITHUB_TOKEN }}"
}
]
},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "ref",
"value": "={{ $('\ucee4\ub9e8\ub4dc \uac10\uc9c0').first().json.headBranch }}"
}
]
},
"options": {}
},
"id": "get-file-content",
"name": "[fix] GitHub: \ud30c\uc77c \ub0b4\uc6a9 \uc77d\uae30",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1120,
760
]
},
{
"parameters": {
"jsCode": "const ctx = $('\ucee4\ub9e8\ub4dc \uac10\uc9c0').first().json;\nconst originalComment = $('[fix] GitHub: \uc6d0\ubcf8 \ucf54\uba58\ud2b8 \uc870\ud68c').first().json;\nconst fileData = $('[fix] GitHub: \ud30c\uc77c \ub0b4\uc6a9 \uc77d\uae30').first().json;\n\nconst fileContent = Buffer.from((fileData.content || '').replace(/\\n/g, ''), 'base64').toString('utf8');\nconst fileSha = fileData.sha;\n\nconst commentAuthor = originalComment.user?.login || 'unknown';\nconst commentBody = originalComment.body || '';\nconst commentLine = originalComment.original_line || originalComment.line || '?';\nconst commentDiff = originalComment.diff_hunk || '';\n\n// PR \ubcc0\uacbd \ud30c\uc77c \uc804\uccb4 \uc870\ud68c (\ucf54\uba58\ud2b8 \ub2ec\ub9b0 \ud30c\uc77c + \uad00\ub828 \ud30c\uc77c)\nconst token = $env.GITHUB_TOKEN;\nfunction ghGet(path) {\n return new Promise((resolve, reject) => {\n const https = require('https');\n const req = https.request({\n hostname: 'api.github.com',\n path,\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'User-Agent': 'n8n-jump-section'\n }\n }, (res) => {\n let data = '';\n res.on('data', chunk => data += chunk);\n res.on('end', () => {\n if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${data}`));\n else resolve(JSON.parse(data));\n });\n });\n req.setTimeout(15000, () => { req.destroy(); reject(new Error('Request timeout')); });\n req.on('error', reject);\n req.end();\n });\n}\n\nconst prFilesData = await ghGet(`/repos/${ctx.repoOwner}/${ctx.repoName}/pulls/${ctx.prNumber}/files?per_page=30`);\nconst tsFiles = prFilesData.filter(f => /\\.(ts|tsx)$/.test(f.filename));\n\nconst otherFiles = await Promise.all(\n tsFiles\n .filter(f => f.filename !== ctx.filePath)\n .map(async (f) => {\n const d = await ghGet(`/repos/${ctx.repoOwner}/${ctx.repoName}/contents/${f.filename}?ref=${ctx.headBranch}`);\n const content = Buffer.from((d.content || '').replace(/\\n/g, ''), 'base64').toString('utf8');\n return { path: f.filename, content, sha: d.sha };\n })\n);\n\nconst allFiles = [\n { path: ctx.filePath, content: fileContent, sha: fileSha },\n ...otherFiles\n];\n\nconst otherFilesContext = otherFiles.length > 0\n ? '\\n\\n## PR\uc758 \ub2e4\ub978 \ubcc0\uacbd \ud30c\uc77c (\ucc38\uace0\uc6a9)\\n\\n' + otherFiles.map(f =>\n `### ${f.path}\\n\\`\\`\\`typescript\\n${f.content}\\n\\`\\`\\``\n ).join('\\n\\n')\n : '';\n\nconst userPrompt = `## \uc218\uc815 \uc694\uccad\\n\\nPR: #${ctx.prNumber} ${ctx.prTitle}\\n\\n## \ucf54\uba58\ud2b8 (${commentAuthor}, ${ctx.filePath} line ${commentLine})\\n\\n${commentBody}\\n\\n## \ud574\ub2f9 \ucf54\ub4dc \ucee8\ud14d\uc2a4\ud2b8\\n\\n\\`\\`\\`diff\\n${commentDiff}\\n\\`\\`\\`\\n\\n## \ucf54\uba58\ud2b8 \ub2ec\ub9b0 \ud30c\uc77c \uc804\uccb4\\n\\n\\`\\`\\`typescript\\n${fileContent}\\n\\`\\`\\`${otherFilesContext}\\n\\n---\\n\\n\ucf54\uba58\ud2b8 \ub0b4\uc6a9\uc744 \ubc18\uc601\ud558\uc138\uc694. \uc218\uc815\uc774 \uc5ec\ub7ec \ud30c\uc77c\uc5d0 \uac78\uccd0 \ud544\uc694\ud558\uba74 \uac01 \ud30c\uc77c\ub9c8\ub2e4 write_file\uc744 \ud638\ucd9c\ud558\uc138\uc694. \ucf54\uba58\ud2b8 \uc758\ub3c4\uac00 \uba85\ud655\ud558\uace0 \uc218\uc815 \ubc29\ud5a5\uc774 \ud655\uc2e4\ud55c \uacbd\uc6b0\uc5d0\ub9cc write_file\uc744 \ud638\ucd9c\ud558\uc138\uc694.`;\n\nconst geminiPayload = {\n system_instruction: {\n parts: [{ text: 'TypeScript \uc804\ubb38 \uac1c\ubc1c\uc790\uc785\ub2c8\ub2e4. PR \ub9ac\ubdf0 \ucf54\uba58\ud2b8\ub97c \ubc18\uc601\ud574 \ucf54\ub4dc\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.\\n\\n## \ud544\uc218 \uaddc\uce59\\n\\n### Prettier \ud3ec\ub9f7\\n- \ub4e4\uc5ec\uc4f0\uae30: \uc2a4\ud398\uc774\uc2a4 2\uce78, \uc138\ubbf8\ucf5c\ub860 \ud56d\uc0c1, \uc2f1\uae00\ucffc\ud2b8, \ucd5c\ub300 100\uc790, trailing comma \ud56d\uc0c1\\n\\n### \uc218\uc815 \uc6d0\uce59\\n- \ucf54\uba58\ud2b8 \uc758\ub3c4\uac00 \uba85\ud655\ud558\uace0 \uc218\uc815 \ubc29\ud5a5\uc774 \ud655\uc2e4\ud55c \uacbd\uc6b0\uc5d0\ub9cc write_file \ud638\ucd9c\\n- \uc218\uc815\uc774 \uc5ec\ub7ec \ud30c\uc77c\uc5d0 \uac78\uccd0 \ud544\uc694\ud558\uba74 \uac01 \ud30c\uc77c\ub9c8\ub2e4 write_file\uc744 \ud638\ucd9c (\ud55c \ubc88\uc5d0 \ubaa8\ub450 \uc81c\ucd9c)\\n- \ubaa8\ud638\ud558\uba74 write_file \ud638\ucd9c\ud558\uc9c0 \uc54a\uc74c\\n- \uae30\uc874 Public API \ud558\uc704 \ud638\ud658\uc131 \uc720\uc9c0' }]\n },\n contents: [{ role: 'user', parts: [{ text: userPrompt }] }],\n tools: [{\n functionDeclarations: [{\n name: 'write_file',\n description: '\uc218\uc815\ub41c \ud30c\uc77c\uc758 \uc804\uccb4 \ub0b4\uc6a9\uc744 \uc81c\ucd9c\ud569\ub2c8\ub2e4. \uc5ec\ub7ec \ud30c\uc77c \uc218\uc815 \uc2dc \ud30c\uc77c\ub9c8\ub2e4 \ud55c \ubc88\uc529 \ud638\ucd9c\ud558\uc138\uc694.',\n parameters: {\n type: 'OBJECT',\n properties: {\n path: { type: 'STRING', description: '\ud30c\uc77c \uacbd\ub85c' },\n content: { type: 'STRING', description: '\ud30c\uc77c \uc804\uccb4 \ub0b4\uc6a9' },\n description: { type: 'STRING', description: '\ubcc0\uacbd \uc124\uba85' }\n },\n required: ['path', 'content', 'description']\n }\n }]\n }],\n toolConfig: { functionCallingConfig: { mode: 'ANY' } },\n generationConfig: { maxOutputTokens: 16384 }\n};\n\nreturn [{\n json: {\n ...ctx,\n allFiles,\n fileSha,\n fileContent,\n commentBody,\n commentLine,\n geminiPayload\n }\n}];"
},
"id": "prepare-fix",
"name": "[fix] Gemini \ud504\ub86c\ud504\ud2b8 \uc900\ube44",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1340,
680
]
},
{
"parameters": {
"method": "POST",
"url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={{ $env.GEMINI_API_KEY }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.geminiPayload }}",
"options": {}
},
"id": "gemini-fix",
"name": "[fix] Gemini: \ucf54\ub4dc \uc218\uc815",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1560,
680
]
},
{
"parameters": {
"jsCode": "const response = $input.first().json;\nconst ctx = $('[fix] Gemini \ud504\ub86c\ud504\ud2b8 \uc900\ube44').first().json;\n\nif (!response.candidates || response.candidates.length === 0) {\n return [{ json: { noChanges: true, geminiError: JSON.stringify(response.promptFeedback || response.error || 'no candidates'), ...ctx } }];\n}\n\nconst candidate = response.candidates[0];\nif (!candidate.content || !candidate.content.parts || candidate.finishReason === 'MALFORMED_FUNCTION_CALL') {\n return [{ json: { noChanges: true, geminiError: candidate.finishReason || 'no content', ...ctx } }];\n}\n\nconst parts = candidate.content.parts;\nconst functionCalls = parts.filter(p => p.functionCall && p.functionCall.name === 'write_file');\n\nif (functionCalls.length === 0) {\n return [{ json: { noChanges: true, ...ctx } }];\n}\n\nconst fixedFiles = functionCalls.map(p => {\n const rawContent = p.functionCall.args.content || ''; const content = rawContent.replace(/^```[\\w]*\\n?/, '').replace(/\\n?```$/, '').trim();\n const original = (ctx.allFiles || []).find(f => f.path === p.functionCall.args.path);\n return {\n path: p.functionCall.args.path,\n content,\n description: p.functionCall.args.description,\n sha: original?.sha || null\n };\n});\n\nconst filesSummary = fixedFiles.map(f => `\u2022 \\`${f.path}\\`: ${f.description}`).join('\\n');\n\nreturn [{\n json: {\n noChanges: false,\n ...ctx,\n fixedFiles,\n filesSummary\n }\n}];"
},
"id": "parse-fix",
"name": "[fix] \uc218\uc815 \uacb0\uacfc \ud30c\uc2f1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1780,
680
]
},
{
"parameters": {
"jsCode": "const data = $input.first().json;\nif (data.noChanges || !data.fixedFiles || data.fixedFiles.length === 0) return [];\nconst fixedFiles = data.fixedFiles.filter(f => f.path && f.content && f.content.trim().length > 0);\nif (fixedFiles.length === 0) return [];\nconst { headBranch, repoOwner, repoName, filesSummary } = data;\nconst token = $env.GITHUB_TOKEN;\nconst baseUrl = `https://api.github.com/repos/${repoOwner}/${repoName}`;\n\nconst headers = {\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'Content-Type': 'application/json',\n 'User-Agent': 'n8n-jump-section'\n};\n\nfunction request(method, url, body) {\n return new Promise((resolve, reject) => {\n const https = require('https');\n const path = url.replace('https://api.github.com', '');\n const options = { hostname: 'api.github.com', path, method, headers };\n const req = https.request(options, (res) => {\n let d = '';\n res.on('data', chunk => d += chunk);\n res.on('end', () => {\n if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${d}`));\n else resolve(JSON.parse(d));\n });\n });\n req.setTimeout(15000, () => { req.destroy(); reject(new Error('Request timeout')); });\n req.on('error', reject);\n if (body) req.write(JSON.stringify(body));\n req.end();\n });\n}\n\nconst refData = await request('GET', `${baseUrl}/git/refs/heads/${headBranch}`);\nconst baseSha = refData.object.sha;\nconst commitData = await request('GET', `${baseUrl}/git/commits/${baseSha}`);\nconst baseTreeSha = commitData.tree.sha;\n\nconst newTree = await request('POST', `${baseUrl}/git/trees`, {\n base_tree: baseTreeSha,\n tree: fixedFiles.map(f => ({ path: f.path, mode: '100644', type: 'blob', content: f.content }))\n});\n\nconst newCommit = await request('POST', `${baseUrl}/git/commits`, {\n message: `fix: apply review comment suggestion\\n\\n${filesSummary}`,\n tree: newTree.sha,\n parents: [baseSha]\n});\n\nawait request('PATCH', `${baseUrl}/git/refs/heads/${headBranch}`, { sha: newCommit.sha });\n\nawait request('POST', `${baseUrl}/actions/workflows/auto-format.yml/dispatches`, {\n ref: headBranch,\n inputs: { branch: headBranch }\n});\n\nreturn [{ json: { ...data, commitSha: newCommit.sha } }];"
},
"id": "commit-fix",
"name": "[fix] GitHub: \ucee4\ubc0b",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2000,
680
]
},
{
"parameters": {
"method": "POST",
"url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/pulls/{{ $json.prNumber }}/comments/{{ $json.commentId }}/replies",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Authorization",
"value": "=Bearer {{ $env.GITHUB_TOKEN }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { body: '\u2705 \uc218\uc815 \uc644\ub8cc\\n\\n' + $json.filesSummary + '\\n\\ncommit: `' + $json.commitSha.slice(0, 7) + '`' } }}",
"options": {}
},
"id": "reply-fix",
"name": "[fix] GitHub: \ucf54\uba58\ud2b8 \ub2f5\uc7a5",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2220,
680
]
}
],
"connections": {
"Webhook: PR Comment Event": {
"main": [
[
{
"node": "IF: action=created",
"type": "main",
"index": 0
}
]
]
},
"IF: action=created": {
"main": [
[
{
"node": "\ucee4\ub9e8\ub4dc \uac10\uc9c0",
"type": "main",
"index": 0
}
]
]
},
"\ucee4\ub9e8\ub4dc \uac10\uc9c0": {
"main": [
[
{
"node": "IF: fix-build?",
"type": "main",
"index": 0
}
]
]
},
"IF: fix-build?": {
"main": [
[
{
"node": "[build] \ud30c\uc77c + CI \uc870\ud68c \ubc0f \ud504\ub86c\ud504\ud2b8 \uc900\ube44",
"type": "main",
"index": 0
}
],
[
{
"node": "IF: fix-lint?",
"type": "main",
"index": 0
}
]
]
},
"IF: fix-lint?": {
"main": [
[
{
"node": "[lint] \ud30c\uc77c \uc870\ud68c \ubc0f \ud504\ub86c\ud504\ud2b8 \uc900\ube44",
"type": "main",
"index": 0
}
],
[
{
"node": "[fix] GitHub: \uc6d0\ubcf8 \ucf54\uba58\ud2b8 \uc870\ud68c",
"type": "main",
"index": 0
}
]
]
},
"[build] \ud30c\uc77c + CI \uc870\ud68c \ubc0f \ud504\ub86c\ud504\ud2b8 \uc900\ube44": {
"main": [
[
{
"node": "[build] Gemini: \ucf54\ub4dc \uc218\uc815",
"type": "main",
"index": 0
}
]
]
},
"[build] Gemini: \ucf54\ub4dc \uc218\uc815": {
"main": [
[
{
"node": "[build] \uc218\uc815 \uacb0\uacfc \ud30c\uc2f1",
"type": "main",
"index": 0
}
]
]
},
"[build] \uc218\uc815 \uacb0\uacfc \ud30c\uc2f1": {
"main": [
[
{
"node": "[build] IF: \uc218\uc815\ub41c \ud30c\uc77c \uc788\ub294\uc9c0",
"type": "main",
"index": 0
}
]
]
},
"[build] IF: \uc218\uc815\ub41c \ud30c\uc77c \uc788\ub294\uc9c0": {
"main": [
[
{
"node": "[build] GitHub: \ucee4\ubc0b",
"type": "main",
"index": 0
}
],
[]
]
},
"[build] GitHub: \ucee4\ubc0b": {
"main": [
[
{
"node": "[build] GitHub: PR \ucf54\uba58\ud2b8",
"type": "main",
"index": 0
}
]
]
},
"[lint] \ud30c\uc77c \uc870\ud68c \ubc0f \ud504\ub86c\ud504\ud2b8 \uc900\ube44": {
"main": [
[
{
"node": "[lint] Gemini: \ud3ec\ub9f7 \uc218\uc815",
"type": "main",
"index": 0
}
]
]
},
"[lint] Gemini: \ud3ec\ub9f7 \uc218\uc815": {
"main": [
[
{
"node": "[lint] \uc218\uc815 \uacb0\uacfc \ud30c\uc2f1",
"type": "main",
"index": 0
}
]
]
},
"[lint] \uc218\uc815 \uacb0\uacfc \ud30c\uc2f1": {
"main": [
[
{
"node": "[lint] IF: \uc218\uc815\ub41c \ud30c\uc77c \uc788\ub294\uc9c0",
"type": "main",
"index": 0
}
]
]
},
"[lint] IF: \uc218\uc815\ub41c \ud30c\uc77c \uc788\ub294\uc9c0": {
"main": [
[
{
"node": "[lint] GitHub: \ucee4\ubc0b",
"type": "main",
"index": 0
}
],
[]
]
},
"[lint] GitHub: \ucee4\ubc0b": {
"main": [
[
{
"node": "[lint] GitHub: PR \ucf54\uba58\ud2b8",
"type": "main",
"index": 0
}
]
]
},
"[fix] GitHub: \uc6d0\ubcf8 \ucf54\uba58\ud2b8 \uc870\ud68c": {
"main": [
[
{
"node": "[fix] GitHub: \ud30c\uc77c \ub0b4\uc6a9 \uc77d\uae30",
"type": "main",
"index": 0
}
]
]
},
"[fix] GitHub: \ud30c\uc77c \ub0b4\uc6a9 \uc77d\uae30": {
"main": [
[
{
"node": "[fix] Gemini \ud504\ub86c\ud504\ud2b8 \uc900\ube44",
"type": "main",
"index": 0
}
]
]
},
"[fix] Gemini \ud504\ub86c\ud504\ud2b8 \uc900\ube44": {
"main": [
[
{
"node": "[fix] Gemini: \ucf54\ub4dc \uc218\uc815",
"type": "main",
"index": 0
}
]
]
},
"[fix] Gemini: \ucf54\ub4dc \uc218\uc815": {
"main": [
[
{
"node": "[fix] \uc218\uc815 \uacb0\uacfc \ud30c\uc2f1",
"type": "main",
"index": 0
}
]
]
},
"[fix] \uc218\uc815 \uacb0\uacfc \ud30c\uc2f1": {
"main": [
[
{
"node": "[fix] GitHub: \ucee4\ubc0b",
"type": "main",
"index": 0
}
]
]
},
"[fix] GitHub: \ucee4\ubc0b": {
"main": [
[
{
"node": "[fix] GitHub: \ucf54\uba58\ud2b8 \ub2f5\uc7a5",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "jump-section"
},
{
"name": "comment-fix"
}
]
}
About this workflow
jump-section: Comment Fix Pipeline. Uses httpRequest. Webhook trigger; 24 nodes.
Source: https://github.com/bae080311/jump-section/blob/c566471882fa8b8f0e4d00704693b7c92d62c519/.n8n/workflows/comment-fix-pipeline.json — original creator credit. Request a take-down →