11 KiB
GitHub, releases & site
Detail for AGENTS.md.
Every PR must be linked to an issue — Closes #N will NOT do it
Hard rule: link the PR to its issue in the GitHub Development panel (right sidebar → gear → pick the issue). A closing keyword in the body is not a substitute and does not work here.
GitHub creates the linked-issue relationship from a closing keyword only when the PR targets the
repo's default branch. This repo's default is master and essentially every PR targets
develop, so Closes #N here creates no link at all — not merely "no auto-close".
Measured 2026-08-20: PRs #537/#539/#542/#544/#545/#547 all carried Closes #N; every one
reported closingIssuesReferences: []. Only #546, linked by hand in the UI, had a real link. It
went unnoticed because timeline cross-references still appear — the issue looks connected while
its Development panel stays empty, so the issue never shows which release/pre-release tag
contains the work (that tag is the whole reason to link).
-
Check:
gh pr view N --json closingIssuesReferences --jq '.closingIssuesReferences | length'—0= unlinked. -
There IS a public API —
addCloseIssueReferences("Adds one or more pull requests as manually linked closing references on an issue"), idempotent. An earlier revision of this file said "Web UI only; don't burn time on a GraphQL mutation", which was wrong and cost every new PR a red check for a step that cannot be done until after the PR exists.scripts/pr-link-issue.sh <pr-number> [issue-number] [--repo owner/name]With no issue number it scans the PR body for a tag. Safe to re-run: already-linked is a no-op.
-
Normal flow: tag the PR body, CI links it.
.github/workflows/pr-issue-link.ymlruns onopened/edited/reopened/synchronize/labeled/unlabeled/ready_for_review, finds the tag, and creates the link. Accepted tags (case-insensitive):Closes #123 Fixes #123 Resolves #123 Refs #123 <!-- link-issue: #123 --> (invisible in the rendered body)The hidden comment form wins over the keyword forms, so a PR can discuss one issue in prose and link a different one deliberately. The keywords still do nothing on GitHub's own side here — they are markers for this job.
-
Forgot the tag at creation? Edit the description.
editedre-runs the job and it links then; no need to push a commit. The job checks out the BASE ref, never the PR head, because it runspull_request_targetwith a write token. -
Genuinely issue-less PR → add the
no-issuelabel. -
Still close the issue by hand when the PR merges (keywords don't fire on
develop).
GitHub Copilot PR reviews
- List unresolved threads:
gh api graphql -f query='{repository(owner:"libretro",name:"virtualjaguar-libretro"){pullRequest(number:N){reviewThreads(first:30){nodes{id isResolved comments(first:1){nodes{id author{login} body}}}}}}}' - Inline reply:
gh api -X POST repos/libretro/virtualjaguar-libretro/pulls/N/comments/<REST_ID>/replies -f body="..."— parent is the RESTidfromgh api .../comments, NOT the GraphQLPRRC_*id (404s). - Resolve thread:
gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "PRRT_..."}) { thread { isResolved } } }'. - Always reply AND resolve when addressing feedback — an open thread after a fix is noise.
- Do NOT trigger Copilot reviews.
gh pr comment N --body "@copilot review"bills per token since 2026-06-01 and spawns a coding-agent session;kimi-review.ymlalready reviews every PR todevelop/masterautomatically. Use only when a second opinion is worth the spend, and say why in the PR. - Repo-level Copilot instructions:
.github/copilot-instructions.md(input on every request — keep short). Depth goes in.github/prompts/*.prompt.md(loads only when invoked). - Review bots CodeRabbit/Copilot/Cursor all need a libretro-org App install we cannot do; every
PR push spends Kimi quota;
@kimi reviewneeds the workflow on master.
Branching (GitFlow)
Branch new work off develop (integration branch); master is release-only (tagged
commits, hotfix/release merges). PRs targeting master get auto-warned by
.github/workflows/warn-pr-base.yml — retarget to develop unless source is hotfix/* or
release/*. Full flow in docs/release-process.md. Branch off libretro/develop, not local
develop (local can be stale).
Release process (GitFlow)
Full detail in docs/release-process.md. Quick reference:
Nightlies: every push to develop reruns release.yml's 16-platform matrix and replaces the
rolling nightly prerelease + its pinned tracking issue. Gated on compiling, not on the test
suite — don't call nightlies "CI-verified". The nightly tag sits outside the v* filter so it
can't trigger a real release.
Cutting a release:
git checkout develop && git checkout -b release/vX.Y.Z- Bump version (all must match):
Makefile→CORE_BASE_VERSION := vX.Y.Z;dist/info/virtualjaguar_libretro.info→display_version = "vX.Y.Z";src/core/version_fallback.h→#define CORE_BASE_VERSION "vX.Y.Z"(used when the generatedsrc/core/version.his absent).bash scripts/check-info-version.shchecks all three (#734). - Release notes:
docs/RELEASE_NOTES_vX.Y.Z.md(template:docs/RELEASE_NOTES_v2.3.0.md) — highlights, bug fixes, perf, testing, known issues, stats (git diff --shortstat vPREV..HEAD), downloads, maintainers. - Verify:
make clean && make -j…clean,make testpasses,strings *.dylib | grep vX.Y.Z. Also regenerate both boot matrices on the corpus machine and gate the tag onpython3 -I test/tools/matrix_diff.py <prev-tag copy> <regenerated>(exit 1 = backward row = blocked; not runnable in CI) —docs/release-process.md§1. - Commit:
chore: bump version to vX.Y.Z, add release notes. - Push + PR:
git push -u libretro release/vX.Y.Zthengh pr create --base master. - After merge to master: tag
vX.Y.Zand push —release.ymlbuilds 16 platforms + publishes. - Back-merge:
git checkout develop && git merge master && git push libretro develop. - libretro-super: PR updating
dist/info/virtualjaguar_libretro.infothere.
Don't: tag before the PR merges to master; put new features on a release branch (bug fixes only); forget the back-merge (step 8) or develop diverges from the tagged version string.
Savestate version policy: one bump per release (all in-flight changes share the version); develop breakage OK.
The website is NOT published by this repo's pages.yml
Live site: https://jaguar.provenance-emu.com/, built from site/pages/*.html +
scripts/build_site.py on develop.
A green Deploy Pages run here is NOT evidence the site updated. The libretro org's Pages
config redirects every project site to www.libretro.com/<repo>/, which 404s — so this repo's
pages.yml deploys, reports success, and publishes to a dead path.
Real publisher: Publish site (workflow id 329140069) on the
Provenance-Emu/virtualjaguar-libretro fork, branch provenance. Checks out
libretro/…@develop (fetch-depth: 0 — generator derives sitemap lastmod from per-file git
dates, refuses a shallow clone), builds, deploys to the fork's Pages with the verified domain.
Trigger: cron 17 * * * * (hourly) + workflow_dispatch. A change merged to develop goes live
up to an hour later. Publish immediately:
gh workflow run 329140069 --repo Provenance-Emu/virtualjaguar-libretro --ref provenance
Verify against the live domain, not workflow status:
curl -sI https://jaguar.provenance-emu.com/ | grep -i last-modified
That timestamp tracks the fork's last publish; if it predates your develop commit the site is
stale. Responses carry cache-control: max-age=600 — polling past ~10 min with unchanged
last-modified means content is old, not cached. Local preview: python3 scripts/build_site.py
→ _site/; scripts/check_site.py asserts canonical/og:url/sitemap/robots agree with
SITE_BASE (override VJ_SITE_BASE).
Roadmap
Planning lives in GitHub milestones (pinned issue #371), never in md files.
Temporary CI runners
VJ_TEMP_RUNNERS is an optional repository variable containing a JSON object with
a refs allowlist and mappings from hosted labels to private runner labels.
Only listed Git refs use those mappings in explicitly opted-in lightweight jobs;
absent refs or labels keep their original GitHub-hosted runners. Leave the variable unset normally. A temporary release
runner must use a unique custom label with default labels disabled, preserve the
requested OS/architecture, and be removed together with the variable after its
checks finish. Do not map Ubuntu 22.04 packaging jobs onto Ubuntu 24.04: that would
change the supported glibc floor.
Shared production core builds
For same-repository release/** PRs with a merge tree identical to the release head,
C/C++ CI shares ten compile-only production builds from the exact-head Release push run:
macOS Intel, WASM, four Android ABIs, iOS, tvOS, Vita and Switch. Other PRs, forks,
manual runs and integration-branch pushes keep the full compiler matrix. The configurations
live in .github/ci/core-matrix.json; a Ruby workflow-contract check rejects mismatched
compiler/SDK/architecture settings before any rows are omitted.
The seven native C/C++ matrix rows, GCC/Clang differences, MSVC checks, coverage,
ASan/UBSan, clang-tidy and production -> TEST_EXPORTS=1 -> production mode-switch
coverage stay independent. Compile-only fallback builds use the same RELEASE_DEBUG_INFO=1
configuration as Release (adds debug information to the optimized production build).
Android's compile-definition and selected-SIMD-object assertions run through
scripts/build-ndk-ci.sh in both workflows. Compile-only Mach-O production exports are
checked before Release uploads a shared binary.
Release retains an unstripped ci-core-<platform> intermediate before packaging/signing.
BUILD.json records the exact commit, platform, filename, production ABI setting and
SHA-256. C/C++ CI requires successful production jobs, both raw and distributable artifact
groups, and verifies the downloaded metadata/checksums before retaining all ten original
C/C++ artifact names. Failed, cancelled, skipped, stale, missing or mismatched evidence
cannot make the shared check green. The hosted waiting job does not occupy the temporary
runner needed by Release metadata jobs.
These intermediates have 14-day retention and are explicitly excluded by both release publishers' staging filters. Shipped binaries, split debug archives, their names and checksum collection remain unchanged. The C/C++ artifact aliases retain their normal artifact retention. The shared core verifier and shared app-package verifier must both pass before merging a release PR that uses these routes; a configured skip of duplicate build rows is not sufficient.