Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,16 @@ Must be one of the following:

* **feat**: new features or capabilities that enhance the user's experience.
* **fix**: bug fixes that enhance the user's experience.
* **security**: CVE fixes, vulnerability mitigations, VEX updates.
* **refactor**: a code changes that neither fixes a bug nor adds a feature.
* **docs**: updates or improvements to documentation.
* **test**: additions or corrections to tests.
* **chore**: updates that don't fit into other types.
* **chore**: other changes that still affect users in some way and don't fit into other types (e.g. dependency or base image updates).
* **internal**: developer-only changes that users never notice: CI, templates, tooling. Excluded from the changelog and release notes.

Rule of thumb: if a user of the module can notice the change in any way — it is **chore**
(or a more specific type); if the change is visible only to developers of this
repository — it is **internal**.

#### Scope

Expand Down
15 changes: 13 additions & 2 deletions cliff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ trim = false
# Emit the module changelog yaml: all four categories are always present,
# empty ones are rendered as [].
body = """
{%- set breaking = commits | filter(attribute="group", value="breaking") -%}
{%- set feats = commits | filter(attribute="group", value="features") -%}
{%- set fixes = commits | filter(attribute="group", value="fixes") -%}
{%- set security = commits | filter(attribute="group", value="security") -%}
{%- set chores = commits | filter(attribute="group", value="chore") -%}
breaking:
{%- if breaking | length == 0 %} []
{%- else %}
{%- for c in breaking %}
- "{{ c.message | split(pat="\n") | first | trim | replace(from="\\", to="\\\\") | replace(from='"', to='\\"') }}"
{%- endfor %}
{%- endif %}
features:
{%- if feats | length == 0 %} []
{%- else %}
Expand Down Expand Up @@ -59,9 +67,12 @@ protect_breaking_commits = true
tag_pattern = "^v[0-9]+\\.[0-9]+\\.[0-9]+$"

commit_parsers = [
# noise: merge commits and generated-changelog commits
# noise: merge commits and internal (developer-only) changes: ci, templates, tooling
{ message = "^Merge", skip = true },
{ message = "^internal\\(changelog\\)", skip = true },
{ message = "^internal", skip = true },
# breaking changes: "type!: ..." or BREAKING CHANGE footer
{ message = "^[a-z]+(\\([^)]*\\))?!:", group = "breaking" },
{ body = "(?s).*BREAKING CHANGE.*", group = "breaking" },
# security first: explicit type or CVE/vex mentions
{ message = "^security", group = "security" },
{ message = "(?i)\\bcve-\\d{4}-\\d+", group = "security" },
Expand Down
58 changes: 56 additions & 2 deletions hack/chlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"fixes": {"en": "Bug Fixes", "ru": "Исправления"},
"security": {"en": "Security Fixes", "ru": "Исправления безопасности"},
"chore": {"en": "Chore", "ru": "Прочее"},
"breaking": {"en": "Breaking Changes", "ru": "Несовместимые изменения"},
}


Expand Down Expand Up @@ -279,6 +280,7 @@ def release_notes_handler(changelog_dir: str, lang: str, output: str = "") -> in

headers = {
"title": TRANSLATIONS["title"][lang],
"breaking": TRANSLATIONS["breaking"][lang],
"features": TRANSLATIONS["features"][lang],
"fixes": TRANSLATIONS["fixes"][lang],
"security": TRANSLATIONS["security"][lang],
Expand All @@ -292,6 +294,7 @@ def release_notes_handler(changelog_dir: str, lang: str, output: str = "") -> in
"version": os.path.basename(filename).replace(
".ru.yaml" if lang == "ru" else ".yaml", ""
),
"breaking": "",
"features": "",
"fixes": "",
"security": "",
Expand All @@ -301,10 +304,15 @@ def release_notes_handler(changelog_dir: str, lang: str, output: str = "") -> in
for category, changes in changelog.items():
logging.debug("processing category: %s", category)
if changes:
if lang == "en" and isinstance(changes, list):
changes = [
to_past_tense(c) if isinstance(c, str) else c
for c in changes
]
entries[category] = yaml_to_markdown(changes)

markdown += f"\n## {entries['version']}\n"
for category in ["features", "fixes", "security", "chore"]:
for category in ["breaking", "features", "fixes", "security", "chore"]:
if entries[category]:
markdown += f"\n### {headers[category]}\n{entries[category]}"

Expand All @@ -317,6 +325,46 @@ def release_notes_handler(changelog_dir: str, lang: str, output: str = "") -> in
return 0


# Leading verbs converted to past tense when rendering release notes.
# Changelog yaml files keep the original (imperative) wording.
PAST_TENSE = {
"add": "added",
"allow": "allowed",
"apply": "applied",
"bump": "bumped",
"change": "changed",
"create": "created",
"delete": "deleted",
"disable": "disabled",
"drop": "dropped",
"enable": "enabled",
"enforce": "enforced",
"fix": "fixed",
"forbid": "forbidden",
"improve": "improved",
"introduce": "introduced",
"mark": "marked",
"move": "moved",
"remove": "removed",
"rename": "renamed",
"replace": "replaced",
"resolve": "resolved",
"rework": "reworked",
"update": "updated",
"upgrade": "upgraded",
}


def to_past_tense(entry: str) -> str:
words = entry.split(maxsplit=1)
if not words:
return entry
verb = PAST_TENSE.get(words[0].lower())
if not verb:
return entry
return verb + (" " + words[1] if len(words) > 1 else "")


def openwebui_translate_handler(file: str, lang: str, output: str = "") -> int:
api_token = os.environ.get("GPT_API_TOKEN", None)
if not api_token:
Expand All @@ -339,7 +387,13 @@ def openwebui_translate_handler(file: str, lang: str, output: str = "") -> int:

target = "Russian" if lang == "ru" else "English"

text = f"Translate the following YAML to {target}. Preserve YAML formatting.\n\n```{content}```"
text = (
f"Translate the following YAML to {target}. Preserve YAML formatting.\n"
"Do not translate YAML keys. Keep technical terms, product names, CVE ids\n"
"and file paths in English. Describe changes as already done: use past\n"
"tense passive forms (e.g. \u00abисправлено\u00bb, \u00abдобавлена\u00bb, \u00abобновлены\u00bb).\n"
f"\n```{content}```"
)

data = {
"model": "gpt-4o",
Expand Down
Loading