diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 644a501..c576a67 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -142,6 +142,12 @@ jobs: --output "${asset}.asc" "$asset" done + - name: Extract release notes from the changelog + shell: bash + run: | + set -euo pipefail + python scripts/release_notes.py "${GITHUB_REF_NAME#v}" > "${RUNNER_TEMP}/release-notes.md" + - name: Stage draft release with assets env: GH_TOKEN: ${{ github.token }} @@ -156,13 +162,16 @@ jobs: fi gh release upload "${GITHUB_REF_NAME}" release/* \ --repo "${GITHUB_REPOSITORY}" --clobber + gh release edit "${GITHUB_REF_NAME}" \ + --repo "${GITHUB_REPOSITORY}" \ + --notes-file "${RUNNER_TEMP}/release-notes.md" else gh release create "${GITHUB_REF_NAME}" release/* \ --repo "${GITHUB_REPOSITORY}" \ --draft \ --verify-tag \ --title "TrinaxAI ${GITHUB_REF_NAME#v}" \ - --generate-notes + --notes-file "${RUNNER_TEMP}/release-notes.md" fi - name: Verify exact draft release assets and signatures diff --git a/README.es.md b/README.es.md index c224371..33e8072 100644 --- a/README.es.md +++ b/README.es.md @@ -125,6 +125,16 @@ La configuración elige un perfil según la memoria y los recursos gráficos: El perfil `8gb` funciona solo con CPU. Consulta la [referencia de configuración](docs/CONFIGURATION.es.md) para modelos y recursos. +## Límites conocidos + +- Preguntas sobre código respondidas por modelos locales pequeños: las fuentes + citadas a veces son erróneas o incompletas, así que abre el archivo citado + antes de fiarte de una respuesta. +- `trinaxai research` es lento en un equipo sin GPU. +- No medido: el flujo completo (instalación, chat, RAG, agente y app web) en + Windows, con GPU y con modelos grandes. macOS y Windows están cubiertos por las + comprobaciones automáticas de CI, no por ejecuciones de extremo a extremo. + ## Privacidad y seguridad TrinaxAI enlaza los servicios locales a loopback por defecto. Ollama no se diff --git a/README.md b/README.md index 9366cd3..3900479 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,16 @@ Setup chooses a model profile from available memory and GPU resources: The `8gb` profile works with CPU-only hardware. See the [configuration reference](docs/CONFIGURATION.md) for model and resource settings. +## Known limits + +- Questions about code answered by small local models: the cited sources are + sometimes wrong or incomplete, so open the cited file before relying on an + answer. +- `trinaxai research` is slow on a CPU-only computer. +- Not measured: the full flow (install, chat, RAG, agent, web app) on Windows, + on a GPU, and with large models. macOS and Windows are covered by the automated + CI checks, not by end-to-end runs. + ## Privacy and security TrinaxAI binds local services to loopback by default. Ollama is not exposed as diff --git a/docs/CHANGELOG.es.md b/docs/CHANGELOG.es.md index 2169838..2b9b8c8 100644 --- a/docs/CHANGELOG.es.md +++ b/docs/CHANGELOG.es.md @@ -18,7 +18,7 @@ el formato de [Keep a Changelog](https://keepachangelog.com/). ## [Sin publicar] -## [1.4.0] — 2026-10-23 +## [1.4.0] — 2026-10-26 ### Añadido @@ -326,6 +326,16 @@ el formato de [Keep a Changelog](https://keepachangelog.com/). traducidos, y una instalación fallida indica que se puede ejecutar de nuevo sin riesgo. +### Límites conocidos + +- Preguntas sobre código respondidas por modelos locales pequeños: las fuentes + citadas a veces son erróneas o incompletas, así que abre el archivo citado + antes de fiarte de una respuesta. +- `trinaxai research` es lento en un equipo sin GPU. +- No medido: el flujo completo (instalación, chat, RAG, agente y app web) en + Windows, con GPU y con modelos grandes. macOS y Windows están cubiertos por las + comprobaciones automáticas de CI, no por ejecuciones de extremo a extremo. + ## [1.3.0] — 2026-10-18 ### Cambiado diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d92db74..548aa54 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -18,7 +18,7 @@ All notable changes to TrinaxAI are documented here. This project follows the ## [Unreleased] -## [1.4.0] — 2026-10-23 +## [1.4.0] — 2026-10-26 ### Added @@ -311,6 +311,16 @@ All notable changes to TrinaxAI are documented here. This project follows the - Installer messages that appeared in English in Spanish mode are translated, and a failed install says it is safe to run again. +### Known limits + +- Questions about code answered by small local models: the cited sources are + sometimes wrong or incomplete, so open the cited file before relying on an + answer. +- `trinaxai research` is slow on a CPU-only computer. +- Not measured: the full flow (install, chat, RAG, agent, web app) on Windows, + on a GPU, and with large models. macOS and Windows are covered by the automated + CI checks, not by end-to-end runs. + ## [1.3.0] — 2026-10-18 ### Changed diff --git a/scripts/release_notes.py b/scripts/release_notes.py new file mode 100644 index 0000000..a266f0b --- /dev/null +++ b/scripts/release_notes.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Print one release's section of docs/CHANGELOG.md, to use as the GitHub release notes. + +The release workflow fails when the section is missing or empty, so a tag can never +publish notes that skip the known limits written in the changelog. +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_CHANGELOG = ROOT / "docs" / "CHANGELOG.md" + + +def extract_release_notes(changelog: str, version: str) -> str: + """Return the body under ``## [version]`` up to the next ``## `` heading.""" + heading = re.compile(rf"^## \[{re.escape(version)}\][^\n]*(?:\n|$)", re.MULTILINE) + start = heading.search(changelog) + if start is None: + raise ValueError(f"CHANGELOG has no section for version {version}") + rest = changelog[start.end() :] + following = re.search(r"^## ", rest, re.MULTILINE) + body = (rest[: following.start()] if following else rest).strip() + if not body: + raise ValueError(f"CHANGELOG section for version {version} is empty") + return body + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("version", help="release version without the leading v, for example 1.4.0") + parser.add_argument("--changelog", type=Path, default=DEFAULT_CHANGELOG) + args = parser.parse_args(argv) + try: + notes = extract_release_notes(args.changelog.read_text(encoding="utf-8"), args.version) + except (OSError, ValueError) as exc: + print(f"release notes: {exc}", file=sys.stderr) + return 1 + print(notes) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_release_notes.py b/tests/test_release_notes.py new file mode 100644 index 0000000..174be3a --- /dev/null +++ b/tests/test_release_notes.py @@ -0,0 +1,69 @@ +import re +from pathlib import Path + +import pytest + +from scripts import release_notes + +CHANGELOG = """# Changelog + +## [Unreleased] + +## [1.4.0] — 2026-10-26 + +### Added + +- Something new. + +### Known limits + +- A limit. + +## [1.3.0] — 2026-10-18 + +- Older. +""" + + +def test_extracts_only_the_requested_section() -> None: + notes = release_notes.extract_release_notes(CHANGELOG, "1.4.0") + + assert notes.startswith("### Added") + assert "### Known limits" in notes + assert "Older." not in notes + assert "[1.4.0]" not in notes + + +def test_last_section_runs_to_the_end_of_the_file() -> None: + assert release_notes.extract_release_notes(CHANGELOG, "1.3.0") == "- Older." + + +def test_missing_or_empty_sections_are_errors() -> None: + with pytest.raises(ValueError, match="no section for version 9.9.9"): + release_notes.extract_release_notes(CHANGELOG, "9.9.9") + with pytest.raises(ValueError, match="empty"): + release_notes.extract_release_notes(CHANGELOG, "Unreleased") + with pytest.raises(ValueError, match="no section"): + release_notes.extract_release_notes(CHANGELOG, "1.4") + + +def test_cli_prints_notes_and_fails_closed(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + changelog = tmp_path / "CHANGELOG.md" + changelog.write_text(CHANGELOG, encoding="utf-8") + + assert release_notes.main(["1.4.0", "--changelog", str(changelog)]) == 0 + assert "### Known limits" in capsys.readouterr().out + assert release_notes.main(["2.0.0", "--changelog", str(changelog)]) == 1 + assert "no section for version 2.0.0" in capsys.readouterr().err + assert release_notes.main(["1.4.0", "--changelog", str(tmp_path / "missing.md")]) == 1 + + +def test_the_repository_changelog_has_notes_for_the_current_version() -> None: + root = Path(release_notes.__file__).resolve().parents[1] + match = re.search(r'^version = "([^"]+)"$', (root / "pyproject.toml").read_text(encoding="utf-8"), re.MULTILINE) + assert match is not None + version = match.group(1) + + notes = release_notes.extract_release_notes((root / "docs" / "CHANGELOG.md").read_text(encoding="utf-8"), version) + + assert notes diff --git a/tests/test_scripts.py b/tests/test_scripts.py index 07ad785..90d7a3f 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -590,7 +590,9 @@ def test_tagged_releases_publish_a_simple_verified_release() -> None: assert "SHA256SUMS" in workflow assert "gh release create" in workflow assert "--verify-tag" in workflow - assert "--generate-notes" in workflow + assert "--generate-notes" not in workflow + assert "scripts/release_notes.py" in workflow + assert workflow.count("--notes-file") == 2 assert "docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e" in workflow assert "actions/attest@v4" not in workflow