Skip to content

fix(core): permission prompts and the agent indicator (#148) (#150) #48

fix(core): permission prompts and the agent indicator (#148) (#150)

fix(core): permission prompts and the agent indicator (#148) (#150) #48

Workflow file for this run

# THE CORE RELEASES ITSELF, AND TELLS PRISM (#23).
#
# core/ is the terminal of TWO apps. Prism builds against a pinned tag of the
# `core-dist` branch, so a core change reaches Prism only when (1) core-dist is
# re-split, (2) a core-v<version> tag is pushed, and (3) Prism's pin is bumped.
# All three were done by hand, which is how an app falls silently behind.
# Owner, 2026-09-19: "that compiled copy needs to be auto bumped when a new
# Prism Terminal release or merge to main happens."
#
# So, on every push to main that touches core/:
# 1. split core/ into core-dist and tag it core-v<core/package.json version>;
# 2. open a PR in Maxaubert/Prism that bumps the pin to that tag.
#
# THE BUMP PROVES ITSELF (#23, and Prism #164). Prism's terminal gate used to
# run only on the owner's machine, so a bump could not merge without a person.
# It now runs on a GitHub runner (`terminal-gate.yml` in Prism; MEASURED green
# 3 runs of 3, real dictation included), beside Prism's typecheck, lint and unit
# tests. So the bump PR carries its own version bump, this job WAITS for every
# check on it, and then:
# - repo variable PRISM_AUTO_MERGE = 'true': it squash-merges the PR, Prism's
# release.yml publishes the new version, and Prism's users see the update
# chip. No person in the loop. This is a STANDING EXCEPTION to the owner's
# "never merge without my word" rule, and only the owner switches it on.
# - anything else (the default): the PR stays open, green, for a person to merge.
# A red check never merges, in either mode: the PR stays open and says why.
#
# Step 2 needs a token that can write to the Prism repo, since this repo's own
# GITHUB_TOKEN cannot: a fine-grained token on Maxaubert/Prism with Contents and
# Pull requests read/write, stored here as the secret PRISM_BUMP_TOKEN. Without
# it step 2 says so and is skipped; step 1 still happens.
name: core-release
on:
push:
branches: [main]
paths: ['core/**']
workflow_dispatch:
permissions:
contents: write
issues: write
concurrency:
group: core-release
cancel-in-progress: false
jobs:
release:
# MAIN ONLY (code review 2026-09-24, #34). A run by hand may pick any
# branch, and this job force-pushes core-dist and pushes a tag that is never
# moved: from a feature branch it released unreviewed core to Prism, and
# with auto-merge on, Prism shipped it. By hand is for resuming on main.
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
fresh: ${{ steps.tag.outputs.fresh }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Split core/ and tag it
id: tag
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
version=$(node -p "require('./core/package.json').version")
tag="core-v$version"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
git subtree split --prefix=core -b core-dist-new
tree=$(git rev-parse 'core-dist-new^{tree}')
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
if [ "$(git rev-parse "$tag^{tree}")" = "$tree" ]; then
echo "::notice::$tag already holds exactly this core. Nothing to release."
echo "fresh=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# A tag is a promise to everyone who pinned it. It is never moved.
msg="core/ changed but core/package.json still says $version, and $tag already exists with different contents. Bump the version in core/package.json (ci.yml checks this on the PR)."
echo "::error::$msg"
# Not only a red run nobody opens (code review 2026-09-24, #39): two
# PRs that bumped core to the same version both passed, and the
# second one's core never reached Prism. An issue says so.
gh issue create --title "Core change on main was not released: $tag already exists" \
--body "$msg Commit: $GITHUB_SHA. Open a PR that bumps core/package.json; its merge releases the core." || true
exit 1
fi
git push -f origin core-dist-new:core-dist
git tag "$tag" core-dist-new
git push origin "$tag"
echo "fresh=true" >> "$GITHUB_OUTPUT"
echo "::notice::Released $tag"
bump-prism:
needs: release
# On a fresh tag, or when run by hand: that is how a bump HELD by a red check
# is resumed once the cause is fixed (it finds the open PR and carries on).
if: needs.release.outputs.fresh == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 45
env:
BUMP_TOKEN: ${{ secrets.PRISM_BUMP_TOKEN }}
AUTO_MERGE: ${{ vars.PRISM_AUTO_MERGE }}
TAG: ${{ needs.release.outputs.tag }}
steps:
- name: No token, no bump
if: env.BUMP_TOKEN == ''
run: |
echo "::warning::$TAG is released, but the secret PRISM_BUMP_TOKEN is not set, so no PR was opened in Maxaubert/Prism. Bump Prism's pin by hand, or add the secret (see the top of core-release.yml) and re-run this workflow."
- uses: actions/checkout@v4
if: env.BUMP_TOKEN != ''
with:
repository: Maxaubert/Prism
token: ${{ secrets.PRISM_BUMP_TOKEN }}
- uses: actions/setup-node@v4
if: env.BUMP_TOKEN != ''
with:
node-version: 22
- name: Bump the pin and the version, open the PR
id: pr
if: env.BUMP_TOKEN != ''
env:
GH_TOKEN: ${{ secrets.PRISM_BUMP_TOKEN }}
run: |
set -euo pipefail
branch="chore/$TAG"
if [ -n "$(git ls-remote --heads origin "$branch")" ]; then
# Already opened, by an earlier run: never rebuilt (a person may have
# pushed a fix onto it). Carry on from its open PR, if it still has one.
url=$(gh pr list --repo Maxaubert/Prism --head "$branch" --state open --json url --jq '.[0].url // ""')
if [ -n "$url" ]; then
echo "::notice::$branch is already open as $url. Resuming from its checks."
echo "url=$url" >> "$GITHUB_OUTPUT"
else
echo "::notice::$branch exists in Prism with no open PR (merged or closed). Nothing to do."
fi
exit 0
fi
current=$(node -p "require('./package.json').devDependencies['prism-term-core']")
want="github:Maxaubert/PrismTerminal#$TAG"
if [ "$current" = "$want" ]; then
echo "::notice::Prism already pins $TAG."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b "$branch"
npm pkg set "devDependencies.prism-term-core=$want"
# A NEW Prism version, or release.yml refuses to publish and nobody's
# update chip fires. A core FEATURE (its major.minor moved) is a minor
# bump of Prism, anything else a patch: the owner's own rule.
kind=$(node -e "
const v = (s) => (String(s).match(/core-v(\d+)\.(\d+)\./) || []).slice(1, 3).join('.')
process.stdout.write(v(process.argv[1]) && v(process.argv[1]) !== v(process.argv[2]) ? 'minor' : 'patch')
" "$current" "$TAG")
npm version "$kind" --no-git-tag-version >/dev/null
version=$(node -p "require('./package.json').version")
# THE LOCKFILE IS EDITED, NOT REGENERATED. `npm install --package-lock-only`
# on this Linux runner rewrote about 300 unrelated lines of Prism's lock
# (MEASURED on the first live bump, Prism #165: a different npm drops every
# "libc" field, and lock-only mode invents bundled entries even under the
# owner's own npm). A pin bump is FOUR lines: the two version fields npm
# version has just set, the pin, and the commit it resolves to. The rest
# of the entry (the core's own version and peer ranges) is read from the
# released core itself, so the lock says what `npm ci` will find.
sha=$(git ls-remote https://github.com/Maxaubert/PrismTerminal.git "refs/tags/$TAG" | cut -f1)
if [ -z "$sha" ]; then echo "::error::$TAG was not found on PrismTerminal."; exit 1; fi
curl -fsSL "https://raw.githubusercontent.com/Maxaubert/PrismTerminal/$sha/package.json" -o "$RUNNER_TEMP/core.json"
WANT="$want" SHA="$sha" CORE="$RUNNER_TEMP/core.json" node -e "
const fs = require('fs')
const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8'))
const core = JSON.parse(fs.readFileSync(process.env.CORE, 'utf8'))
const entry = lock.packages['node_modules/prism-term-core']
if (!entry) throw new Error('package-lock.json has no prism-term-core entry')
lock.packages[''].devDependencies['prism-term-core'] = process.env.WANT
entry.version = core.version
entry.resolved = 'git+ssh://git@github.com/Maxaubert/PrismTerminal.git#' + process.env.SHA
if (core.peerDependencies) entry.peerDependencies = core.peerDependencies
fs.writeFileSync('package-lock.json', JSON.stringify(lock, null, 2) + '\n')
"
changed=$(git diff --numstat package-lock.json | cut -f1)
if [ "${changed:-0}" -gt 12 ]; then
echo "::error::The lockfile edit touched $changed lines; a pin bump is about four. Not pushing it."
exit 1
fi
git add package.json package-lock.json
git commit -m "build(terminal): pin prism-term-core to $TAG, $version"
git push origin "$branch"
url=$(gh pr create --repo Maxaubert/Prism --base main --head "$branch" \
--title "Terminal core: prism-term-core $TAG (Prism $version)" \
--body "Opened automatically by PrismTerminal's \`core-release\` workflow: the shared terminal core released **$TAG**, and Prism pinned \`$current\`. This bumps the pin and makes Prism **$version** ($kind).
What changed in the core: https://github.com/Maxaubert/PrismTerminal/commits/main/core
The checks on this PR are the proof: typecheck, lint, the unit suite, and **terminal-gate**, which builds the app on a Windows runner and drives every scenario the terminal can break, real dictation included.
If the repo variable \`PRISM_AUTO_MERGE\` in PrismTerminal is \`true\`, this merges itself once every check is green and Prism releases $version. Otherwise it waits here for a person. A red check never merges.")
echo "url=$url" >> "$GITHUB_OUTPUT"
echo "::notice::Opened $url"
- name: Wait for Prism's checks, then merge if the owner switched that on
if: env.BUMP_TOKEN != '' && steps.pr.outputs.url != ''
env:
GH_TOKEN: ${{ secrets.PRISM_BUMP_TOKEN }}
URL: ${{ steps.pr.outputs.url }}
run: |
set -uo pipefail
# Checks take a moment to be registered; "no checks" is not "green".
# THE GATE BY NAME (code review 2026-09-24, #38): a count of three
# checks meant the gate only while Prism had exactly three; one more
# check anywhere and --watch could finish green before terminal-gate
# was even registered, and a bump merged without the ratchet.
gate() {
gh pr checks "$URL" --json workflow,state \
--jq "[.[] | select(.workflow == \"terminal-gate\"$1)] | length" 2>/dev/null || echo 0
}
for i in $(seq 1 40); do
[ "$(gate '')" -ge 1 ] && break
sleep 15
done
if [ "$(gate '')" -lt 1 ]; then
echo "::error::Prism's terminal-gate never reported on $URL. Not merging."
exit 1
fi
if ! gh pr checks "$URL" --watch --interval 30 --fail-fast; then
echo "::error::A check failed on $URL. The bump stays open; it is NOT merged."
exit 1
fi
if [ "$(gate ' and .state == "SUCCESS"')" -lt 1 ]; then
echo "::error::terminal-gate did not pass on $URL (skipped or cancelled). Not merging."
exit 1
fi
if [ "${AUTO_MERGE:-}" = "true" ]; then
gh pr merge "$URL" --squash --delete-branch
echo "::notice::Merged $URL. Prism's release workflow publishes it now."
else
echo "::notice::$URL is green and waiting for a person (PRISM_AUTO_MERGE is not 'true')."
fi