first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Convert changelog-draft.json to the release-pipeline-compatible changelog-release.json.
Reads the audit artifact produced by the changelog-draft skill and emits the
flat JSON structure consumed by the create_release workflow (Slack payload
builder + in-app changelog.json step).
Usage:
python3 convert_to_release_json.py --input <changelog-draft.json> --output <changelog-release.json>
The output schema:
{
"newFeatures": ["..."],
"improvements": ["..."],
"bugFixes": ["..."],
"images": ["..."],
"oz_updates": ["..."]
}
"""
import argparse
import json
import sys
# Map from changelog-draft.json category names to release JSON keys.
CATEGORY_MAP = {
"NEW-FEATURE": "newFeatures",
"IMPROVEMENT": "improvements",
"BUG-FIX": "bugFixes",
"OZ": "oz_updates",
"IMAGE": "images",
}
def github_profile_link(username: str) -> str:
"""Format a GitHub username as a markdown profile link."""
return f"[@{username}](https://github.com/{username})"
def format_entry(entry: dict) -> str:
"""Format a single changelog entry as a text line with a PR link.
Includes external contributor attribution when applicable.
"""
text = entry["text"]
pr_number = entry.get("pr_number") or entry.get("number")
url = entry.get("url") or entry.get("pr_url")
link = ""
if url and pr_number:
link = f" ([#{pr_number}]({url}))"
attribution = ""
if entry.get("is_external") and entry.get("author"):
attribution = f"{github_profile_link(entry['author'])}"
return f"{text}{link}{attribution}"
def convert(draft: dict) -> dict:
"""Convert a changelog-draft.json dict to changelog-release.json dict."""
release: dict[str, list[str]] = {
"newFeatures": [],
"improvements": [],
"bugFixes": [],
"images": [],
"oz_updates": [],
}
for entry in draft.get("entries", []):
category = entry.get("category", "")
release_key = CATEGORY_MAP.get(category)
if release_key is None:
continue
if category == "IMAGE":
# IMAGE entries store a URL in "text" — pass through directly.
release["images"].append(entry["text"])
else:
release[release_key].append(format_entry(entry))
return release
def main() -> None:
parser = argparse.ArgumentParser(
description="Convert changelog-draft.json to changelog-release.json"
)
parser.add_argument(
"--input",
required=True,
help="Path to changelog-draft.json",
)
parser.add_argument(
"--output",
required=True,
help="Path to write changelog-release.json",
)
args = parser.parse_args()
with open(args.input) as f:
draft = json.load(f)
release = convert(draft)
with open(args.output, "w") as f:
json.dump(release, f, indent=2)
f.write("\n")
# Summary to stdout for CI logs
for key, items in release.items():
print(f" {key}: {len(items)} entries")
if __name__ == "__main__":
main()