毎週金曜の夕方、Jira を開いて GitHub を開いてメモを見返して…それだけで30分以上かかる。しかも書き終えた後に「これ、本当に伝わっているのか?」という虚しさも残る。そんな時期が1年ほど続いていました。
週次進捗レポートは、エンジニアにとって地味に消耗する作業のひとつです。重要なのはわかっています。でも、情報を集めて整形する作業自体に創造性はほとんどありません。ここは Claude API に任せるべき仕事だと気づいたのは、ある金曜日の夜のことでした。
GitHub のコミット・PR データと Linear の完了課題を自動集計し、Claude API で読みやすいレポートに整形して Slack に投稿するシステムの実装を順番にご紹介します。週30分かかっていた作業が、完全に自動化されます。
システムの全体像と設計方針
まず全体の流れを整理しておきましょう。
GitHub API → 今週のコミット・PRを取得
Linear API → 今週完了した課題を取得
↓
Claude API → データを整形して読みやすいレポートを生成
↓
Slack Webhook → 指定チャンネルに自動投稿
このシステムを GitHub Actions のスケジュール実行(毎週金曜17時)として動かします。サーバーを立てる必要はなく、無料枠で十分動きます。
設計で大切にしたのは「Claude API に情報の整形判断を委ねる」という点です。単純なテンプレート埋め込みではなく、週ごとに実際の成果に合わせた文章を生成するため、毎回違う読み応えのあるレポートになります。
必要な環境の準備
Node.js(v18以上)と以下のAPIキーが必要です。
- Anthropic API キー: console.anthropic.com で取得
- GitHub Personal Access Token:
repoスコープのみ - Linear API キー: Linear の Settings → API から取得
- Slack Incoming Webhook URL: Slack App の設定から作成
依存パッケージをインストールします。
npm init -y
npm install @anthropic-ai/sdk node-fetch dotenv.env ファイルに各APIキーを設定します(GitHub Actionsでは Secrets として登録)。
ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEY
GITHUB_TOKEN=YOUR_GITHUB_TOKEN
LINEAR_API_KEY=YOUR_LINEAR_API_KEY
SLACK_WEBHOOK_URL=YOUR_SLACK_WEBHOOK_URL
GITHUB_OWNER=your-org-name
GITHUB_REPO=your-repo-nameGitHub からコミット・PR データを取得する
GitHub REST API を使って、過去7日間のデータを取得します。
// src/github.js
import { Octokit } from '@octokit/rest';
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
export async function getWeeklyGitHubData() {
const since = new Date();
since.setDate(since.getDate() - 7);
const sinceISO = since.toISOString();
const owner = process.env.GITHUB_OWNER;
const repo = process.env.GITHUB_REPO;
// マージ済みPRを取得
const { data: pulls } = await octokit.pulls.list({
owner,
repo,
state: 'closed',
sort: 'updated',
direction: 'desc',
per_page: 50,
});
const mergedPRs = pulls
.filter(pr => pr.merged_at && new Date(pr.merged_at) >= since)
.map(pr => ({
title: pr.title,
number: pr.number,
author: pr.user.login,
mergedAt: pr.merged_at,
labels: pr.labels.map(l => l.name),
}));
// コミット数をカウント(デフォルトブランチ)
const { data: commits } = await octokit.repos.listCommits({
owner,
repo,
since: sinceISO,
per_page: 100,
});
// ボットのコミットを除外
const humanCommits = commits.filter(c =>
c.author && !c.author.login.includes('[bot]')
);
return {
mergedPRs,
commitCount: humanCommits.length,
activeContributors: [...new Set(humanCommits.map(c => c.author?.login).filter(Boolean))],
};
}ボットのコミットを除外する処理は忘れがちですが、GitHub Actions や Dependabot のコミットが混入すると数字が水増しされます。実際に動かしてみると「今週50コミット!」がほぼ全部 bot だった、という経験をしたことがあります。
Linear から完了課題を取得する
Linear は GraphQL API を採用しています。REST に慣れていると少し戸惑いますが、一度書けばシンプルです。
// src/linear.js
const LINEAR_API_URL = 'https://api.linear.app/graphql';
export async function getWeeklyLinearData() {
const since = new Date();
since.setDate(since.getDate() - 7);
const sinceISO = since.toISOString();
const query = `
query WeeklyIssues($since: DateTime!) {
issues(
filter: {
completedAt: { gte: $since }
state: { type: { eq: "completed" } }
}
orderBy: updatedAt
first: 50
) {
nodes {
title
identifier
assignee { name }
labels { nodes { name } }
completedAt
estimate
}
}
}
`;
const response = await fetch(LINEAR_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: process.env.LINEAR_API_KEY,
},
body: JSON.stringify({ query, variables: { since: sinceISO } }),
});
const { data, errors } = await response.json();
if (errors) {
console.error('Linear API エラー:', errors);
throw new Error('Linear データ取得失敗');
}
return data.issues.nodes.map(issue => ({
title: issue.title,
identifier: issue.identifier,
assignee: issue.assignee?.name ?? '未割当',
labels: issue.labels.nodes.map(l => l.name),
estimate: issue.estimate ?? null,
}));
}estimate フィールドは Linear のポイント見積もりです。ストーリーポイントを集計することで「今週は計20pt分の作業を完了した」という表現が可能になります。
Claude API でレポートを生成する
ここがシステムの核心部分です。ただのテンプレート埋め込みではなく、Claude に「このデータを読んで、チームへの伝え方を考えて」と依頼します。
// src/report.js
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
export async function generateWeeklyReport(githubData, linearIssues) {
const today = new Date().toLocaleDateString('ja-JP', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
const prompt = `
今週(${today}時点)のエンジニアチームの活動データを元に、Slack 投稿用の週次進捗レポートを作成してください。
## GitHub データ
- マージ済み PR 数: ${githubData.mergedPRs.length} 件
- コミット数: ${githubData.commitCount} 件
- アクティブ開発者: ${githubData.activeContributors.join(', ')}
### マージされた PR 一覧
${githubData.mergedPRs.map(pr => `- #${pr.number}: ${pr.title} (by ${pr.author})`).join('\n')}
## Linear 完了課題
- 完了課題数: ${linearIssues.length} 件
- 合計見積もりポイント: ${linearIssues.reduce((sum, i) => sum + (i.estimate ?? 0), 0)} pt
### 完了した課題
${linearIssues.map(issue => `- [${issue.identifier}] ${issue.title} (担当: ${issue.assignee})`).join('\n')}
## 作成ルール
- Slack の mrkdwn 形式で書く(*太字* :絵文字: などを使う)
- 冒頭に今週のハイライトを2〜3文で書く(データから実際に成果を読み取って書くこと)
- PR とLinear課題の両方を簡潔にまとめる
- 最後に来週への一言を添える
- 全体で300〜400文字程度にまとめる
- 過度に形式的にならず、チームらしい温かみのある文体で
`;
const message = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
});
return message.content[0].text;
}プロンプトのポイントは「データから成果を読み取って書くこと」という指示です。これがないと、Claude は単にデータを列挙するだけのレポートを生成してしまいます。「今週は認証周りの改修が集中しました」のような、データを読んだ上での一言が入ることで、レポートが人間の言葉になります。
Slack へ投稿し、全体をまとめる
// src/slack.js
export async function postToSlack(text) {
const payload = {
text,
unfurl_links: false,
unfurl_media: false,
};
const response = await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Slack 投稿失敗: ${response.status}`);
}
}// src/index.js(エントリーポイント)
import 'dotenv/config';
import { getWeeklyGitHubData } from './github.js';
import { getWeeklyLinearData } from './linear.js';
import { generateWeeklyReport } from './report.js';
import { postToSlack } from './slack.js';
async function main() {
console.log('週次レポート生成開始...');
try {
const [githubData, linearIssues] = await Promise.all([
getWeeklyGitHubData(),
getWeeklyLinearData(),
]);
console.log(`GitHub: PR ${githubData.mergedPRs.length}件, コミット ${githubData.commitCount}件`);
console.log(`Linear: 完了課題 ${linearIssues.length}件`);
const report = await generateWeeklyReport(githubData, linearIssues);
await postToSlack(report);
console.log('✅ 週次レポート投稿完了');
} catch (error) {
console.error('❌ エラー:', error.message);
process.exit(1);
}
}
main();GitHub Actions でスケジュール実行する
.github/workflows/weekly-report.yml を作成します。
name: Weekly Progress Report
on:
schedule:
# 毎週金曜17時(JST = UTC+9 なので 08:00 UTC)
- cron: '0 8 * * 5'
workflow_dispatch: # 手動実行も可能にする
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Generate and post weekly report
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
GITHUB_OWNER: ${{ github.repository_owner }}
GITHUB_REPO: ${{ github.event.repository.name }}
run: node src/index.jsworkflow_dispatch を追加しておくと、GitHub の UI から手動で実行できます。スケジュールを待たずにテストできるので、開発中は重宝します。
実際に使ってみて気づいたこと
このシステムを3ヶ月ほど運用して気づいたことをいくつか。
Claude のレポートが「ハズレ」になることはほぼない: 同じデータでも毎回少し違う表現を使ってくれるため、「また同じコピペか」という印象を持たれません。ここは素朴なテンプレートとの大きな違いです。
Linearを使っていないチームへの対応: Linear の部分を Jira(REST API)や GitHub Issues に置き換えても基本構造は変わりません。getWeeklyLinearData の戻り値の形式さえ合わせれば、Claude API へのリクエストはそのまま使えます。
コスト感: claude-sonnet-4-6 を使って週1回実行した場合、月のAPIコストは数十円程度です。Extended Thinking は不要で、通常のモデルで十分な品質が得られます。
まずは workflow_dispatch で手動実行して、どんなレポートが生成されるか試してみてください。データが揃っていれば、最初の実行で十分使えるレポートが出てくるはずです。