{
  "name": "Code Review with Claude Subscription",
  "nodes": [
    {
      "parameters": {
        "formTitle": "Code Review",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Merge Request ID",
              "fieldName": "mr_iid",
              "requiredField": true
            },
            {
              "fieldLabel": "Project",
              "fieldName": "project_id",
              "defaultValue": "17"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.5,
      "position": [
        -432,
        -192
      ],
      "id": "34e44e9c-b6f5-45ba-82e2-db8cdf0bccb0",
      "name": "On form submission"
    },
    {
      "parameters": {
        "command": "if [ -d \"/home/node/nimble-client/.git\" ]; then\n  git -C /home/node/nimble-client pull --ff-only\nelse\n  git clone --depth 1 \"$REPO_URL\" /home/node/nimble-client\nfi"
      },
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        -240,
        -192
      ],
      "id": "a1b2c3d4-1111-2222-3333-444444444420",
      "name": "Update Repo"
    },
    {
      "parameters": {
        "url": "={{ 'https://gitlab.ninternal.com/api/v4/projects/' + $('On form submission').item.json.project_id + '/merge_requests/' + $('On form submission').item.json.mr_iid + '/changes' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "gitlabApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "PRIVATE-TOKEN",
              "value": "={{ $env.GITLAB_TOKEN }}"
            },
            {}
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        -16,
        -32
      ],
      "id": "5b77ab09-ca9e-4029-b044-86e794589939",
      "name": "HTTP Request",
      "credentials": {
        "gitlabApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// 1. Grab the 'changes' array from the previous node (HTTP Request)\n// .item is used to access the specific data object n8n just received\nconst changes = $input.first().json.changes;\n\n// 2. Filter out lock files and SVGs (Your exact Bun logic)\nconst filtered = changes.filter((c) => \n  !c.new_path.includes(\"lock\") && !c.new_path.endsWith(\".svg\")\n);\n\n// 3. Format the diffs into a single clean string\nconst formattedDiffs = filtered\n  .map((c) => `File: ${c.new_path}\\n${c.diff}`)\n  .join(\"\\n\\n\");\n\n// 4. Return the data for the next step\nreturn { \n  diff: formattedDiffs,\n  hasChanges: filtered.length > 0 \n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        160,
        -64
      ],
      "id": "396cb0d0-5bae-47dd-a66a-c6a1d81f89aa",
      "name": "Code in JavaScript"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "set-system-prompt-001",
              "name": "systemPrompt",
              "value": "You are a Senior Staff Frontend Engineer doing an INDEPENDENT review of a Git diff. Do NOT hold back findings \u2014 a second reviewer will also review independently. Project: React/Redux + legacy Backbone.js. Uses JSDoc + React Compiler. Review Focus: 1. Correctness: logic, null/undefined, race conditions. 2. Performance: Redundant hooks (React Compiler handles these). 3. Clean Code: SRP, extract constants, prefer Native JS over Lodash. Rules: Be concise. Bullet points. Reference specific lines (+line 42). Tags: \ud83d\udd34 Critical | \ud83d\udfe1 Warning | \ud83d\udd35 Nit. Suggest concrete fix in code block. No memoization. FORMAT: Finding -> File: path | Line: +X -> Code block. If clean, say LGTM!. The full repository is mounted at /home/node/nimble-client. Diff paths are relative to the repo root. Read files only when the diff alone is insufficient to judge correctness (e.g. unclear import, missing context for a hook or utility). Do not read files speculatively.",
              "type": "string"
            },
            {
              "id": "set-diff-passthrough-001",
              "name": "diff",
              "value": "={{ $json.diff }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        384,
        -64
      ],
      "id": "a1b2c3d4-1111-2222-3333-444444444410",
      "name": "Review Prompt"
    },
    {
      "parameters": {
        "jsCode": "const { diff, systemPrompt } = $input.first().json;\nrequire('fs').writeFileSync('/tmp/review-prompt.txt', systemPrompt + '\\nDIFF TO REVIEW:\\n' + diff, 'utf8');\nreturn [{ json: {} }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        554,
        -64
      ],
      "id": "a1b2c3d4-1111-2222-3333-444444444407",
      "name": "Build Prompt"
    },
    {
      "parameters": {
        "command": "claude -p --output-format text < /tmp/review-prompt.txt"
      },
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        724,
        -200
      ],
      "id": "4a528059-6bbe-4eaf-802b-2773fb14f005",
      "name": "Claude Review"
    },
    {
      "parameters": {
        "command": "codex exec --full-auto --ephemeral --skip-git-repo-check - < /tmp/review-prompt.txt"
      },
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        724,
        80
      ],
      "id": "a1b2c3d4-1111-2222-3333-444444444401",
      "name": "Codex Review"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "set-claude-review-001",
              "name": "review",
              "value": "={{ $json.stdout }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        964,
        -200
      ],
      "id": "a1b2c3d4-1111-2222-3333-444444444402",
      "name": "Normalize Claude"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "set-codex-review-001",
              "name": "review",
              "value": "={{ $json.stdout }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        964,
        80
      ],
      "id": "a1b2c3d4-1111-2222-3333-444444444403",
      "name": "Normalize Codex"
    },
    {
      "parameters": {
        "mode": "append",
        "options": {}
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        1204,
        -60
      ],
      "id": "a1b2c3d4-1111-2222-3333-444444444404",
      "name": "Wait for All"
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst claudeReview = items[0].json.review;\nconst gptReview = items[1].json.review;\n\nconst prompt = `You are synthesizing two independent code reviews of the same Git diff.\n\nINSTRUCTIONS:\n1. MERGE: Keep ALL findings. Where both reviewers flagged the same issue (same file/line or same problem), merge into one finding.\n2. ATTRIBUTE each finding with one of:\n   - \"Found by: both\" \u2014 both reviewers independently flagged it\n   - \"Found by: Claude\" (+ \"GPT-4o: supports inclusion\" if GPT-4o agrees, or \"GPT-4o: disagrees\" if not)\n   - \"Found by: GPT-4o\" (+ \"Claude: supports inclusion\" if Claude agrees, or \"Claude: disagrees\" if not)\n3. FORMAT:\n   - Start with: \"Overall risk: HIGH/MEDIUM/LOW \u2014 N high, N medium, N low findings\"\n   - Each finding: \ud83d\udd34/\ud83d\udfe1/\ud83d\udd35 Title \u2192 File: path | Line: +X \u2192 code block \u2192 Fix suggestion \u2192 Found by: ...\n   - If no issues at all, say LGTM!\n\nREVIEW 1 (Claude):\n${claudeReview}\n\nREVIEW 2 (GPT-4o):\n${gptReview}`;\n\nrequire('fs').writeFileSync('/tmp/synthesis-prompt.txt', prompt, 'utf8');\nreturn [{ json: {} }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1424,
        -60
      ],
      "id": "a1b2c3d4-1111-2222-3333-444444444405",
      "name": "Build Synthesis Prompt"
    },
    {
      "parameters": {
        "command": "claude -p --output-format text < /tmp/synthesis-prompt.txt"
      },
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        1644,
        -60
      ],
      "id": "a1b2c3d4-1111-2222-3333-444444444406",
      "name": "Claude Synthesis"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://gitlab.ninternal.com/api/v4/projects/17/merge_requests/{{ $node[\"On form submission\"].json.mr_iid }}/notes",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "PRIVATE-TOKEN",
              "value": "={{ $env.GITLAB_TOKEN }}"
            },
            {}
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "body",
              "value": "={{ $json.stdout }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1864,
        -60
      ],
      "id": "ab7b322d-c97c-400a-9637-9c7d28e5bf6e",
      "name": "HTTP Request1"
    }
  ],
  "connections": {
    "On form submission": {
      "main": [
        [
          {
            "node": "Update Repo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Repo": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Review Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Review Prompt": {
      "main": [
        [
          {
            "node": "Build Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Prompt": {
      "main": [
        [
          {
            "node": "Claude Review",
            "type": "main",
            "index": 0
          },
          {
            "node": "Codex Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Review": {
      "main": [
        [
          {
            "node": "Normalize Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Codex Review": {
      "main": [
        [
          {
            "node": "Normalize Codex",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Claude": {
      "main": [
        [
          {
            "node": "Wait for All",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Codex": {
      "main": [
        [
          {
            "node": "Wait for All",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Wait for All": {
      "main": [
        [
          {
            "node": "Build Synthesis Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Synthesis Prompt": {
      "main": [
        [
          {
            "node": "Claude Synthesis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Synthesis": {
      "main": [
        [
          {
            "node": "HTTP Request1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request1": {
      "main": [
        []
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "staticData": null,
  "meta": {
    "templateCredsSetupCompleted": true
  }
}