v2.10.0 (#697)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Corentin André <corentin.andre@mistral.ai> Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com> Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai> Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com> Co-authored-by: Peter Evers <pevers90@gmail.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Quentin <quentin.torroba@mistral.ai> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: MichisGitIsKing <MichisGitIsKing@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
626f905186
commit
228f3c65a9
158 changed files with 7235 additions and 916 deletions
|
|
@ -18,3 +18,20 @@ uv run scripts/bump_version.py micro
|
|||
# or
|
||||
uv run scripts/bump_version.py patch
|
||||
```
|
||||
|
||||
## Releasing
|
||||
|
||||
`prepare_release.py` builds the release branch from the previous public release tag, cherry-picks commits from the matching `-private` tags, and (by default) squashes them into a single release commit.
|
||||
|
||||
As part of release branch creation, the script **freezes the full transitive dependency graph** into both `[project].dependencies` and `[dependency-groups].build` of `pyproject.toml` using the current `uv.lock`:
|
||||
|
||||
```bash
|
||||
uv export --no-hashes --no-dev --no-emit-project --frozen --format requirements.txt
|
||||
uv export --only-group build --no-emit-project --no-hashes --frozen --format requirements.txt
|
||||
```
|
||||
|
||||
The pinned `[project].dependencies` is what `uv build` reads in `.github/workflows/release.yml`, so the wheel published to PyPI carries `Requires-Dist:` entries pinned to exact versions (with environment markers preserved). End users installing `mistral-vibe` from PyPI get the same dependency set the team tested against.
|
||||
|
||||
The pinned `[dependency-groups].build` is what `uv sync --no-dev --group build` reads in `.github/workflows/build-and-upload.yml`, so the PyInstaller binaries on each release tag are built against the exact same PyInstaller / truststore versions every time.
|
||||
|
||||
`main` keeps `>=` ranges, so day-to-day upgrades on `main` (`uv lock --upgrade-package …`, Renovate PRs, etc.) are unaffected. Each new release re-snapshots `uv.lock` — there is no hand-maintained pin list.
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ def get_current_version() -> str:
|
|||
return version_match.group(1)
|
||||
|
||||
|
||||
def update_changelog(current_version: str, new_version: str) -> None:
|
||||
def scaffold_changelog(new_version: str) -> None:
|
||||
changelog_path = Path("CHANGELOG.md")
|
||||
|
||||
if not changelog_path.exists():
|
||||
|
|
@ -104,52 +104,53 @@ def update_changelog(current_version: str, new_version: str) -> None:
|
|||
updated_content = content[:insert_position] + new_entry + content[insert_position:]
|
||||
changelog_path.write_text(updated_content)
|
||||
|
||||
# Auto-fill changelog using Vibe in headless mode
|
||||
print("Filling CHANGELOG.md...")
|
||||
prompt = f"""Fill the new CHANGELOG.md section for version {new_version} (the one that was just added).
|
||||
|
||||
Rules:
|
||||
- Use only commits in origin/main that touch the `vibe` folder in this repo since version {current_version}. Inspect git history to list relevant changes.
|
||||
- Follow the existing file convention: Keep a Changelog format with ### Added, ### Changed, ### Fixed, ### Removed. One bullet per line, concise. Match the tone and style of the entries already in the file.
|
||||
- Do not mention commit hashes or PR numbers.
|
||||
- Remove any subsection that has no bullets (leave no empty ### Added / ### Changed / etc)."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["vibe", "-p", prompt], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError("Failed to auto-fill CHANGELOG.md")
|
||||
except Exception:
|
||||
print(
|
||||
"Warning: failed to auto-fill CHANGELOG.md, please fill it manually.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def fill_whats_new_message(new_version: str) -> None:
|
||||
def scaffold_whats_new() -> None:
|
||||
whats_new_path = Path("vibe/whats_new.md")
|
||||
if not whats_new_path.exists():
|
||||
raise FileNotFoundError("whats_new.md not found in current directory")
|
||||
|
||||
whats_new_path.write_text("")
|
||||
|
||||
print("Filling whats_new.md...")
|
||||
prompt = f"""Fill vibe/whats_new.md using only the CHANGELOG.md section for version {new_version}.
|
||||
|
||||
Rules:
|
||||
- Include only the most important user-facing changes: visible CLI/UI behavior, new commands or key bindings, UX improvements. Exclude internal refactors, API-only changes, and dev/tooling updates.
|
||||
- If there are no such changes, write nothing (empty file).
|
||||
- Otherwise: first line must be "# What's new in v{new_version}" (no extra heading). Then one bullet per item, format: "- **Feature**: short summary" (e.g. - **Interactive resume**: Added a /resume command to choose which session to resume). One line per bullet, concise.
|
||||
- Do not copy the full changelog; summarize only what matters to someone reading "what's new" in the app."""
|
||||
def fill_release_notes(current_version: str, new_version: str, autofill: bool) -> None:
|
||||
if not autofill:
|
||||
print("Skipping CHANGELOG.md and whats_new.md auto-fill.")
|
||||
return
|
||||
|
||||
print("Filling CHANGELOG.md and vibe/whats_new.md...")
|
||||
prompt = f"""Fill both CHANGELOG.md and vibe/whats_new.md for version {new_version} in a single pass, reusing the same git history context for both files.
|
||||
|
||||
Step 1 — Gather context (do this once):
|
||||
- Inspect git history for commits in origin/main that touch the `vibe` folder since version {current_version}.
|
||||
- Build a single mental list of relevant changes. Do not mention commit hashes or PR numbers.
|
||||
|
||||
Step 2 — Fill CHANGELOG.md:
|
||||
- Edit the section for version {new_version} that was just scaffolded at the top of the file.
|
||||
- Follow the existing file convention: Keep a Changelog format with ### Added, ### Changed, ### Fixed, ### Removed. One bullet per line, concise. Match the tone and style of existing entries.
|
||||
- Remove any subsection that has no bullets (leave no empty ### Added / ### Changed / etc).
|
||||
|
||||
Step 3 — Fill vibe/whats_new.md (reuse the same context, do NOT re-inspect git):
|
||||
- This file is an in-app announcement shown to users on upgrade. It is NOT a changelog. Its only purpose is to advertise a handful of notable new things. Most releases warrant 0-3 bullets. If nothing is genuinely noteworthy to an end user, leave the file empty.
|
||||
- Inclusion criteria (a bullet must meet ALL):
|
||||
* Visible in the CLI/UI: a new command, key binding, screen, flag, or behavior the user will actually notice.
|
||||
* Net-new capability or a meaningful UX improvement (not a tweak, polish, or fix unless it unblocks a real workflow).
|
||||
* Worth interrupting the user to tell them about.
|
||||
- Hard exclusions (never include, even if user-facing): bug fixes, small UI polish, copy changes, refactors, performance tweaks, dependency bumps, internal/API-only changes, config plumbing, telemetry, logging, build/CI, tests, docs.
|
||||
- Be ruthless. When in doubt, leave it out. Prefer an empty file over a weak bullet. Do NOT pad the list to look substantial.
|
||||
- Format (only if there is at least one qualifying item):
|
||||
* First line: "# What's new in v{new_version}" (no other headings).
|
||||
* Then up to 3 bullets, one line each: "- **Feature**: short summary" (e.g. "- **Interactive resume**: Added a /resume command to choose which session to resume").
|
||||
* Do not copy or paraphrase the full changelog."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["vibe", "-p", prompt], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError("Failed to auto-fill whats_new.md")
|
||||
raise RuntimeError("Failed to auto-fill release notes")
|
||||
except Exception:
|
||||
print(
|
||||
"Warning: failed to auto-fill whats_new.md, please fill it manually.",
|
||||
"Warning: failed to auto-fill CHANGELOG.md and whats_new.md, please fill them manually.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
|
@ -172,8 +173,14 @@ Examples:
|
|||
parser.add_argument(
|
||||
"bump_type", choices=BUMP_TYPES, help="Type of version bump to perform"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-autofill",
|
||||
action="store_true",
|
||||
help="Skip auto-filling CHANGELOG.md and whats_new.md via vibe -p",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
autofill = not args.no_autofill
|
||||
|
||||
try:
|
||||
# Get current version
|
||||
|
|
@ -213,9 +220,11 @@ Examples:
|
|||
)
|
||||
|
||||
print()
|
||||
update_changelog(current_version=current_version, new_version=new_version)
|
||||
|
||||
fill_whats_new_message(new_version=new_version)
|
||||
scaffold_changelog(new_version=new_version)
|
||||
scaffold_whats_new()
|
||||
fill_release_notes(
|
||||
current_version=current_version, new_version=new_version, autofill=autofill
|
||||
)
|
||||
print()
|
||||
|
||||
subprocess.run(["uv", "lock"], check=True)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S uv run python
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -7,6 +7,10 @@ from pathlib import Path
|
|||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any, cast
|
||||
|
||||
import tomlkit
|
||||
from tomlkit.items import Array
|
||||
|
||||
|
||||
def run_git_command(
|
||||
|
|
@ -217,6 +221,79 @@ def squash_commits(
|
|||
print("Successfully created release commit with co-authors")
|
||||
|
||||
|
||||
def get_pinned_dependencies(group: str | None = None) -> list[str]:
|
||||
cmd = [
|
||||
"uv",
|
||||
"export",
|
||||
"--no-hashes",
|
||||
"--no-emit-project",
|
||||
"--frozen",
|
||||
"--format",
|
||||
"requirements.txt",
|
||||
"--no-annotate",
|
||||
"--no-header",
|
||||
]
|
||||
if group is None:
|
||||
cmd.append("--no-dev")
|
||||
else:
|
||||
cmd += ["--only-group", group]
|
||||
|
||||
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||
|
||||
pins: list[str] = []
|
||||
for raw_line in result.stdout.splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
pins.append(line)
|
||||
|
||||
if not pins:
|
||||
target = group or "[project].dependencies"
|
||||
raise ValueError(f"uv export returned no dependencies for {target}")
|
||||
|
||||
return pins
|
||||
|
||||
|
||||
def make_multiline_array(pins: list[str]) -> Array:
|
||||
arr = tomlkit.array()
|
||||
arr.extend(pins)
|
||||
arr.multiline(True)
|
||||
return arr
|
||||
|
||||
|
||||
def pin_dependencies(version: str) -> None:
|
||||
print("Pinning dependencies for release...")
|
||||
|
||||
pyproject_path = Path("pyproject.toml")
|
||||
if not pyproject_path.exists():
|
||||
raise FileNotFoundError("pyproject.toml not found in current directory")
|
||||
|
||||
project_pins = get_pinned_dependencies()
|
||||
build_pins = get_pinned_dependencies(group="build")
|
||||
|
||||
doc = tomlkit.parse(pyproject_path.read_text())
|
||||
cast(dict[str, Any], doc["project"])["dependencies"] = make_multiline_array(
|
||||
project_pins
|
||||
)
|
||||
cast(dict[str, Any], doc["dependency-groups"])["build"] = make_multiline_array(
|
||||
build_pins
|
||||
)
|
||||
pyproject_path.write_text(tomlkit.dumps(doc))
|
||||
print(
|
||||
f"Pinned {len(project_pins)} project deps and "
|
||||
f"{len(build_pins)} build-group deps in pyproject.toml"
|
||||
)
|
||||
|
||||
print("Refreshing uv.lock...")
|
||||
subprocess.run(["uv", "lock"], check=True)
|
||||
|
||||
run_git_command("add", "pyproject.toml", "uv.lock")
|
||||
run_git_command(
|
||||
"commit", "--allow-empty", "-m", f"chore: pin dependencies for v{version}"
|
||||
)
|
||||
print(f"Committed pinned dependencies for v{version}")
|
||||
|
||||
|
||||
def get_commits_summary(previous_version: str, current_version: str) -> str:
|
||||
previous_tag = f"v{previous_version}-private"
|
||||
current_tag = f"v{current_version}-private"
|
||||
|
|
@ -352,7 +429,13 @@ def main() -> None:
|
|||
# Step 7: Cherry-pick commits
|
||||
cherry_pick_commits(previous_private_tag, current_private_tag)
|
||||
|
||||
# Step 8: Squash commits
|
||||
# Step 8: Pin dependencies from uv.lock so the published wheel
|
||||
# has frozen Requires-Dist metadata. When squashing, this commit
|
||||
# is folded into the release commit; otherwise it stays as the
|
||||
# final commit on the release branch.
|
||||
pin_dependencies(current_version)
|
||||
|
||||
# Step 9: Squash commits
|
||||
if squash:
|
||||
squash_commits(
|
||||
previous_version,
|
||||
|
|
@ -361,7 +444,7 @@ def main() -> None:
|
|||
current_private_tag,
|
||||
)
|
||||
|
||||
# Step 8: Get summary information
|
||||
# Step 10: Get summary information
|
||||
commits_summary = get_commits_summary(previous_version, current_version)
|
||||
changelog_entry = get_changelog_entry(current_version)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue