From b727cd78d3cd0e32f803a7565fe53f38d9ec1a43 Mon Sep 17 00:00:00 2001 From: Seungpyo1007 Date: Thu, 29 Oct 2026 12:17:34 +0900 Subject: [PATCH 1/2] ci: add TechEngineBot final-check comment for important PRs Release PRs into main and PRs labeled final-check get a single comment from TechEngineBot with validator/test results and the records touched. The comment is edited in place on each push. Posting needs the TECHENGINEBOT_TOKEN secret; without it the job skips the comment. --- .github/scripts/final_check.py | 87 +++++++++++++++++++++++++++++++ .github/workflows/final-check.yml | 69 ++++++++++++++++++++++++ README.md | 4 ++ 3 files changed, 160 insertions(+) create mode 100644 .github/scripts/final_check.py create mode 100644 .github/workflows/final-check.yml diff --git a/.github/scripts/final_check.py b/.github/scripts/final_check.py new file mode 100644 index 0000000..71d23d4 --- /dev/null +++ b/.github/scripts/final_check.py @@ -0,0 +1,87 @@ +"""Render TechEngineBot's final-check comment for a PR. + +Reads the validator / test outcomes from the environment and summarises the +ES records the PR touches. Prints Markdown to stdout. Stdlib only. +""" + +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +MARKER = "" +ICON = {"success": "✅", "failure": "❌", "skipped": "⏭️", "cancelled": "⏹️"} + + +def git(*args: str) -> str: + return subprocess.run(["git", *args], cwd=ROOT, check=True, capture_output=True, text=True).stdout + + +def changed_records(base: str, head: str) -> list[tuple[str, str]]: + """(status, path) for every data/cpu JSON the PR adds, modifies, or removes.""" + out = git("diff", "--name-status", "--no-renames", f"{base}...{head}", "--", "data/cpu") + rows = [] + for line in out.splitlines(): + status, path = line.split("\t", 1) + if path.endswith(".json"): + rows.append((status[0], path)) + return rows + + +def describe(path: str) -> str: + try: + rec = json.loads((ROOT / path).read_text(encoding="utf-8-sig")) + except (OSError, ValueError) as exc: + return f"`{path}` (unreadable: {exc})" + ident = rec.get("qspec") or rec.get("opn") or "—" + sources = len(rec.get("source_urls") or []) + return ( + f"`{rec.get('slug')}` · {ident} · {rec.get('architecture')} · " + f"{rec.get('cores')}C/{rec.get('threads')}T · class `{rec.get('sample_class')}` · " + f"{sources} source(s)" + ) + + +def main() -> int: + validate = os.environ.get("VALIDATE_OUTCOME", "skipped") + tests = os.environ.get("TESTS_OUTCOME", "skipped") + base = os.environ["BASE_SHA"] + head = os.environ["HEAD_SHA"] + passed = validate == "success" and tests == "success" + + rows = changed_records(base, head) + added = [p for s, p in rows if s == "A"] + modified = [p for s, p in rows if s == "M"] + removed = [p for s, p in rows if s == "D"] + + lines = [ + MARKER, + "## Final check", + "", + "**✅ Ready to merge**" if passed else "**❌ Not ready — fix the failing checks below**", + "", + f"- {ICON.get(validate, '❔')} `python -m app.validate`", + f"- {ICON.get(tests, '❔')} `python -m pytest -q`", + "", + f"**Records:** +{len(added)} added · ~{len(modified)} modified · −{len(removed)} removed", + ] + for title, paths in (("Added", added), ("Modified", modified)): + if paths: + lines += ["", f"
{title} ({len(paths)})", ""] + lines += [f"- {describe(p)}" for p in paths] + lines += ["", "
"] + if removed: + lines += ["", f"
Removed ({len(removed)})", ""] + lines += [f"- `{p}`" for p in removed] + lines += ["", "
"] + lines += ["", f"Checked at {head[:7]}. This comment is updated in place on every push."] + print("\n".join(lines)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/final-check.yml b/.github/workflows/final-check.yml new file mode 100644 index 0000000..fda8130 --- /dev/null +++ b/.github/workflows/final-check.yml @@ -0,0 +1,69 @@ +name: final-check + +# TechEngineBot leaves one final-check comment on important PRs: +# releases into main, or any PR labeled `final-check`. The comment is +# edited in place on each push, never duplicated. + +on: + pull_request: + branches: [develop, main] + types: [opened, synchronize, reopened, ready_for_review, labeled] + +permissions: + contents: read + +concurrency: + group: final-check-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + final-check: + if: >- + !github.event.pull_request.draft && + (github.base_ref == 'main' || contains(github.event.pull_request.labels.*.name, 'final-check')) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # diff against the base branch + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Validate ES catalog + id: validate + continue-on-error: true + run: python -m app.validate + - name: Tests + id: tests + continue-on-error: true + run: | + python -m pip install pytest + python -m pytest -q + - name: Render comment + env: + VALIDATE_OUTCOME: ${{ steps.validate.outcome }} + TESTS_OUTCOME: ${{ steps.tests.outcome }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: python .github/scripts/final_check.py > final-check.md + - name: Post as TechEngineBot + env: + GH_TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN }} + REPO: ${{ github.repository }} + PR: ${{ github.event.pull_request.number }} + run: | + # Bot account only: never fall back to a personal token. + if [ -z "$GH_TOKEN" ]; then + echo "::warning::TECHENGINEBOT_TOKEN is not set; skipping the comment" + exit 0 + fi + id=$(gh api "repos/$REPO/issues/$PR/comments" --paginate \ + --jq '.[] | select(.user.login == "TechEngineBot" and (.body | startswith(""))) | .id' | head -n1) + if [ -n "$id" ]; then + gh api -X PATCH "repos/$REPO/issues/comments/$id" -F body=@final-check.md > /dev/null + else + gh api "repos/$REPO/issues/$PR/comments" -F body=@final-check.md > /dev/null + fi + - name: Fail on failed checks + if: steps.validate.outcome != 'success' || steps.tests.outcome != 'success' + run: exit 1 diff --git a/README.md b/README.md index 6c19697..05f0db8 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,10 @@ Crawlers come later. This classifier is the gate. A release is a PR from `develop` to `main`. Open work PRs **against `develop`**. +## Final check + +Important PRs get one final-check comment from **TechEngineBot**: every release PR into `main`, and any PR labeled `final-check`. The comment reports the validator and test results plus the records the PR adds, modifies, or removes. It is edited in place on each push, and the job fails when a check fails. The comment is posted with the `TECHENGINEBOT_TOKEN` secret (a TechEngineBot token, never a personal one); without it the job still runs and skips the comment. + ## Contributing Open a PR against `develop` with a new/updated JSON file. `sample_class` must be `es`. Include `qspec` (Intel) and/or `opn` (AMD) and at least one public `source_url`. From 3620a54446250cc4dd36362252e02689adf06a90 Mon Sep 17 00:00:00 2001 From: Seungpyo1007 Date: Thu, 29 Oct 2026 12:32:59 +0900 Subject: [PATCH 2/2] ci: stop final-check when the comment lookup fails Without pipefail a failed gh api call in the lookup pipeline left its error body in $id, and the step then sent it to PATCH as a comment id. --- .github/workflows/final-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/final-check.yml b/.github/workflows/final-check.yml index fda8130..b4105d8 100644 --- a/.github/workflows/final-check.yml +++ b/.github/workflows/final-check.yml @@ -51,6 +51,7 @@ jobs: GH_TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN }} REPO: ${{ github.repository }} PR: ${{ github.event.pull_request.number }} + shell: bash # -eo pipefail: a failed lookup must stop here, not feed an error body into PATCH run: | # Bot account only: never fall back to a personal token. if [ -z "$GH_TOKEN" ]; then