import os, requests, time, base64
DOMAIN, EMAIL = os.environ["JIRA_DOMAIN"], os.environ["JIRA_EMAIL"]
TOKEN, MV = os.environ["JIRA_API_TOKEN"], os.environ["MAVERA_API_KEY"]
AB, MB = f"https://{DOMAIN}.atlassian.net/rest/agile/1.0", "https://app.mavera.io/api/v1"
cred = base64.b64encode(f"{EMAIL}:{TOKEN}".encode()).decode()
JH = {"Authorization": f"Basic {cred}", "Content-Type": "application/json"}
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
BOARD_ID = 42
r = requests.get(f"{AB}/board/{BOARD_ID}/sprint", headers=JH, params={"state": "closed", "maxResults": 5})
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", 30)))
r = requests.get(f"{AB}/board/{BOARD_ID}/sprint", headers=JH, params={"state": "closed", "maxResults": 5})
r.raise_for_status()
sprints = r.json().get("values", [])
if not sprints:
raise SystemExit("No completed sprints found")
sprint = sprints[-1]
print(f"Sprint: {sprint['name']}")
issues, start = [], 0
while True:
r = requests.get(f"{AB}/sprint/{sprint['id']}/issue", headers=JH,
params={"startAt": start, "maxResults": 100, "fields": "summary,issuetype,priority,resolution"})
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", 30)))
continue
r.raise_for_status()
data = r.json()
issues.extend(data.get("issues", []))
if start + data["maxResults"] >= data["total"]:
break
start += data["maxResults"]
time.sleep(0.5)
resolved = [i for i in issues if i["fields"].get("resolution")]
cats = {"features": [], "bugs": [], "improvements": []}
for iss in resolved:
name = iss["fields"]["issuetype"]["name"].lower()
entry = f"- [{iss['key']}] {iss['fields']['summary']} ({iss['fields']['priority']['name']})"
if "story" in name or "feature" in name: cats["features"].append(entry)
elif "bug" in name: cats["bugs"].append(entry)
else: cats["improvements"].append(entry)
cat_block = "\n".join(f"{k.upper()} ({len(v)}):\n" + "\n".join(v) for k, v in cats.items() if v)
time.sleep(0.3)
notes = requests.post(f"{MB}/generations", headers=MH, json={
"prompt": f"Release notes for '{sprint['name']}'.\n\n{cat_block[:5000]}\n\n"
"Group by New Features/Bug Fixes/Improvements. User benefit first. Include Jira keys.",
}).json()
release_notes = notes.get("output", notes.get("content", ""))
time.sleep(0.3)
changelog = requests.post(f"{MB}/generations", headers=MH, json={
"prompt": f"CHANGELOG.md for '{sprint['name']}'.\n\n{release_notes[:3000]}\n\n"
"Keep-a-Changelog format (Added/Changed/Fixed). One line per item.",
}).json()
print(f"\nRELEASE NOTES:\n{release_notes[:2000]}")
print(f"\nCHANGELOG:\n{changelog.get('output', changelog.get('content', ''))[:1500]}")