{
  "id": "GRGQy17eKiwTWmlSRmig-",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Markdown Proofreader with GitHub Auto-Commit",
  "tags": [],
  "nodes": [
    {
      "id": "33bdd4e6-8838-40e2-b496-09548ba8de69",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        544,
        1444
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "4f99d9fb-b157-4032-86e3-764320fecafb",
      "name": "Has Issues?",
      "type": "n8n-nodes-base.if",
      "position": [
        2016,
        1444
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "b9bee30e-1623-422f-9003-8d8c738c2b65",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.issueCount }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "9d36d265-9c5f-496a-8dcc-6108117f5cb0",
      "name": "Edits Applied?",
      "type": "n8n-nodes-base.if",
      "position": [
        3040,
        1244
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "918549c9-1a46-4e12-b333-1b07c201cb12",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.success }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "84feed8e-8ab1-403d-ac19-c5eaccedd6ab",
      "name": "Fetch Blog Post from GitHub",
      "type": "n8n-nodes-base.github",
      "position": [
        992,
        1444
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.repoOwner }}"
        },
        "filePath": "={{ $json.filePath }}",
        "resource": "file",
        "operation": "get",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.repoName }}"
        },
        "authentication": "oAuth2",
        "asBinaryProperty": false,
        "additionalParameters": {
          "reference": ""
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "5a646f46-5b55-475d-a8cf-7b16d88b67f6",
      "name": "Decode Base64 & Add Line Numbers",
      "type": "n8n-nodes-base.code",
      "position": [
        1216,
        1444
      ],
      "parameters": {
        "jsCode": "// Decode base64 and add line numbers for QA\n\nconst fileContent = $input.first().json.content;\nconst fileEncoding = $input.first().json.encoding;\nconst fileSha = $input.first().json.sha;\nconst filePath = $input.first().json.path;\n\nlet decodedContent = fileContent;\nif (fileEncoding === 'base64') {\n  decodedContent = Buffer.from(fileContent, 'base64').toString('utf-8');\n}\n\nconst lines = decodedContent.split('\\n');\nconst numberedLines = lines.map((line, index) => {\n  const lineNumber = index + 1;\n  return `${lineNumber}: ${line}`;\n});\nconst numberedContent = numberedLines.join('\\n');\n\nreturn {\n  json: {\n    originalContent: decodedContent,\n    numberedContent: numberedContent,\n    lineCount: lines.length,\n    sha: fileSha,\n    path: filePath\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c0c3c967-0d82-4d91-bcdd-3a3b651b9f7f",
      "name": "QA Agent - Analyze Content",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueErrorOutput",
      "maxTries": 3,
      "position": [
        1440,
        1444
      ],
      "parameters": {
        "text": "=Analyze this blog post and return issues as JSON array:\n\n{{ $json.numberedContent }}",
        "options": {
          "systemMessage": "You are a content QA specialist. Analyze the provided Markdown blog post and identify issues.\n\nFor each issue found, output a JSON object with these fields:\n- line_number: integer (1-indexed line number where issue occurs)\n- issue_type: one of \"tone\", \"clarity\", \"grammar\", \"structure\", \"accuracy\", \"style\"\n- severity: one of \"low\", \"medium\", \"high\"\n- description: brief explanation of the problem\n- suggested_fix: the corrected text for that line (or null if deletion recommended)\n\nCRITICAL RULES:\n1. Output ONLY valid JSON array. No markdown, no explanation, no preamble.\n2. Each issue must reference a specific line number.\n3. Be precise about line numbers - count from 1, include blank lines.\n4. Focus on substantive issues, not nitpicks.\n5. suggested_fix should be the COMPLETE corrected line, not a partial fix."
        },
        "promptType": "define",
        "needsFallback": true
      },
      "retryOnFail": true,
      "typeVersion": 3.1,
      "waitBetweenTries": 5000
    },
    {
      "id": "d0714374-0aa1-43c7-96e7-158188715b7f",
      "name": "QA Agent LLM",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1448,
        1668
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "bec92205-1dfc-476d-bf30-3b25584ff92f",
      "name": "Parse QA Issues JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        1792,
        1444
      ],
      "parameters": {
        "jsCode": "// Parse QA output and filter to high/medium severity only\n\nconst qaAgentResponse = $input.first().json.output;\n\nlet cleanedResponse = qaAgentResponse.trim();\nif (cleanedResponse.startsWith('```json')) {\n  cleanedResponse = cleanedResponse.slice(7);\n}\nif (cleanedResponse.startsWith('```')) {\n  cleanedResponse = cleanedResponse.slice(3);\n}\nif (cleanedResponse.endsWith('```')) {\n  cleanedResponse = cleanedResponse.slice(0, -3);\n}\ncleanedResponse = cleanedResponse.trim();\n\ntry {\n  const allIssues = JSON.parse(cleanedResponse);\n  \n  if (!Array.isArray(allIssues)) {\n    throw new Error('QA output is not an array');\n  }\n  \n  const validIssues = allIssues.filter(issue => {\n    const hasLineNumber = issue.line_number && typeof issue.line_number === 'number';\n    const hasIssueType = issue.issue_type;\n    const hasDescription = issue.description;\n    const isHighOrMedium = issue.severity === 'high' || issue.severity === 'medium';\n    \n    return hasLineNumber && hasIssueType && hasDescription && isHighOrMedium;\n  });\n  \n  const originalContent = $('Decode Base64 & Add Line Numbers').first().json.originalContent;\n  const fileSha = $('Decode Base64 & Add Line Numbers').first().json.sha;\n  const filePath = $('Decode Base64 & Add Line Numbers').first().json.path;\n  \n  return {\n    json: {\n      issues: validIssues,\n      issueCount: validIssues.length,\n      originalContent: originalContent,\n      sha: fileSha,\n      path: filePath,\n      parseSuccess: true\n    }\n  };\n\n} catch (error) {\n  return {\n    json: {\n      issues: [],\n      issueCount: 0,\n      parseError: error.message,\n      rawResponse: qaAgentResponse,\n      parseSuccess: false\n    }\n  };\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "4ac7a9f7-a656-4c5b-baf3-e7f09ab00af9",
      "name": "Editor Agent - Generate Edit Ops",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueErrorOutput",
      "maxTries": 3,
      "position": [
        2240,
        1140
      ],
      "parameters": {
        "text": "=Convert these QA issues into edit operations:\n\n{{ JSON.stringify($json.issues, null, 2) }}",
        "options": {
          "systemMessage": "You are a precise text editor. You receive QA issues and convert them into executable edit instructions.\n\nOutput: A JSON array of edit operations. Each operation must have:\n- operation: one of \"replace\", \"insert_after\", \"delete\"\n- line_number: integer (which line to modify)\n- new_text: string (the new content, required for replace/insert_after, omit for delete)\n\nCRITICAL RULES:\n1. Output ONLY valid JSON array. No markdown, no explanation.\n2. Sort operations by line_number in DESCENDING order (highest first).\n3. Only include operations where suggested_fix is not null.\n4. For \"replace\": new_text is the complete replacement line.\n5. Validate that line_number is a positive integer."
        },
        "promptType": "define",
        "needsFallback": true
      },
      "retryOnFail": true,
      "typeVersion": 3.1,
      "waitBetweenTries": 5000
    },
    {
      "id": "7e2ccc24-e5de-4bc4-b3a8-5c35a6018c71",
      "name": "Editor Agent LLM",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        2248,
        1364
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "1946202a-a925-4fc4-a2f0-f98e09f96aa7",
      "name": "Parse Edit Operations JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        2592,
        1244
      ],
      "parameters": {
        "jsCode": "// Parse editor output and sort edits descending (bottom-to-top)\n\nconst editorAgentResponse = $input.first().json.output;\n\nlet cleanedResponse = editorAgentResponse.trim();\nif (cleanedResponse.startsWith('```json')) {\n  cleanedResponse = cleanedResponse.slice(7);\n}\nif (cleanedResponse.startsWith('```')) {\n  cleanedResponse = cleanedResponse.slice(3);\n}\nif (cleanedResponse.endsWith('```')) {\n  cleanedResponse = cleanedResponse.slice(0, -3);\n}\ncleanedResponse = cleanedResponse.trim();\n\ntry {\n  const editOperations = JSON.parse(cleanedResponse);\n  \n  if (!Array.isArray(editOperations)) {\n    throw new Error('Editor output is not an array');\n  }\n  \n  const sortedEdits = editOperations.sort((a, b) => {\n    return b.line_number - a.line_number;\n  });\n  \n  const originalContent = $('Parse QA Issues JSON').first().json.originalContent;\n  const fileSha = $('Parse QA Issues JSON').first().json.sha;\n  const filePath = $('Parse QA Issues JSON').first().json.path;\n  const issues = $('Parse QA Issues JSON').first().json.issues;\n  \n  return {\n    json: {\n      edits: sortedEdits,\n      editCount: sortedEdits.length,\n      originalContent: originalContent,\n      sha: fileSha,\n      path: filePath,\n      issues: issues,\n      parseSuccess: true\n    }\n  };\n\n} catch (error) {\n  return {\n    json: {\n      edits: [],\n      editCount: 0,\n      parseError: error.message,\n      rawResponse: editorAgentResponse,\n      parseSuccess: false\n    }\n  };\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "c83f964b-4ca9-4666-9d26-a89cc99b0700",
      "name": "Apply Line-by-Line Edits",
      "type": "n8n-nodes-base.code",
      "position": [
        2816,
        1244
      ],
      "parameters": {
        "jsCode": "// Apply edits to content, skip invalid lines, track success rate\n\nconst originalContent = $input.first().json.originalContent;\nconst editOperations = $input.first().json.edits;\nconst fileSha = $input.first().json.sha;\nconst filePath = $input.first().json.path;\nconst issues = $input.first().json.issues;\n\nlet lines = originalContent.split('\\n');\nconst totalLines = lines.length;\n\neditOperations.sort((a, b) => b.line_number - a.line_number);\n\nlet appliedCount = 0;\nlet skippedCount = 0;\nconst appliedChanges = [];\nconst skippedChanges = [];\n\nfor (const edit of editOperations) {\n  const arrayIndex = edit.line_number - 1;\n  \n  if (arrayIndex < 0 || arrayIndex >= totalLines) {\n    skippedCount++;\n    skippedChanges.push({\n      line: edit.line_number,\n      reason: 'Line number out of range'\n    });\n    continue;\n  }\n  \n  if (edit.operation === 'replace') {\n    appliedChanges.push({\n      line: edit.line_number,\n      operation: 'replace',\n      oldText: lines[arrayIndex],\n      newText: edit.new_text\n    });\n    lines[arrayIndex] = edit.new_text;\n    appliedCount++;\n    \n  } else if (edit.operation === 'insert_after') {\n    appliedChanges.push({\n      line: edit.line_number,\n      operation: 'insert_after',\n      insertedText: edit.new_text\n    });\n    lines.splice(arrayIndex + 1, 0, edit.new_text);\n    appliedCount++;\n    \n  } else if (edit.operation === 'delete') {\n    appliedChanges.push({\n      line: edit.line_number,\n      operation: 'delete',\n      deletedText: lines[arrayIndex]\n    });\n    lines.splice(arrayIndex, 1);\n    appliedCount++;\n    \n  } else {\n    skippedCount++;\n    skippedChanges.push({\n      line: edit.line_number,\n      reason: 'Unknown operation: ' + edit.operation\n    });\n  }\n}\n\nconst totalEdits = appliedCount + skippedCount;\nconst successRate = totalEdits > 0 ? appliedCount / totalEdits : 1;\nconst isSuccess = successRate >= 0.5 && appliedCount > 0;\nconst updatedContent = lines.join('\\n');\n\nreturn {\n  json: {\n    updatedContent: updatedContent,\n    sha: fileSha,\n    path: filePath,\n    issues: issues,\n    applied: appliedCount,\n    skipped: skippedCount,\n    successRate: successRate,\n    success: isSuccess,\n    appliedChanges: appliedChanges,\n    skippedChanges: skippedChanges\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "21eda142-611b-4d86-86aa-862dae40b7aa",
      "name": "Commit Updated File to GitHub",
      "type": "n8n-nodes-base.github",
      "position": [
        3264,
        1296
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Github Config').item.json.repoOwner }}"
        },
        "filePath": "={{ $('Github Config').item.json.filePath }}",
        "resource": "file",
        "operation": "edit",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Github Config').item.json.repoName }}"
        },
        "fileContent": "={{ $json.updatedContent }}",
        "commitMessage": "=AI QA: Applied {{ $json.applied }} edit(s) - {{ new Date().toISOString().slice(0,10) }}",
        "authentication": "oAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "02c73e7f-f250-42f7-a0ac-2f5bba509353",
      "name": "Format QA Report",
      "type": "n8n-nodes-base.code",
      "position": [
        3264,
        1104
      ],
      "parameters": {
        "jsCode": "// Build markdown report with applied changes\n\nconst issues = $input.first().json.issues;\nconst appliedChanges = $input.first().json.appliedChanges;\nconst skippedChanges = $input.first().json.skippedChanges;\nconst successRate = $input.first().json.successRate;\nconst fileSha = $input.first().json.sha;\nconst filePath = $input.first().json.path;\nconst updatedContent = $input.first().json.updatedContent;\nconst isSuccess = $input.first().json.success;\n\nconst today = new Date().toISOString().slice(0, 10);\n\nlet report = '';\nreport += `# QA Report - ${today}\\n\\n`;\nreport += `## Summary\\n`;\nreport += `- Applied: ${appliedChanges.length}\\n`;\nreport += `- Skipped: ${skippedChanges.length}\\n`;\nreport += `- Success Rate: ${(successRate * 100).toFixed(0)}%\\n\\n`;\n\nreport += `## Issues Found\\n\\n`;\nfor (const issue of issues) {\n  report += `### Line ${issue.line_number} (${issue.issue_type})\\n`;\n  report += `${issue.description}\\n\\n`;\n}\n\nreport += `## Changes Applied\\n\\n`;\nfor (const change of appliedChanges) {\n  report += `- Line ${change.line}: ${change.operation}\\n`;\n}\n\nif (skippedChanges.length > 0) {\n  report += `\\n## Skipped Edits\\n\\n`;\n  for (const skip of skippedChanges) {\n    report += `- Line ${skip.line}: ${skip.reason}\\n`;\n  }\n}\n\nreturn {\n  json: {\n    report: report,\n    sha: fileSha,\n    path: filePath,\n    updatedContent: updatedContent,\n    success: isSuccess\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "50cff18d-b1a2-4863-aa60-5777652e116d",
      "name": "Save QA Report to GitHub",
      "type": "n8n-nodes-base.github",
      "position": [
        3488,
        1104
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Github Config').item.json.repoOwner }}"
        },
        "filePath": "=qa-reports/{{ new Date().toISOString().slice(0,19).replace(/:/g, '-') }}-report.md",
        "resource": "file",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Github Config').item.json.repoName }}"
        },
        "fileContent": "={{ $json.report }}",
        "commitMessage": "=AI QA: Content improvements - {{ new Date().toISOString().slice(0,10) }}",
        "authentication": "oAuth2"
      },
      "typeVersion": 1.1
    },
    {
      "id": "67756c95-93a2-4a75-9ba6-d0d292bb6de3",
      "name": "Save QA Report to GitHub Without Issues",
      "type": "n8n-nodes-base.github",
      "position": [
        2624,
        1664
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Github Config').item.json.repoOwner }}"
        },
        "filePath": "=qa-reports/{{ new Date().toISOString().slice(0,19).replace(/:/g, '-') }}-CLEAN-report.md",
        "resource": "file",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Github Config').item.json.repoName }}"
        },
        "fileContent": "={{ $json.report }}",
        "commitMessage": "=AI QA: Content improvements - {{ new Date().toISOString().slice(0,10) }}",
        "authentication": "oAuth2"
      },
      "typeVersion": 1.1
    },
    {
      "id": "4290553e-f274-4fe5-bac2-0938fd1076cc",
      "name": "Format Clean Report",
      "type": "n8n-nodes-base.code",
      "position": [
        2336,
        1664
      ],
      "parameters": {
        "jsCode": "// Report when no issues found\n\nconst today = new Date().toISOString().slice(0, 10);\nconst filePath = $('Parse QA Issues JSON').first().json.path;\n\nlet report = '';\nreport += `# QA Report - ${today}\\n\\n`;\nreport += `## Summary\\n`;\nreport += `\u2705 No issues detected\\n\\n`;\nreport += `The blog post passed QA review with no changes required.\\n`;\n\nreturn {\n  json: {\n    report: report,\n    path: filePath\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "6bcfdc7f-1a6e-4e93-bd98-a79bcde46999",
      "name": "Format Failure Report",
      "type": "n8n-nodes-base.code",
      "position": [
        3264,
        1488
      ],
      "parameters": {
        "jsCode": "// Report when edits failed to apply\n\nconst skippedChanges = $input.first().json.skippedChanges || [];\nconst appliedCount = $input.first().json.applied || 0;\nconst skippedCount = $input.first().json.skipped || 0;\nconst successRate = $input.first().json.successRate || 0;\n\nconst today = new Date().toISOString().slice(0, 10);\n\nlet report = '';\nreport += `# QA Report - ${today}\\n\\n`;\nreport += `## \u26a0\ufe0f Commit Skipped\\n\\n`;\nreport += `The edits could not be safely applied. No changes were made to the file.\\n\\n`;\nreport += `## Summary\\n`;\nreport += `- Applied: ${appliedCount}\\n`;\nreport += `- Skipped: ${skippedCount}\\n`;\nreport += `- Success Rate: ${(successRate * 100).toFixed(0)}%\\n\\n`;\n\nif (skippedChanges.length > 0) {\n  report += `## Skipped Edits\\n\\n`;\n  for (const skip of skippedChanges) {\n    report += `- Line ${skip.line}: ${skip.reason}\\n`;\n  }\n}\n\nreport += `\\n## Next Steps\\n`;\nreport += `- Review the QA issues manually\\n`;\nreport += `- Check if line numbers are correct\\n`;\nreport += `- Re-run the workflow after fixing the source content\\n`;\n\nreturn {\n  json: {\n    report: report,\n    path: $input.first().json.path\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "64fe16da-4204-4614-a4c6-cea41a45cea9",
      "name": "Create a file",
      "type": "n8n-nodes-base.github",
      "position": [
        3488,
        1488
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Github Config').item.json.repoOwner }}"
        },
        "filePath": "=qa-reports/{{ new Date().toISOString().slice(0,19).replace(/:/g, '-') }}-FAILED-report.md",
        "resource": "file",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Github Config').item.json.repoName }}"
        },
        "fileContent": "={{ $json.report }}",
        "commitMessage": "=AI QA: Edit application failed - {{ new Date().toISOString().slice(0,10) }}",
        "authentication": "oAuth2"
      },
      "typeVersion": 1.1
    },
    {
      "id": "04705019-90b0-4a47-8856-8576d30923be",
      "name": "Fallback Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "position": [
        1576,
        1668
      ],
      "parameters": {
        "model": "openai/gpt-oss-20b",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "a85b931f-f5e2-4772-9f60-cb756aa6e7f9",
      "name": "Fallback Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "position": [
        2376,
        1364
      ],
      "parameters": {
        "model": "openai/gpt-oss-20b",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "5a71abda-d41c-4651-b945-2e94f252d9dd",
      "name": "Github Config",
      "type": "n8n-nodes-base.set",
      "position": [
        768,
        1444
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "755e6a1a-c3ec-4428-890d-8c5ffa26d97f",
              "name": "repoOwner",
              "type": "string",
              "value": "YOUR_GITHUB_USERNAME"
            },
            {
              "id": "04fe558c-666e-4b7f-8423-8b19492852c4",
              "name": "repoName",
              "type": "string",
              "value": "blog-n8n"
            },
            {
              "id": "dc73920f-6c6c-4f6c-821c-7dbace4427d0",
              "name": "filePath",
              "type": "string",
              "value": "blog-post.md"
            },
            {
              "id": "2b742aa2-9782-4e8c-ba9f-864418cfbb77",
              "name": "reportFolder",
              "type": "string",
              "value": "qa-reports"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "bac9b915-2374-47ed-a3dd-f0b3b6cd008b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -32,
        1088
      ],
      "parameters": {
        "width": 480,
        "height": 832,
        "content": "## AI Automated Markdown Blog QA\n\n### How it works\n\n1.  Fetch: A manual trigger fetches your target markdown file from GitHub.\n\n2. Prep: The file is decoded and numbered for the AI.\n\n3. Agent 1 (Analysis): The QA Agent identifies tone, clarity, and grammar issues, outputting JSON.\n\n4. Agent 2 (Editing): The Editor Agent translates those issues into precise line-by-line edits.\n\n5. Execute: The workflow applies the edits, then commits the updated file and a Markdown report to GitHub.\n\n### Setup steps\n\n- [ ] Connect GitHub OAuth2 to all GitHub nodes.\n- [ ] Add Google Gemini and Groq API keys.\n- [ ] Update the Github Config node (repoOwner, repoName, filePath).\n\n\n### Customization\n\n- Customize\n\n- Style: Edit the QA Agent's prompt to enforce brand rules.\n\n- Models: Swap Gemini/Groq for OpenAI or Anthropic.\n\n- Routing: Adjust the GitHub nodes to change report destinations."
      },
      "typeVersion": 1
    },
    {
      "id": "387ddb16-cabc-400a-9cf3-8c5b9369163c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        1360
      ],
      "parameters": {
        "color": 7,
        "width": 912,
        "height": 304,
        "content": "## Get blog post"
      },
      "typeVersion": 1
    },
    {
      "id": "26baa3ac-0294-47bf-be5f-224c15366366",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1408,
        1360
      ],
      "parameters": {
        "color": 7,
        "width": 736,
        "height": 464,
        "content": "## Analyze content\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3a88ad17-39f9-4563-9aa8-e410142ce4cd",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2192,
        1040
      ],
      "parameters": {
        "color": 7,
        "width": 976,
        "height": 480,
        "content": "## Generate & apply edits\n"
      },
      "typeVersion": 1
    },
    {
      "id": "20e71b57-af0b-491e-811f-302837aca8d9",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3200,
        1024
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 720,
        "content": "## Update repository"
      },
      "typeVersion": 1
    },
    {
      "id": "a30e6809-8331-4a78-bb9a-5dd3a0126120",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        1584
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 288,
        "content": "## Path: No Issues Found"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "f64eb8e8-dc8b-4f93-ab85-7d3d231858ef",
  "connections": {
    "Has Issues?": {
      "main": [
        [
          {
            "node": "Editor Agent - Generate Edit Ops",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Clean Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QA Agent LLM": {
      "ai_languageModel": [
        [
          {
            "node": "QA Agent - Analyze Content",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Github Config": {
      "main": [
        [
          {
            "node": "Fetch Blog Post from GitHub",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edits Applied?": {
      "main": [
        [
          {
            "node": "Commit Updated File to GitHub",
            "type": "main",
            "index": 0
          },
          {
            "node": "Format QA Report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Failure Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fallback Model": {
      "ai_languageModel": [
        [
          {
            "node": "Editor Agent - Generate Edit Ops",
            "type": "ai_languageModel",
            "index": 1
          }
        ]
      ]
    },
    "Editor Agent LLM": {
      "ai_languageModel": [
        [
          {
            "node": "Editor Agent - Generate Edit Ops",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Format QA Report": {
      "main": [
        [
          {
            "node": "Save QA Report to GitHub",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fallback Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "QA Agent - Analyze Content",
            "type": "ai_languageModel",
            "index": 1
          }
        ]
      ]
    },
    "Format Clean Report": {
      "main": [
        [
          {
            "node": "Save QA Report to GitHub Without Issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse QA Issues JSON": {
      "main": [
        [
          {
            "node": "Has Issues?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Failure Report": {
      "main": [
        [
          {
            "node": "Create a file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apply Line-by-Line Edits": {
      "main": [
        [
          {
            "node": "Edits Applied?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Edit Operations JSON": {
      "main": [
        [
          {
            "node": "Apply Line-by-Line Edits",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QA Agent - Analyze Content": {
      "main": [
        [
          {
            "node": "Parse QA Issues JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Blog Post from GitHub": {
      "main": [
        [
          {
            "node": "Decode Base64 & Add Line Numbers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decode Base64 & Add Line Numbers": {
      "main": [
        [
          {
            "node": "QA Agent - Analyze Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Editor Agent - Generate Edit Ops": {
      "main": [
        [
          {
            "node": "Parse Edit Operations JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Github Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}