{
  "id": "56d9907d-c77f-40a6-a529-1e3e57966cca",
  "name": "Read-Only GitHub PR Review Draft",
  "nodes": [
    {
      "parameters": {
        "content": "# Reviewed Harshith.com template\n\nReads files from one public pull request, bounds and redacts the diff, then asks OpenAI for a review draft. It never posts to GitHub.\n\nAttach your own OpenAI credential, use an authorized repository, and review the generated text. Large or sensitive diffs should not be sent to a third-party model. This export contains no credentials, deployment-specific IDs, webhook IDs, or pinned data.",
        "height": 350,
        "width": 460,
        "color": 7
      },
      "id": "pr-note-001",
      "name": "Read Me First",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-800, -330]
    },
    {
      "parameters": {},
      "id": "pr-trigger-001",
      "name": "Run Manually",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [-680, 60]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "pr-owner-field",
              "name": "owner",
              "type": "string",
              "value": "n8n-io"
            },
            {
              "id": "pr-repository-field",
              "name": "repository",
              "type": "string",
              "value": "n8n"
            },
            {
              "id": "pr-number-field",
              "name": "pullNumber",
              "type": "number",
              "value": 36260
            },
            {
              "id": "pr-limit-field",
              "name": "maxDiffCharacters",
              "type": "number",
              "value": 12000
            }
          ]
        },
        "options": {}
      },
      "id": "pr-config-001",
      "name": "Set Authorized Pull Request",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [-430, 60]
    },
    {
      "parameters": {
        "url": "=https://api.github.com/repos/{{ $json.owner }}/{{ $json.repository }}/pulls/{{ $json.pullNumber }}/files?per_page=100&page=1",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "options": {}
      },
      "id": "pr-fetch-001",
      "name": "Read Pull Request Files",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [-160, 60]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const config = $('Set Authorized Pull Request').first().json;\nconst files = $input.all().map((item) => item.json);\nconst apiPageLimit = 100;\nconst fileListMayBeIncomplete = files.length === apiPageLimit;\nconst secretLike = /(api[_-]?key|secret|token|password|authorization)\\s*[:=]\\s*[^\\s,;]+/gi;\nconst combined = files.map((file) => {\n  const patch = String(file.patch || '').replace(secretLike, '$1=[redacted]');\n  return `FILE: ${file.filename || 'unknown'}\\nSTATUS: ${file.status || 'unknown'}\\n${patch}`;\n}).join('\\n\\n');\nconst max = Number(config.maxDiffCharacters) || 12000;\nconst bounded = combined.slice(0, max);\nconst characterLimitReached = combined.length > bounded.length;\nconst scopeNotice = fileListMayBeIncomplete\n  ? `Scope warning: GitHub returned the first ${apiPageLimit} files only. Treat this as a partial review.`\n  : 'Scope: GitHub returned fewer than 100 files for this pull request.';\nconst truncationReasons = [];\nif (fileListMayBeIncomplete) truncationReasons.push('github_file_page_limit');\nif (characterLimitReached) truncationReasons.push('character_limit');\n\nreturn [{ json: {\n  prompt: `Review this bounded pull request diff. Identify correctness, security, maintainability, and test gaps. Cite filenames. Do not invent code outside the diff. Return a draft for human review, not an approval.\\n\\n${scopeNotice}\\n\\n${bounded}`,\n  sourceUrl: `https://github.com/${config.owner}/${config.repository}/pull/${config.pullNumber}`,\n  filesRead: files.length,\n  fileListMayBeIncomplete,\n  characterLimitReached,\n  truncated: truncationReasons.length > 0,\n  truncationReasons,\n  status: 'draft_only'\n} }];"
      },
      "id": "pr-bound-001",
      "name": "Bound and Redact Diff",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [120, 60]
    },
    {
      "parameters": {
        "messages": {
          "values": [
            {
              "role": "user",
              "content": "={{ $json.prompt }}"
            }
          ]
        },
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0.2
        }
      },
      "id": "pr-openai-001",
      "name": "Draft Review with OpenAI",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [400, 60]
    }
  ],
  "connections": {
    "Run Manually": {
      "main": [[{"node": "Set Authorized Pull Request", "type": "main", "index": 0}]]
    },
    "Set Authorized Pull Request": {
      "main": [[{"node": "Read Pull Request Files", "type": "main", "index": 0}]]
    },
    "Read Pull Request Files": {
      "main": [[{"node": "Bound and Redact Diff", "type": "main", "index": 0}]]
    },
    "Bound and Redact Diff": {
      "main": [[{"node": "Draft Review with OpenAI", "type": "main", "index": 0}]]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveDataErrorExecution": "none",
    "saveDataSuccessExecution": "none",
    "saveManualExecutions": false,
    "executionTimeout": 60
  },
  "tags": []
}
