{
  "name": "Weekly Dev Report",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        4752,
        -496
      ],
      "id": "c5dfb9b4-9f03-49ac-8b7b-23470e1c5463",
      "name": "Weekly Trigger (Mon 9AM)"
    },
    {
      "parameters": {
        "jsCode": "const now = new Date();\n\n// Last week Monday (7 days ago)\nconst weekStart = new Date(now);\nweekStart.setDate(now.getDate() - 7);\nweekStart.setHours(0, 0, 0, 0);\n\n// Last week Sunday (yesterday)\nconst weekEnd = new Date(now);\nweekEnd.setDate(now.getDate() - 1);\nweekEnd.setHours(23, 59, 59, 999);\n\n// ISO format for API queries\nconst formatDate = (d) => d.toISOString().split('T')[0];\nconst formatDateKR = (d) => `${d.getMonth()+1}/${d.getDate()}`;\n\nreturn {\n  trigger_time: now.toISOString(),\n  week_start: weekStart.toISOString(),\n  week_end: weekEnd.toISOString(),\n  week_start_date: formatDate(weekStart),\n  week_end_date: formatDate(weekEnd),\n  date_range: `${formatDateKR(weekStart)} ~ ${formatDateKR(weekEnd)}`,\n  jql_date_range: `created >= \"${formatDate(weekStart)}\" AND created <= \"${formatDate(weekEnd)}\"`,\n  github_since: weekStart.toISOString(),\n  github_until: weekEnd.toISOString()\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4944,
        -496
      ],
      "id": "eb2d8af3-d99d-4a79-a94b-f8b7180e3c53",
      "name": "Calculate Week Range"
    },
    {
      "parameters": {
        "operation": "getAll",
        "limit": 100,
        "options": {
          "jql": "={{ `updated >= '${$json.week_start_date}' OR created >= '${$json.week_start_date}' ORDER BY updated DESC` }}"
        }
      },
      "type": "n8n-nodes-base.jira",
      "typeVersion": 1,
      "position": [
        5200,
        -496
      ],
      "id": "109a26a7-023d-49ef-b73b-fc1c5d3d7dee",
      "name": "Jira: All Issues",
      "credentials": {
        "jiraSoftwareCloudApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "={{ `https://api.github.com/search/issues?q=repo:ryu-qqq/setof-commerce+repo:ryu-qqq/Gateway+repo:ryu-qqq/CrawlingHub+repo:ryu-qqq/FileFlow+repo:ryu-qqq/AuthHub+repo:ryu-qqq/MarketPlace+repo:ryu-qqq/claude-spring-standards+is:pr+is:merged+merged:${$('Calculate Week Range').first().json.week_start_date}..${$('Calculate Week Range').first().json.week_end_date}&per_page=100` }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        5456,
        -496
      ],
      "id": "79b50fc1-7fd4-427b-8a12-192f2f8c72be",
      "name": "GitHub: All PRs",
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "=https://sentry.io/api/0/organizations/ryu-qqq/issues/?query=is:unresolved&statsPeriod=7d&limit=50",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "sentryIoApi",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        5712,
        -496
      ],
      "id": "fcb28660-c455-40ea-b3a9-ab3d8127ff81",
      "name": "Sentry: New Issues",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        },
        "sentryIoApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "=https://sentry.io/api/0/organizations/ryu-qqq/issues/?query=is:resolved&statsPeriod=7d&limit=50",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "sentryIoApi",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        5968,
        -496
      ],
      "id": "4131e09e-1ee7-4a8f-9e4e-83a317d1e3bf",
      "name": "Sentry: Resolved Issues",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        },
        "sentryIoApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// \uac01 \ub178\ub4dc\uc5d0\uc11c \uc9c1\uc811 \ub370\uc774\ud130 \uac00\uc838\uc624\uae30\nconst weekRange = $('Calculate Week Range').first().json;\nconst allJiraIssues = $('Jira: All Issues').all();\n\n// GitHub PRs - \ud1b5\ud569 \ub178\ub4dc\uc5d0\uc11c \uac00\uc838\uc624\uae30\nconst githubResult = $('GitHub: All PRs').first().json;\nconst allPRs = githubResult?.items || [];\n\nconst sentryNewItems = $('Sentry: New Issues').all();\nconst sentryResolvedItems = $('Sentry: Resolved Issues').all();\n\n// Date range\nconst dateRange = weekRange?.date_range || 'N/A';\nconst weekStart = weekRange?.week_start_date || 'N/A';\nconst weekEnd = weekRange?.week_end_date || 'N/A';\nconst weekStartDate = new Date(weekRange?.week_start);\nconst weekEndDate = new Date(weekRange?.week_end);\n\n// Jira \uc774\uc288\ub97c status\ubcc4\ub85c \ubd84\ub958\nconst completedStatuses = ['Done', '\uc644\ub8cc', 'Closed', 'Resolved'];\nconst inProgressStatuses = ['In Progress', '\uc9c4\ud589 \uc911', 'In Review', '\uac80\ud1a0 \uc911'];\n\nconst jiraCompleted = allJiraIssues.filter(i => {\n  const status = i.json?.fields?.status?.name || '';\n  return completedStatuses.includes(status);\n});\n\nconst jiraInProgress = allJiraIssues.filter(i => {\n  const status = i.json?.fields?.status?.name || '';\n  return inProgressStatuses.includes(status);\n});\n\n// \ud574\ub2f9 \uc8fc\uac04\uc5d0 \uc0dd\uc131\ub41c \uc774\uc288\nconst jiraCreated = allJiraIssues.filter(i => {\n  const created = new Date(i.json?.fields?.created);\n  return created >= weekStartDate && created <= weekEndDate;\n});\n\n// Calculate story points (\uc644\ub8cc\ub41c \uc774\uc288\ub9cc)\nconst storyPoints = jiraCompleted.reduce((sum, issue) => {\n  const sp = issue.json?.fields?.customfield_10016 || 0;\n  return sum + sp;\n}, 0);\n\n// Count by type (\uc644\ub8cc\ub41c \uc774\uc288 \uae30\uc900)\nconst completedBugs = jiraCompleted.filter(i => i.json?.fields?.issuetype?.name === 'Bug' || i.json?.fields?.issuetype?.name === '\ubc84\uadf8').length;\nconst completedFeatures = jiraCompleted.filter(i => ['Story', 'Feature', '\uc2a4\ud1a0\ub9ac'].includes(i.json?.fields?.issuetype?.name)).length;\nconst completedTasks = jiraCompleted.filter(i => i.json?.fields?.issuetype?.name === 'Task' || i.json?.fields?.issuetype?.name === '\uc791\uc5c5').length;\n\n// GitHub PRs (Search API\ub85c \uc774\ubbf8 \uba38\uc9c0\ub41c PR\ub9cc \uac00\uc838\uc634)\nconst mergedPRs = allPRs.length;\n\n// Sentry data - handle array response\nconst sentryNewList = Array.isArray(sentryNewItems[0]?.json) ? sentryNewItems[0].json : sentryNewItems.map(i => i.json);\nconst sentryResolvedList = Array.isArray(sentryResolvedItems[0]?.json) ? sentryResolvedItems[0].json : sentryResolvedItems.map(i => i.json);\nconst sentryNew = sentryNewList.length;\nconst sentryResolved = sentryResolvedList.length;\n\n// Build metrics stub (would read from stored file in production)\nconst buildMetrics = {\n  total: 45,\n  success: 42,\n  avg_duration: '4m 32s',\n  deployments: 8\n};\n\n// Sentry event total (placeholder - would come from Sentry stats API)\nconst totalEvents = 1247;\nconst lastWeekEvents = 1467;\nconst trendPercent = Math.round(((totalEvents - lastWeekEvents) / lastWeekEvents) * 100);\n\nreturn {\n  date_range: dateRange,\n  week_start: weekStart,\n  week_end: weekEnd,\n  \n  build_metrics: buildMetrics,\n  \n  jira_metrics: {\n    completed: jiraCompleted.length,\n    created: jiraCreated.length,\n    in_progress: jiraInProgress.length,\n    story_points: storyPoints,\n    by_type: {\n      bugs: completedBugs,\n      features: completedFeatures,\n      tasks: completedTasks\n    }\n  },\n  \n  github_metrics: {\n    merged_prs: mergedPRs\n  },\n  \n  sentry_metrics: {\n    new_issues: sentryNew,\n    resolved: sentryResolved,\n    total_events: totalEvents,\n    trend_percent: trendPercent\n  },\n  \n  raw_summary: `Jira: ${jiraCompleted.length} completed, ${jiraCreated.length} created. GitHub: ${mergedPRs} PRs merged. Sentry: ${sentryNew} new issues, ${sentryResolved} resolved.`\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6224,
        -496
      ],
      "id": "97bfb072-02dc-4ed9-8d6e-20a727a900f7",
      "name": "Aggregate Metrics"
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-5.2-pro",
          "mode": "list",
          "cachedResultName": "GPT-5.2-PRO"
        },
        "messages": {
          "values": [
            {
              "content": "\ub2f9\uc2e0\uc740 \uac1c\ubc1c\ud300\uc758 \uc8fc\uac04 \ub9ac\ud3ec\ud2b8\ub97c \uc694\uc57d\ud558\ub294 AI \uc5b4\uc2dc\uc2a4\ud134\ud2b8\uc785\ub2c8\ub2e4.\n\n\uc8fc\uc5b4\uc9c4 \uba54\ud2b8\ub9ad\uc744 \ubd84\uc11d\ud558\uace0 2-3\ubb38\uc7a5\uc73c\ub85c \ud575\uc2ec \uc778\uc0ac\uc774\ud2b8\ub97c \ud55c\uad6d\uc5b4\ub85c \uc81c\uacf5\ud558\uc138\uc694.\n\n\ud3ec\ud568\ud560 \ub0b4\uc6a9:\n- \uc774\ubc88 \uc8fc \uc8fc\uc694 \uc131\uacfc\n- \uc8fc\ubaa9\ud560 \ub9cc\ud55c \ud2b8\ub80c\ub4dc (\uae0d\uc815\uc801 \ub610\ub294 \ubd80\uc815\uc801)\n- \ub2e4\uc74c \uc8fc \uad8c\uc7a5 \uc561\uc158 (\uc788\ub2e4\uba74)\n\n\uac04\uacb0\ud558\uace0 \uc561\uc158 \uac00\ub2a5\ud55c \uc778\uc0ac\uc774\ud2b8\ub97c \uc81c\uacf5\ud558\uc138\uc694.",
              "role": "system"
            },
            {
              "content": "=\uc774\ubc88 \uc8fc \uac1c\ubc1c \uba54\ud2b8\ub9ad:\n\n\ube4c\ub4dc: \ucd1d {{ $json.build_metrics.total }}\ud68c, \uc131\uacf5\ub960 {{ Math.round($json.build_metrics.success / $json.build_metrics.total * 100) }}%\n\ubc30\ud3ec: {{ $json.build_metrics.deployments }}\ud68c\n\nJira:\n- \uc644\ub8cc: {{ $json.jira_metrics.completed }}\uac1c (\ubc84\uadf8 {{ $json.jira_metrics.by_type.bugs }}, \uae30\ub2a5 {{ $json.jira_metrics.by_type.features }}, \ud0dc\uc2a4\ud06c {{ $json.jira_metrics.by_type.tasks }})\n- \uc2e0\uaddc: {{ $json.jira_metrics.created }}\uac1c\n- \uc2a4\ud1a0\ub9ac\ud3ec\uc778\ud2b8: {{ $json.jira_metrics.story_points }} SP\n\nGitHub: {{ $json.github_metrics.merged_prs }}\uac1c PR \uba38\uc9c0\ub428\n\nSentry:\n- \uc0c8 \uc774\uc288: {{ $json.sentry_metrics.new_issues }}\uac1c\n- \ud574\uacb0: {{ $json.sentry_metrics.resolved }}\uac1c\n- \uc5d0\ub7ec \uc774\ubca4\ud2b8: {{ $json.sentry_metrics.total_events }}\ud68c ({{ $json.sentry_metrics.trend_percent }}% \uc804\uc8fc \ub300\ube44)\n\n\uc704 \ub370\uc774\ud130\ub97c \uae30\ubc18\uc73c\ub85c \uc8fc\uac04 \uc694\uc57d\uc744 \uc791\uc131\ud574\uc8fc\uc138\uc694."
            }
          ]
        },
        "options": {
          "temperature": 0.3
        }
      },
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        6480,
        -496
      ],
      "id": "e99bb4c3-269e-4cc9-9a9a-c9a7e2696f63",
      "name": "AI Summarize",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst metricsInput = $('Aggregate Metrics').first().json;\n\n// Extract AI summary\nconst aiSummary = input.message?.content || input.text || '\uc694\uc57d \ub370\uc774\ud130 \uc5c6\uc74c';\n\nconst buildSuccessRate = metricsInput.build_metrics.total > 0\n  ? Math.round((metricsInput.build_metrics.success / metricsInput.build_metrics.total) * 100)\n  : 0;\n\nconst errorTrend = metricsInput.sentry_metrics.trend_percent > 0\n  ? `+${metricsInput.sentry_metrics.trend_percent}%`\n  : `${metricsInput.sentry_metrics.trend_percent}%`;\nconst trendEmoji = metricsInput.sentry_metrics.trend_percent > 0\n  ? ':chart_with_upwards_trend:'\n  : ':chart_with_downwards_trend:';\n\nconst blocks = [\n  {\n    \"type\": \"header\",\n    \"text\": {\n      \"type\": \"plain_text\",\n      \"text\": `:bar_chart: \uc8fc\uac04 \uac1c\ubc1c \ub9ac\ud3ec\ud2b8 (${metricsInput.date_range})`,\n      \"emoji\": true\n    }\n  },\n  {\n    \"type\": \"divider\"\n  },\n  {\n    \"type\": \"section\",\n    \"text\": {\n      \"type\": \"mrkdwn\",\n      \"text\": \"*:hammer_and_wrench: \ube4c\ub4dc & \ubc30\ud3ec*\"\n    }\n  },\n  {\n    \"type\": \"section\",\n    \"fields\": [\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\ucd1d \ube4c\ub4dc*\\n${metricsInput.build_metrics.total}\ud68c`\n      },\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\uc131\uacf5\ub960*\\n${buildSuccessRate}%`\n      },\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\ud3c9\uade0 \uc2dc\uac04*\\n${metricsInput.build_metrics.avg_duration}`\n      },\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\ubc30\ud3ec*\\n${metricsInput.build_metrics.deployments}\ud68c`\n      }\n    ]\n  },\n  {\n    \"type\": \"divider\"\n  },\n  {\n    \"type\": \"section\",\n    \"text\": {\n      \"type\": \"mrkdwn\",\n      \"text\": \"*:ticket: Jira \ud2f0\ucf13*\"\n    }\n  },\n  {\n    \"type\": \"section\",\n    \"fields\": [\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\uc644\ub8cc*\\n${metricsInput.jira_metrics.completed}\uac1c`\n      },\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\uc2e0\uaddc*\\n${metricsInput.jira_metrics.created}\uac1c`\n      },\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\uc9c4\ud589\uc911*\\n${metricsInput.jira_metrics.in_progress}\uac1c`\n      },\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\uc2a4\ud1a0\ub9ac \ud3ec\uc778\ud2b8*\\n${metricsInput.jira_metrics.story_points} SP`\n      }\n    ]\n  },\n  {\n    \"type\": \"divider\"\n  },\n  {\n    \"type\": \"section\",\n    \"text\": {\n      \"type\": \"mrkdwn\",\n      \"text\": \"*:bug: Sentry \uc5d0\ub7ec*\"\n    }\n  },\n  {\n    \"type\": \"section\",\n    \"fields\": [\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\uc0c8 \uc774\uc288*\\n${metricsInput.sentry_metrics.new_issues}\uac1c`\n      },\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\ud574\uacb0*\\n${metricsInput.sentry_metrics.resolved}\uac1c`\n      },\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\ucd1d \uc774\ubca4\ud2b8*\\n${metricsInput.sentry_metrics.total_events.toLocaleString()}\ud68c`\n      },\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": `*\ucd94\uc138* ${trendEmoji}\\n${errorTrend} (\uc804\uc8fc \ub300\ube44)`\n      }\n    ]\n  },\n  {\n    \"type\": \"divider\"\n  },\n  {\n    \"type\": \"section\",\n    \"text\": {\n      \"type\": \"mrkdwn\",\n      \"text\": `*:robot_face: AI \uc694\uc57d*\\n${aiSummary}`\n    }\n  },\n  {\n    \"type\": \"divider\"\n  },\n  {\n    \"type\": \"context\",\n    \"elements\": [\n      {\n        \"type\": \"mrkdwn\",\n        \"text\": \":link: <https://your-domain.atlassian.net/jira/|Jira> | <https://sentry.io/organizations/ryu-qqq/|Sentry> | <https://github.com/ryu-qqq|GitHub>\"\n      }\n    ]\n  }\n];\n\nreturn {\n  blocks: blocks,\n  channel: '#dev-reports',\n  text: `\uc8fc\uac04 \uac1c\ubc1c \ub9ac\ud3ec\ud2b8 (${metricsInput.date_range})`,\n  metrics: metricsInput,\n  ai_summary: aiSummary\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6736,
        -496
      ],
      "id": "b8a6e8f6-d1ff-4033-b55f-05cad2c5494c",
      "name": "Format Slack Message"
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "C0A8M6YU477",
          "mode": "id"
        },
        "messageType": "block",
        "blocksUi": "={{ $json.blocks }}",
        "text": "={{ $json.text }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        6992,
        -496
      ],
      "id": "748608ad-96d1-4371-943e-e0d8ba7ca21e",
      "name": "Send Weekly Report",
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Weekly Trigger (Mon 9AM)": {
      "main": [
        [
          {
            "node": "Calculate Week Range",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Week Range": {
      "main": [
        [
          {
            "node": "Jira: All Issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Jira: All Issues": {
      "main": [
        [
          {
            "node": "GitHub: All PRs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub: All PRs": {
      "main": [
        [
          {
            "node": "Sentry: New Issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sentry: New Issues": {
      "main": [
        [
          {
            "node": "Sentry: Resolved Issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sentry: Resolved Issues": {
      "main": [
        [
          {
            "node": "Aggregate Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Metrics": {
      "main": [
        [
          {
            "node": "AI Summarize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Summarize": {
      "main": [
        [
          {
            "node": "Format Slack Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Slack Message": {
      "main": [
        [
          {
            "node": "Send Weekly Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false
  },
  "versionId": "5cac9360-6ebe-458e-a4c5-ee392b83e3ab",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "b1kHYS3PRNc2qoVO",
  "tags": []
}