chore: version packages #367
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: OpenSpec Label | |
| # Reports whether a pull request's stack still carries an unresolved OpenSpec | |
| # change. It labels, it warns, and it never fails. | |
| # | |
| # NOTHING HERE BLOCKS, AND THAT IS THE DESIGN. Three earlier versions of this | |
| # check reported through a check status and all three were deleted in 8d1f3a1: | |
| # `pr-check-openspec.yml` reddened a pull request whenever `gh pr list` failed, | |
| # a step in `validate.yml` ran `main` red for as long as a forward-merging stack | |
| # took to drain, and both trained people to route around red. A signal expected | |
| # to be red is not a signal. A label costs a shrug when it is wrong, which is | |
| # what lets the predicate stay simple enough to be dependable. | |
| # | |
| # THE LABEL PREDICATE ASKS NOTHING ABOUT STACK POSITION. It reads the head tree: | |
| # does any directory other than `archive/` exist under `openspec/changes/`? A | |
| # stack has not resolved its OpenSpec change until one of its branches archives | |
| # it, and that is a property of the stack rather than of a branch's place in it, | |
| # so every pull request in the stack gets the same answer with no API call that | |
| # can fail. This is the shape `changeset.yml` uses, and it is the only shape in | |
| # this repository that has never misfired. | |
| # | |
| # THIS IS NOT A STEP IN `Validate`, DELIBERATELY. `release-cli-nightly.yml` | |
| # triggers on `workflow_run: workflows: [Validate]`, matched by that workflow's | |
| # `name:` string. Any signal outside that workflow is invisible to the nightly, | |
| # so an unarchived change cannot gate a nightly publish. The separation is what | |
| # enforces it, not a rule anyone has to remember. | |
| on: | |
| # No `branches:` filter. The label is a fact about the stack, so it belongs on | |
| # every pull request in one, and a filter is not a dependable way to scope a | |
| # workflow anyway: GitHub sometimes resolves a stacked pull request's eventual | |
| # target and matches on that, and sometimes stops without warning. | |
| # | |
| # `ready_for_review` is not in the default set and must be named, or a draft | |
| # marked ready keeps whatever label it had until something happens to push. | |
| pull_request: | |
| types: [opened, reopened, synchronize, ready_for_review] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| label: | |
| name: OpenSpec Label | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| # The head commit, not the merge ref: the question is what this branch | |
| # carries. A child branch contains its ancestors' commits, so the head | |
| # tree already answers for the whole stack below it. | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| # The listing and its safe-capture idiom live in the composite action, | |
| # shared with openspec-sweep.yml and openspec-tracking.yml (#289). It | |
| # never fails: an unreadable `openspec/changes` comes back as `ok=false` | |
| # with the reason in `error`, and the step below decides what that means | |
| # here. The action is read from the head checkout above, the same tree | |
| # `openspec-tracking.cjs` already runs from. | |
| - name: List unarchived OpenSpec changes | |
| id: list | |
| uses: ./.github/actions/openspec-list | |
| - name: Report unresolved OpenSpec changes | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| PR: ${{ github.event.pull_request.number }} | |
| HEAD_REF: ${{ github.head_ref }} | |
| HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | |
| LABEL: Open OpenSpec | |
| LIST_OK: ${{ steps.list.outputs.ok }} | |
| LIST_CHANGES: ${{ steps.list.outputs.changes }} | |
| LIST_ERROR: ${{ steps.list.outputs.error }} | |
| run: | | |
| set -euo pipefail | |
| # A failed listing must read as "could not tell" rather than as | |
| # "resolved": reporting an unreadable directory as zero unarchived | |
| # changes would tell a stack its OpenSpec change is done when nobody | |
| # checked, and remove the label from a branch whose change is still | |
| # there. So it gets its own summary and an explicit annotation, and | |
| # the label is left untouched. This workflow's whole point is to | |
| # never fail a check for a reason unrelated to the pull request. | |
| if [ "$LIST_OK" != "true" ]; then | |
| echo "::warning::Could not list openspec/changes/ ($LIST_ERROR). Leaving the '$LABEL' label untouched rather than guessing." | |
| { | |
| echo "### OpenSpec: unknown" | |
| echo "" | |
| echo "Could not list \`openspec/changes/\` on this branch, so whether an unarchived change remains is unknown. The \`$LABEL\` label was left as-is." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| # The action validated `changes` as a JSON array of strings before | |
| # writing it, so this `jq` cannot fail on the value and needs no | |
| # guard of its own. | |
| changes=$(jq -r '.[]' <<<"$LIST_CHANGES") | |
| if [ -z "$changes" ]; then | |
| echo "No unarchived OpenSpec changes on this branch." | |
| { | |
| echo "### OpenSpec: resolved" | |
| echo "" | |
| echo "This branch carries no unarchived change under \`openspec/changes/\`." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| if [ "$HEAD_REPO" = "$REPO" ]; then | |
| gh pr edit "$PR" --repo "$REPO" --remove-label "$LABEL" || \ | |
| echo "::notice::Could not remove the '$LABEL' label; it may not have been applied." | |
| fi | |
| exit 0 | |
| fi | |
| echo "Unarchived OpenSpec changes on this branch:" | |
| echo "$changes" | sed 's/^/ - /' | |
| # A fork's `pull_request` token is read-only. `pull_request_target` is | |
| # the usual answer and it is rejected here: it runs a writable token | |
| # against the base repository, which is too much privilege to buy a | |
| # cosmetic label. The fork path reports through the summary alone. | |
| labelled=1 | |
| if [ "$HEAD_REPO" = "$REPO" ]; then | |
| gh pr edit "$PR" --repo "$REPO" --add-label "$LABEL" || labelled=0 | |
| else | |
| labelled=0 | |
| fi | |
| { | |
| echo "### OpenSpec: unresolved" | |
| echo "" | |
| echo "This branch carries an unarchived change under \`openspec/changes/\`:" | |
| echo "" | |
| echo "$changes" | sed 's/^/- `/;s/$/`/' | |
| echo "" | |
| if [ "$labelled" -eq 1 ]; then | |
| echo "Labelled \`$LABEL\`. The label clears when a branch in this stack archives the change." | |
| else | |
| echo "The \`$LABEL\` label could not be applied. A pull request from a fork gets a read-only token, and this workflow does not use \`pull_request_target\` to work around that." | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # The tip is the last branch that can archive before the change | |
| # reaches `main`, so it is the only position where a warning is | |
| # actionable. Reading it needs the API, which introduces a failure | |
| # that is not about this pull request: the deleted workflow exited 1 | |
| # here and reddened pull requests for `gh` timeouts. This one skips | |
| # the warning and says it could not tell. | |
| # | |
| # Gated on same-repo for the same reason the label write above is. A | |
| # fork's head branch lives in the fork, so no pull request in this | |
| # repository can name it as a base: the count would be 0 whatever the | |
| # real stack position, and every fork pull request would be told it is | |
| # the tip. Reporting "could not tell" is honest; guessing is not. | |
| if [ "$HEAD_REPO" != "$REPO" ]; then | |
| echo "::notice::Stack position is not readable for a fork pull request; skipping the tip warning." | |
| { | |
| echo "" | |
| echo "_This pull request comes from a fork, so its stack position cannot be read from this repository. No tip warning was evaluated._" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| if ! children=$(gh pr list --repo "$REPO" --state open --base "$HEAD_REF" --json number --jq 'length'); then | |
| echo "::notice::Could not determine stack position; skipping the tip warning." | |
| { | |
| echo "" | |
| echo "_Stack position could not be determined, so no tip warning was evaluated._" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| if [ "${children:-0}" -gt 0 ]; then | |
| echo "$children open pull request(s) are stacked on top; the change is not due for archiving." | |
| { | |
| echo "" | |
| echo "_${children} pull request(s) are stacked above this one, so the change is not due for archiving yet._" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| echo "::warning::This is the tip of its stack and leaves an OpenSpec change unarchived. Run 'pnpm openspec archive <change>' before merging, or the standing specs reach main describing requirements the code has already met." | |
| { | |
| echo "" | |
| echo "**No pull request is stacked above this one.** It is the tip, so it is the" | |
| echo "last branch that can archive the change before it reaches \`main\`." | |
| echo "" | |
| echo "\`\`\`bash" | |
| echo "$changes" | sed 's/^/pnpm openspec archive /' | |
| echo "\`\`\`" | |
| echo "" | |
| echo "_This is a warning. It does not block the merge._" | |
| } >> "$GITHUB_STEP_SUMMARY" |