Skip to content

Commit 8e9218c

Browse files
committed
Merge branch 'main' of https://github.com/RooVetGit/Roo-Cline into feature/pupeteer-updates
2 parents ba3b027 + 33eb0a6 commit 8e9218c

27 files changed

+1650
-119
lines changed

.changeset/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
2+
"$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json",
33
"changelog": "@changesets/cli/changelog",
44
"commit": false,
55
"fixed": [],

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: AI Release Notes
2+
description: Generate AI release notes using git and openai, outputs 'RELEASE_NOTES' and 'OPENAI_PROMPT'
3+
4+
inputs:
5+
OPENAI_API_KEY:
6+
required: true
7+
type: string
8+
GHA_PAT:
9+
required: true
10+
type: string
11+
model_name:
12+
required: false
13+
type: string
14+
default: gpt-4o-mini
15+
repo_path:
16+
required: false
17+
type: string
18+
custom_prompt:
19+
required: false
20+
default: ''
21+
type: string
22+
git_ref:
23+
required: false
24+
type: string
25+
default: ''
26+
head_ref:
27+
required: false
28+
type: string
29+
default: main
30+
base_ref:
31+
required: false
32+
type: string
33+
default: main
34+
35+
outputs:
36+
RELEASE_NOTES:
37+
description: "AI generated release notes"
38+
value: ${{ steps.ai_release_notes.outputs.RELEASE_NOTES }}
39+
OPENAI_PROMPT:
40+
description: "Prompt used to generate release notes"
41+
value: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }}
42+
43+
env:
44+
GITHUB_REF: ${{ inputs.git_ref == '' && github.event.pull_request.head.ref || inputs.git_ref }}
45+
BASE_REF: ${{ inputs.base_ref == '' && github.base_ref || inputs.base_ref }}
46+
HEAD_REF: ${{ inputs.head_ref == '' && github.event.pull_request.head.sha || inputs.head_ref }}
47+
48+
runs:
49+
using: "composite"
50+
steps:
51+
- uses: actions/checkout@v4
52+
with:
53+
repository: ${{ inputs.repo_path }}
54+
token: ${{ inputs.GHA_PAT }}
55+
ref: ${{ env.GITHUB_REF }}
56+
fetch-depth: 0
57+
58+
- name: Set Workspace
59+
shell: bash
60+
run: |
61+
pip install tiktoken
62+
pip install pytz
63+
64+
# Github outputs: 'OPENAI_PROMPT'
65+
- name: Add Git Info to base prompt
66+
id: ai_prompt
67+
shell: bash
68+
env:
69+
BASE_REF: ${{ env.BASE_REF }}
70+
HEAD_SHA: ${{ env.HEAD_SHA }}
71+
PR_TITLE: ${{ github.event.pull_request.title }}
72+
PR_BODY: ${{ github.event.pull_request.body }}
73+
MODEL_NAME: ${{ inputs.model_name }}
74+
CUSTOM_PROMPT: ${{ inputs.custom_prompt }} # Default: ''
75+
run: python .github/scripts/release-notes-prompt.py
76+
77+
# Github outputs: 'RELEASE_NOTES'
78+
- name: Generate AI release notes
79+
id: ai_release_notes
80+
shell: bash
81+
env:
82+
OPENAI_API_KEY: ${{ inputs.OPENAI_API_KEY }}
83+
CUSTOM_PROMPT: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }}
84+
MODEL_NAME: ${{ inputs.model_name }}
85+
run: python .github/scripts/ai-release-notes.py
86+

.github/dependabot.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# To get started with Dependabot version updates, you'll need to specify which
2+
# package ecosystems to update and where the package manifests are located.
3+
# Please see the documentation for all configuration options:
4+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5+
6+
version: 2
7+
updates:
8+
- package-ecosystem: "npm" # See documentation for possible values
9+
directory: "/" # Location of package manifests
10+
schedule:
11+
interval: "weekly"

.github/pull_request_template.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!-- **Note:** Consider creating PRs as a DRAFT. For early feedback and self-review. -->
2+
## Description
3+
4+
## Type of change
5+
<!-- Please ignore options that are not relevant -->
6+
- [ ] Bug fix (non-breaking change which fixes an issue)
7+
- [ ] New feature
8+
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
9+
- [ ] This change requires a documentation update
10+
11+
## How Has This Been Tested?
12+
<!-- Please describe the tests that you ran to verify your changes -->
13+
14+
## Checklist:
15+
<!-- Go over all the following points, and put an `x` in all the boxes that apply -->
16+
- [ ] My code follows the patterns of this project
17+
- [ ] I have performed a self-review of my own code
18+
- [ ] I have commented my code, particularly in hard-to-understand areas
19+
- [ ] I have made corresponding changes to the documentation
20+
21+
## Additional context
22+
<!-- Add any other context or screenshots about the pull request here -->
23+
24+
## Related Issues
25+
<!-- List any related issues here. Use the GitHub issue linking syntax: #issue-number -->
26+
27+
## Reviewers
28+
<!-- @mention specific team members or individuals who should review this PR -->
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""
2+
AI-powered release notes generator that creates concise and informative release notes from git changes.
3+
4+
This script uses OpenAI's API to analyze git changes (summary, diff, and commit log) and generate
5+
well-formatted release notes in markdown. It focuses on important changes and their impact,
6+
particularly highlighting new types and schemas while avoiding repetitive information.
7+
8+
Environment Variables Required:
9+
OPENAI_API_KEY: OpenAI API key for authentication
10+
CHANGE_SUMMARY: Summary of changes made (optional if CUSTOM_PROMPT provided)
11+
CHANGE_DIFF: Git diff of changes (optional if CUSTOM_PROMPT provided)
12+
CHANGE_LOG: Git commit log (optional if CUSTOM_PROMPT provided)
13+
GITHUB_OUTPUT: Path to GitHub output file
14+
CUSTOM_PROMPT: Custom prompt to override default (optional)
15+
"""
16+
17+
import os
18+
import requests # type: ignore
19+
import json
20+
import tiktoken # type: ignore
21+
22+
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
23+
CHANGE_SUMMARY = os.environ.get('CHANGE_SUMMARY', '')
24+
CHANGE_DIFF = os.environ.get('CHANGE_DIFF', '')
25+
CHANGE_LOG = os.environ.get('CHANGE_LOG', '')
26+
GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT")
27+
OPEN_AI_BASE_URL = "https://api.openai.com/v1"
28+
OPEN_API_HEADERS = {"Authorization": f"Bearer {OPENAI_API_KEY}", "Content-Type": "application/json"}
29+
CUSTOM_PROMPT = os.environ.get('CUSTOM_PROMPT', '')
30+
MODEL_NAME = os.environ.get('MODEL_NAME', 'gpt-3.5-turbo-16k')
31+
32+
def num_tokens_from_string(string: str, model_name: str) -> int:
33+
"""
34+
Calculate the number of tokens in a text string for a specific model.
35+
36+
Args:
37+
string: The input text to count tokens for
38+
model_name: Name of the OpenAI model to use for token counting
39+
40+
Returns:
41+
int: Number of tokens in the input string
42+
"""
43+
encoding = tiktoken.encoding_for_model(model_name)
44+
num_tokens = len(encoding.encode(string))
45+
return num_tokens
46+
47+
def truncate_to_token_limit(text, max_tokens, model_name):
48+
"""
49+
Truncate text to fit within a maximum token limit for a specific model.
50+
51+
Args:
52+
text: The input text to truncate
53+
max_tokens: Maximum number of tokens allowed
54+
model_name: Name of the OpenAI model to use for tokenization
55+
56+
Returns:
57+
str: Truncated text that fits within the token limit
58+
"""
59+
encoding = tiktoken.encoding_for_model(model_name)
60+
encoded = encoding.encode(text)
61+
truncated = encoded[:max_tokens]
62+
return encoding.decode(truncated)
63+
64+
def generate_release_notes(model_name):
65+
"""
66+
Generate release notes using OpenAI's API based on git changes.
67+
68+
Uses the GPT-3.5-turbo model to analyze change summary, commit log, and code diff
69+
to generate concise and informative release notes in markdown format. The notes
70+
focus on important changes and their impact, with sections for new types/schemas
71+
and other updates.
72+
73+
Returns:
74+
str: Generated release notes in markdown format
75+
76+
Raises:
77+
requests.exceptions.RequestException: If the OpenAI API request fails
78+
"""
79+
max_tokens = 14000 # Reserve some tokens for the response
80+
81+
# Truncate inputs if necessary to fit within token limits
82+
change_summary = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_SUMMARY, 1000, model_name)
83+
change_log = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_LOG, 2000, model_name)
84+
change_diff = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_DIFF, max_tokens - num_tokens_from_string(change_summary, model_name) - num_tokens_from_string(change_log, model_name) - 1000, model_name)
85+
86+
url = f"{OPEN_AI_BASE_URL}/chat/completions"
87+
88+
# Construct prompt for OpenAI API
89+
openai_prompt = CUSTOM_PROMPT if CUSTOM_PROMPT else f"""Based on the following summary of changes, commit log and code diff, please generate concise and informative release notes:
90+
Summary of changes:
91+
{change_summary}
92+
Commit log:
93+
{change_log}
94+
Code Diff:
95+
{json.dumps(change_diff)}
96+
"""
97+
98+
data = {
99+
"model": model_name,
100+
"messages": [{"role": "user", "content": openai_prompt}],
101+
"temperature": 0.7,
102+
"max_tokens": 1000,
103+
}
104+
105+
print("----------------------------------------------------------------------------------------------------------")
106+
print("POST request to OpenAI")
107+
print("----------------------------------------------------------------------------------------------------------")
108+
ai_response = requests.post(url, headers=OPEN_API_HEADERS, json=data)
109+
print(f"Status Code: {str(ai_response.status_code)}")
110+
print(f"Response: {ai_response.text}")
111+
ai_response.raise_for_status()
112+
113+
return ai_response.json()["choices"][0]["message"]["content"]
114+
115+
release_notes = generate_release_notes(MODEL_NAME)
116+
print("----------------------------------------------------------------------------------------------------------")
117+
print("OpenAI generated release notes")
118+
print("----------------------------------------------------------------------------------------------------------")
119+
print(release_notes)
120+
121+
# Write the release notes to GITHUB_OUTPUT
122+
with open(GITHUB_OUTPUT, "a") as outputs_file:
123+
outputs_file.write(f"RELEASE_NOTES<<EOF\n{release_notes}\nEOF")
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import os
2+
import re
3+
import subprocess
4+
5+
def run_git_command(command):
6+
result = subprocess.getoutput(command)
7+
print(f"Git Command: {command}")
8+
print(f"Git Output: {result}")
9+
return result
10+
11+
def parse_merge_commit(line):
12+
# Parse merge commit messages like:
13+
# "355dc82 Merge pull request #71 from RooVetGit/better-error-handling"
14+
pattern = r"([a-f0-9]+)\s+Merge pull request #(\d+) from (.+)"
15+
match = re.match(pattern, line)
16+
if match:
17+
sha, pr_number, branch = match.groups()
18+
return {
19+
'sha': sha,
20+
'pr_number': pr_number,
21+
'branch': branch
22+
}
23+
return None
24+
25+
def get_version_refs():
26+
# Get the merge commits with full message
27+
command = 'git log --merges --pretty=oneline -n 3'
28+
result = run_git_command(command)
29+
30+
if result:
31+
commits = result.split('\n')
32+
if len(commits) >= 3:
33+
# Parse HEAD~1 (PR to generate notes for)
34+
head_info = parse_merge_commit(commits[1])
35+
# Parse HEAD~2 (previous PR to compare against)
36+
base_info = parse_merge_commit(commits[2])
37+
38+
if head_info and base_info:
39+
# Set output for GitHub Actions
40+
with open(os.environ['GITHUB_OUTPUT'], 'a') as gha_outputs:
41+
gha_outputs.write(f"head_ref={head_info['sha']}\n")
42+
gha_outputs.write(f"base_ref={base_info['sha']}")
43+
44+
print(f"Head ref (PR #{head_info['pr_number']}): {head_info['sha']}")
45+
print(f"Base ref (PR #{base_info['pr_number']}): {base_info['sha']}")
46+
return head_info, base_info
47+
48+
print("Could not find or parse sufficient merge history")
49+
return None, None
50+
51+
if __name__ == "__main__":
52+
head_info, base_info = get_version_refs()
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""
2+
This script updates a specific version's release notes section in CHANGELOG.md with new content.
3+
4+
The script:
5+
1. Takes a version number, changelog path, and new content as input from environment variables
6+
2. Finds the section in the changelog for the specified version
7+
3. Replaces the content between the current version header and the next version header
8+
(or end of file if it's the latest version) with the new content
9+
4. Writes the updated changelog back to the file
10+
11+
Environment Variables:
12+
CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md')
13+
VERSION: The version number to update notes for
14+
PREV_VERSION: The previous version number (optional)
15+
NEW_CONTENT: The new content to insert for this version
16+
"""
17+
18+
#!/usr/bin/env python3
19+
20+
import os
21+
22+
CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md")
23+
VERSION = os.environ['VERSION']
24+
PREV_VERSION = os.environ.get("PREV_VERSION", "")
25+
NEW_CONTENT = os.environ['NEW_CONTENT']
26+
27+
def overwrite_changelog_section(content: str):
28+
"""Replace a specific version section in the changelog content.
29+
30+
Args:
31+
content: The full changelog content as a string
32+
33+
Returns:
34+
The updated changelog content with the new section
35+
36+
Example:
37+
>>> content = "## 1.2.0\\nOld changes\\n## 1.1.0\\nOld changes"
38+
>>> NEW_CONTENT = "New changes"
39+
>>> overwrite_changelog_section(content)
40+
'## 1.2.0\\nNew changes\\n## 1.1.0\\nOld changes'
41+
"""
42+
# Find the section for the specified version
43+
version_pattern = f"## {VERSION}\n"
44+
print(f"latest version: {VERSION}")
45+
notes_start_index = content.find(version_pattern) + len(version_pattern)
46+
print(f"prev_version: {PREV_VERSION}")
47+
prev_version_pattern = f"## {PREV_VERSION}\n"
48+
notes_end_index = content.find(prev_version_pattern, notes_start_index) if PREV_VERSION and prev_version_pattern in content else len(content)
49+
return content[:notes_start_index] + f"{NEW_CONTENT}\n" + content[notes_end_index:]
50+
51+
with open(CHANGELOG_PATH, 'r') as f:
52+
content = f.read()
53+
54+
new_changelog = overwrite_changelog_section(content)
55+
56+
print(new_changelog)
57+
58+
# Write back to CHANGELOG.md
59+
with open(CHANGELOG_PATH, 'w') as f:
60+
f.write(new_changelog)

0 commit comments

Comments
 (0)