-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.ts
153 lines (129 loc) · 4.63 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { Octokit } from "@octokit/rest"
import * as fs from "fs"
import Anthropic from "@anthropic-ai/sdk"
import { Level } from "level"
import { getRepos } from "./lib/getRepos"
import { generateMarkdown } from "./lib/generateMarkdown"
import { getMergedPRs, type PullRequest } from "./lib/getMergedPRs"
import filterDiff from "./lib/filterDiff"
export const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
})
// Initialize LevelDB
const db = new Level("./pr-analysis-cache", { valueEncoding: "json" })
export interface AnalyzedPR {
number: number
title: string
description: string
impact: "Major" | "Minor" | "Tiny"
contributor: string
repo: string
url: string
}
async function analyzePRWithClaude(
pr: PullRequest,
repo: string,
): Promise<AnalyzedPR> {
const cacheKey = `${repo}:${pr.number}`
try {
// Try to get the analysis from cache
const cachedAnalysis = JSON.parse(
await db.get(cacheKey, { valueEncoding: "json" }),
)
return cachedAnalysis
} catch (error) {
const reducedDiff = filterDiff(pr.diff)
// If not in cache, perform the analysis
const prompt = `Analyze the following pull request and provide a one-line description of the change. Also, classify the impact as "Major", "Minor", or "Tiny".
Major Impact: Introduces a huge feature, fixes a critical or difficult bug. Generally difficult to implement.
Minor Impact: Bug fixes, simple feature additions, small improvements. Typically more than 100 lines of code changes. Adding a new symbol.
Tiny Impact: Minor documentation changes, typo fixes, small cosmetic fixes, updates to dependencies.
Title: ${pr.title}
Body: ${pr.body}
Diff:
${reducedDiff.slice(0, 8000)}
Response format:
Description: [One-line description]
Impact: [Major/Minor/Tiny]`
const message = await anthropic.messages.create({
model: "claude-3-haiku-20240307",
max_tokens: 1000,
messages: [{ role: "user", content: prompt }],
})
const content = message.content[0].text
const description =
content.split("Description:")?.[1]?.split("Impact:")[0] ?? ""
const impact = content.split("Impact:")?.[1] ?? ""
const analysis: AnalyzedPR = {
number: pr.number,
title: pr.title,
description: description.replace("Description: ", "").trim(),
impact: impact?.replace("Impact: ", "")?.trim() as
| "Major"
| "Minor"
| "Tiny",
contributor: pr.user.login,
repo,
url: pr.html_url,
}
// Store the analysis in cache
await db.put(cacheKey, analysis, { valueEncoding: "json" })
return analysis
}
}
export async function generateOverview(startDate: string) {
const startDateString = startDate
const repos = await getRepos()
const allPRs: AnalyzedPR[] = []
for (const repo of repos) {
console.log(`Analyzing ${repo}`)
const prs = await getMergedPRs(repo, startDateString)
console.log(`Found ${prs.length} merged PRs`)
for (const pr of prs) {
if (pr.user.login.includes("renovate")) {
continue
}
const analysis = await analyzePRWithClaude(pr, repo)
allPRs.push(analysis)
}
}
// Group PRs by contributor
const contributorPRs = allPRs.reduce(
(acc, pr) => {
if (!acc[pr.contributor]) {
acc[pr.contributor] = []
}
acc[pr.contributor].push(pr)
return acc
},
{} as Record<string, AnalyzedPR[]>,
)
// Sort each contributor's PRs by impact
const impactOrder = { Major: 3, Minor: 2, Tiny: 1 }
for (const contributor in contributorPRs) {
contributorPRs[contributor].sort(
(a, b) => impactOrder[b.impact] - impactOrder[a.impact],
)
}
// Flatten the sorted PRs back into a single array
const sortedPRs = Object.values(contributorPRs).flat()
const markdown = await generateMarkdown(sortedPRs, startDateString)
fs.writeFileSync(`contribution-overviews/${startDateString}.md`, markdown)
console.log(`Generated contribution-overviews/${startDateString}.md`)
// Edit the README.md file
const readme = fs.readFileSync("README.md", "utf8")
const updatedReadme = readme.replace(
/<!-- START_CURRENT_WEEK -->[\s\S]*<!-- END_CURRENT_WEEK -->/m,
`<!-- START_CURRENT_WEEK -->\n\n${markdown}\n\n<!-- END_CURRENT_WEEK -->`,
)
fs.writeFileSync("README.md", updatedReadme)
// Close the database
await db.close()
}
export async function generateWeeklyOverview() {
const weekStart = new Date()
weekStart.setDate(weekStart.getDate() - weekStart.getDay() - 4) // Set to last Wednesday
const weekStartString = weekStart.toISOString().split("T")[0]
await generateOverview(weekStartString)
}