From c1487ceeba26fc67f34afc7a6bda2ff76d7823b9 Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Wed, 22 Jul 2026 17:57:33 +0200 Subject: [PATCH 1/5] Add nextchanges_sections to .codegen.json --- .codegen.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.codegen.json b/.codegen.json index ee2037e0ee8..0866ebe7ae9 100644 --- a/.codegen.json +++ b/.codegen.json @@ -8,6 +8,13 @@ "python/uv.lock": "name = \"databricks-bundles\"\nversion = \"$VERSION\"", "libs/template/templates/default/library/versions.tmpl": "{{define \"latest_databricks_bundles_version\" -}}$VERSION{{- end}}" }, + "nextchanges_sections": { + "notable-changes": "Notable Changes", + "cli": "CLI", + "bundles": "Bundles", + "dependency-updates": "Dependency Updates", + "api-changes": "API Changes" + }, "toolchain": { "required": [ "go" From b07f0b292f39e315b1bd5ab92a92a7f87ae68c5a Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Wed, 22 Jul 2026 18:03:21 +0200 Subject: [PATCH 2/5] Read SECTIONS from codegen.json inside tools/validate_nextchanges.py --- tools/validate_nextchanges.py | 38 +++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/tools/validate_nextchanges.py b/tools/validate_nextchanges.py index c89eb9b0083..6b55bebf245 100755 --- a/tools/validate_nextchanges.py +++ b/tools/validate_nextchanges.py @@ -12,15 +12,14 @@ """ import argparse +import json import pathlib import re import sys CHANGELOG_DIR = ".nextchanges" - -# Known section subdirectories. Mirrors NEXTCHANGES_SECTIONS in -# internal/genkit/tagging.py — keep the two in sync. -SECTIONS = ("notable-changes", "cli", "bundles", "dependency-updates", "api-changes") +CODEGEN_FILE = ".codegen.json" +NEXTCHANGES_SECTIONS_KEY = "nextchanges_sections" # .nextchanges/version holds the next release version; the release reads it and # bumps it. Accept a bare semver (optionally v-prefixed), e.g. 1.4.0 / v1.4.0. @@ -31,11 +30,28 @@ SCAFFOLDING = ("README.md", ".gitkeep") -def find_problems(changelog_dir): +def load_sections(root): + codegen_path = root / CODEGEN_FILE + try: + codegen = json.loads(codegen_path.read_text(encoding="utf-8")) + except FileNotFoundError as err: + raise ValueError(f"{CODEGEN_FILE} is missing") from err + except json.JSONDecodeError as err: + raise ValueError(f"{CODEGEN_FILE} is not valid JSON: {err}") from err + + sections = codegen.get(NEXTCHANGES_SECTIONS_KEY) + if not isinstance(sections, dict) or not sections: + raise ValueError(f"{CODEGEN_FILE} must define a non-empty {NEXTCHANGES_SECTIONS_KEY} object") + + return tuple(sections) + + +def find_problems(changelog_dir, sections): """Return a list of ``(path, message)`` for anything unexpected under ``.nextchanges/``: files that aren't a section fragment or known scaffolding, empty fragments, and a missing/malformed version file.""" problems = [] + known_sections = set(sections) for path in sorted(changelog_dir.rglob("*")): if path.is_dir(): continue @@ -49,7 +65,7 @@ def find_problems(changelog_dir): continue # Section-level: .nextchanges/
/. - if len(rel.parts) == 2 and rel.parts[0] in SECTIONS: + if len(rel.parts) == 2 and rel.parts[0] in known_sections: if name in SCAFFOLDING: continue if not name.endswith(".md"): @@ -78,12 +94,18 @@ def main(argv=None): if not changelog_dir.is_dir(): return - problems = find_problems(changelog_dir) + try: + sections = load_sections(args.root) + except ValueError as err: + print(err, file=sys.stderr) + sys.exit(1) + + problems = find_problems(changelog_dir, sections) if problems: for path, msg in problems: print(f"{path}: {msg}", file=sys.stderr) print(f"\nFragments must live at {CHANGELOG_DIR}/
/.md", file=sys.stderr) - print(f"Valid sections: {', '.join(SECTIONS)}", file=sys.stderr) + print(f"Valid sections: {', '.join(sections)}", file=sys.stderr) print(f"{CHANGELOG_DIR}/{VERSION_FILE} must hold the next release version.", file=sys.stderr) sys.exit(1) From a205cbbcdc02c6047bace72afd706d970ea69be2 Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Wed, 22 Jul 2026 18:03:42 +0200 Subject: [PATCH 3/5] Add comment that README.md is rejected by validate_nextchanges.py unlike in the upstream flow --- tools/validate_nextchanges.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/validate_nextchanges.py b/tools/validate_nextchanges.py index 6b55bebf245..84918041ed0 100755 --- a/tools/validate_nextchanges.py +++ b/tools/validate_nextchanges.py @@ -27,6 +27,7 @@ SEMVER_RE = re.compile(r"^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$") # Non-fragment files allowed to sit alongside fragments at any depth. +# Note that tagging workflow accepts README.md inside fragments, but we reject it here for clarity. SCAFFOLDING = ("README.md", ".gitkeep") From 31295d3b5fb8f05333a6a16deb199899eb49b4cc Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Wed, 22 Jul 2026 18:20:11 +0200 Subject: [PATCH 4/5] Update what files are allowed where --- tools/validate_nextchanges.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tools/validate_nextchanges.py b/tools/validate_nextchanges.py index 84918041ed0..67f008d714c 100755 --- a/tools/validate_nextchanges.py +++ b/tools/validate_nextchanges.py @@ -26,9 +26,8 @@ VERSION_FILE = "version" SEMVER_RE = re.compile(r"^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$") -# Non-fragment files allowed to sit alongside fragments at any depth. -# Note that tagging workflow accepts README.md inside fragments, but we reject it here for clarity. -SCAFFOLDING = ("README.md", ".gitkeep") +ROOT_README = "README.md" +SECTION_SCAFFOLDING = (".gitkeep",) def load_sections(root): @@ -59,17 +58,19 @@ def find_problems(changelog_dir, sections): rel = path.relative_to(changelog_dir) name = path.name - # Root-level: only the version file and scaffolding belong here. + # Root-level: only the version file and root documentation belong here. This prevents + # someone accidentally putting a .md into .nextchanges thinking it would be picked up. if len(rel.parts) == 1: - if name != VERSION_FILE and name not in SCAFFOLDING: + if name != VERSION_FILE and name != ROOT_README: problems.append((path, "unexpected file at .nextchanges root")) continue # Section-level: .nextchanges/
/. if len(rel.parts) == 2 and rel.parts[0] in known_sections: - if name in SCAFFOLDING: - continue if not name.endswith(".md"): + # Only allow scaffolding files to exist + if name in SECTION_SCAFFOLDING: + continue problems.append((path, "unexpected file (fragments must be *.md)")) elif not path.read_text(encoding="utf-8").strip(): problems.append((path, "empty fragment")) From f798342f76d1587a08dcf7156c7a295bc0851fb3 Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Wed, 22 Jul 2026 18:22:51 +0200 Subject: [PATCH 5/5] Remove README exception in release_tagging.py --- internal/genkit/release_tagging.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/genkit/release_tagging.py b/internal/genkit/release_tagging.py index 8a34b595a27..75180a7d031 100644 --- a/internal/genkit/release_tagging.py +++ b/internal/genkit/release_tagging.py @@ -75,7 +75,7 @@ def render_nextchanges(package_path: str) -> Optional[str]: continue entries = [] for name in sorted(os.listdir(section_dir)): - if not name.endswith(".md") or name == "README.md": + if not name.endswith(".md"): continue with open(os.path.join(section_dir, name)) as f: text = f.read().strip() @@ -132,8 +132,8 @@ def clear_nextchanges(package_path: str) -> None: Replacement for ``tagging.clean_next_changelog``: stage deletion of the ``.nextchanges/`` fragments consumed by this release and bump ``.nextchanges/version`` to the next minor (its post-release default; teams - can still override it in a PR). Section directories and their README.md are - left in place. ``process_package`` calls this as + can still override it in a PR). Section directories are left in place. + ``process_package`` calls this as ``clean_next_changelog(package.path)``, so the signature matches. """ base = os.path.join(os.getcwd(), package_path, NEXTCHANGES_DIR) @@ -142,7 +142,7 @@ def clear_nextchanges(package_path: str) -> None: if not os.path.isdir(section_dir): continue for name in sorted(os.listdir(section_dir)): - if name.endswith(".md") and name != "README.md": + if name.endswith(".md"): tagging.gh.delete_file(os.path.join(section_dir, name)) version_path = _version_path(package_path)