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
11 changes: 10 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion docs/CHANGELOG.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions scripts/release_notes.py
Original file line number Diff line number Diff line change
@@ -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())
69 changes: 69 additions & 0 deletions tests/test_release_notes.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion tests/test_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading