AutomationFlowsGeneral › jump-section: Comment Fix Pipeline

jump-section: Comment Fix Pipeline

jump-section: Comment Fix Pipeline. Uses httpRequest. Webhook trigger; 24 nodes.

Webhook trigger★★★★☆ complexity24 nodesHttp Request
General Trigger: Webhook Nodes: 24 Complexity: ★★★★☆

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 →

Download .json
{
  "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 →

More General workflows → · Browse all categories →