Skip to content

Commit 2579869

Browse files
committed
Merge remote-tracking branch 'upstream/main' into plyght/mic-selector
2 parents edfc189 + 0604441 commit 2579869

File tree

145 files changed

+5847
-1099
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

145 files changed

+5847
-1099
lines changed

.cargo/config.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,4 @@
22
rustflags = [
33
"-C",
44
"target-feature=-crt-static",
5-
"-C",
6-
"link-arg=/FORCE:MULTIPLE",
75
]
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
name: AI Changelog Generator
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch:
7+
inputs:
8+
ai_model:
9+
description: "OpenRouter model to use"
10+
required: false
11+
default: "anthropic/claude-sonnet-4"
12+
test_mode:
13+
description: "Test mode (use last 10 commits instead of since last release)"
14+
required: false
15+
default: "false"
16+
type: boolean
17+
18+
permissions:
19+
contents: write
20+
21+
jobs:
22+
generate-changelog:
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 30
25+
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@v4
29+
with:
30+
fetch-depth: 0
31+
32+
- name: Setup Node.js
33+
uses: actions/setup-node@v4
34+
with:
35+
node-version: "20"
36+
cache: "npm"
37+
38+
- name: Install dependencies
39+
run: npm install axios @octokit/rest
40+
41+
- name: Generate changelog
42+
id: changelog
43+
env:
44+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45+
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
46+
AI_MODEL: ${{ (github.event.inputs && github.event.inputs.ai_model) || 'anthropic/claude-sonnet-4' }}
47+
TEST_MODE: ${{ (github.event.inputs && github.event.inputs.test_mode) || 'false' }}
48+
run: |
49+
cat > changelog-generator.js << 'EOF'
50+
const { Octokit } = require('@octokit/rest');
51+
const axios = require('axios');
52+
const fs = require('fs');
53+
54+
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
55+
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
56+
const testMode = process.env.TEST_MODE === 'true';
57+
58+
async function getCommits() {
59+
try {
60+
if (testMode) {
61+
console.log('TEST MODE: Getting last 10 commits');
62+
const { data } = await octokit.rest.repos.listCommits({
63+
owner, repo, per_page: 10
64+
});
65+
return data;
66+
}
67+
68+
// Get releases and compare
69+
const { data: releases } = await octokit.rest.repos.listReleases({
70+
owner, repo, per_page: 2
71+
});
72+
73+
if (releases.length < 2) {
74+
console.log('Not enough releases, getting last 50 commits');
75+
const { data } = await octokit.rest.repos.listCommits({
76+
owner, repo, per_page: 50
77+
});
78+
return data;
79+
}
80+
81+
const [current, previous] = releases;
82+
console.log(`Getting commits between ${previous.tag_name} and ${current.tag_name}`);
83+
84+
const { data: comparison } = await octokit.rest.repos.compareCommits({
85+
owner, repo, base: previous.tag_name, head: current.tag_name
86+
});
87+
88+
return comparison.commits;
89+
} catch (error) {
90+
console.error('Error getting commits:', error.message);
91+
const { data } = await octokit.rest.repos.listCommits({
92+
owner, repo, per_page: 50
93+
});
94+
return data;
95+
}
96+
}
97+
98+
async function getCommitDetails(sha) {
99+
try {
100+
const { data } = await octokit.rest.repos.getCommit({
101+
owner, repo, ref: sha
102+
});
103+
return data;
104+
} catch (error) {
105+
console.error(`Error getting commit details for ${sha}:`, error.message);
106+
return null;
107+
}
108+
}
109+
110+
async function generateChangelog(commits, commitDetails) {
111+
const prompt = `You are a technical writer creating a changelog for hyprnote - a desktop note-taking app with AI capabilities (Tauri + React).
112+
113+
Create a professional changelog with:
114+
- Categories: Breaking Changes, Features, Improvements, Bug Fixes, Internal, Dependencies
115+
- No emojis or casual language
116+
- Focus on functional impact and technical details
117+
- Reference specific components when applicable
118+
119+
COMMITS:
120+
${JSON.stringify(commits.map(c => ({
121+
sha: c.sha.substring(0, 8),
122+
message: c.commit.message,
123+
author: c.commit.author.name,
124+
date: c.commit.author.date
125+
})), null, 2)}
126+
127+
DETAILED CHANGES:
128+
${commitDetails.map(detail => {
129+
if (!detail) return 'Commit details unavailable';
130+
return `
131+
Commit: ${detail.sha.substring(0, 8)}
132+
Message: ${detail.commit.message}
133+
Files changed: ${detail.files ? detail.files.length : 0}
134+
${detail.files ? detail.files.map(f => `- ${f.filename} (+${f.additions} -${f.deletions})`).join('\n') : ''}
135+
`;
136+
}).join('\n---\n')}
137+
138+
Generate a markdown changelog suitable for release notes.`;
139+
140+
const response = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
141+
model: process.env.AI_MODEL,
142+
messages: [
143+
{
144+
role: 'system',
145+
content: 'You are a technical writer specializing in software documentation. Generate precise, professional changelog content.'
146+
},
147+
{ role: 'user', content: prompt }
148+
],
149+
max_tokens: 4000,
150+
temperature: 0.1
151+
}, {
152+
headers: {
153+
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
154+
'Content-Type': 'application/json',
155+
'HTTP-Referer': 'https://github.com/hyprnote/hyprnote',
156+
'X-Title': 'hyprnote AI Changelog Generator'
157+
}
158+
});
159+
160+
return response.data.choices[0].message.content;
161+
}
162+
163+
async function main() {
164+
try {
165+
console.log('Starting changelog generation...');
166+
167+
const commits = await getCommits();
168+
console.log(`Found ${commits.length} commits to analyze`);
169+
170+
if (commits.length === 0) {
171+
console.log('No commits found');
172+
return;
173+
}
174+
175+
console.log('Fetching detailed commit information...');
176+
const commitDetails = await Promise.all(
177+
commits.slice(0, 20).map(commit => getCommitDetails(commit.sha))
178+
);
179+
180+
console.log('Generating changelog with AI...');
181+
const commitsToProcess = commits.slice(0, 20);
182+
const changelog = await generateChangelog(commitsToProcess, commitDetails);
183+
184+
fs.writeFileSync('changelog.md', changelog);
185+
186+
const metadata = {
187+
generated_at: new Date().toISOString(),
188+
commit_count: commits.length,
189+
commits_processed: commitsToProcess.length,
190+
model_used: process.env.AI_MODEL,
191+
test_mode: testMode,
192+
repository: `${owner}/${repo}`,
193+
commits_analyzed: commitsToProcess.map(c => ({
194+
sha: c.sha.substring(0, 8),
195+
message: c.commit.message.split('\n')[0],
196+
author: c.commit.author.name,
197+
date: c.commit.author.date
198+
}))
199+
};
200+
201+
fs.writeFileSync('metadata.json', JSON.stringify(metadata, null, 2));
202+
203+
console.log('Changelog generated successfully');
204+
console.log('Preview:');
205+
console.log(changelog.substring(0, 500) + '...');
206+
207+
} catch (error) {
208+
console.error('Error in main:', error);
209+
process.exit(1);
210+
}
211+
}
212+
213+
main();
214+
EOF
215+
216+
node changelog-generator.js
217+
218+
- name: Generate changelog file
219+
run: |
220+
# Create a timestamped changelog file
221+
TIMESTAMP=$(date -u +"%Y%m%d_%H%M%S")
222+
FILENAME="CHANGELOG_${TIMESTAMP}.md"
223+
224+
# Add header with metadata
225+
cat > "$FILENAME" << EOF
226+
# Changelog Generated $(date -u)
227+
228+
**Generation Details:**
229+
- Generated: $(date -u)
230+
- Trigger: ${{ github.event_name == 'release' && 'Release Published' || 'Manual Trigger' }}
231+
- Model: ${{ (github.event.inputs && github.event.inputs.ai_model) || 'anthropic/claude-sonnet-4' }}
232+
- Test Mode: ${{ (github.event.inputs && github.event.inputs.test_mode) || 'false' }}
233+
- Repository: ${{ github.repository }}
234+
- Workflow Run: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
235+
236+
---
237+
238+
EOF
239+
240+
# Append the AI-generated changelog
241+
cat changelog.md >> "$FILENAME"
242+
243+
# Set output for commit step
244+
echo "changelog_file=$FILENAME" >> $GITHUB_OUTPUT
245+
246+
echo "Generated changelog file: $FILENAME"
247+
echo "Preview:"
248+
head -50 "$FILENAME"
249+
id: generate_file
250+
251+
- name: Commit changelog file
252+
run: |
253+
git config --local user.email "action@github.com"
254+
git config --local user.name "GitHub Action"
255+
256+
# Add the generated file
257+
git add "${{ steps.generate_file.outputs.changelog_file }}"
258+
259+
# Only commit if there are changes
260+
if git diff --staged --quiet; then
261+
echo "No changes to commit"
262+
else
263+
git commit -m "Add AI-generated changelog
264+
265+
Generated: $(date -u)
266+
Model: ${{ (github.event.inputs && github.event.inputs.ai_model) || 'anthropic/claude-sonnet-4' }}
267+
Test Mode: ${{ (github.event.inputs && github.event.inputs.test_mode) || 'false' }}
268+
269+
🤖 Generated with AI Changelog Workflow
270+
271+
Co-Authored-By: GitHub Action <action@github.com>"
272+
273+
git push
274+
275+
echo "Changelog committed as: ${{ steps.generate_file.outputs.changelog_file }}"
276+
fi
277+
278+
- name: Upload artifacts
279+
uses: actions/upload-artifact@v4
280+
with:
281+
name: changelog-artifacts
282+
path: |
283+
changelog.md
284+
metadata.json
285+
retention-days: 30

0 commit comments

Comments
 (0)