Skip to content
Merged
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
79 changes: 79 additions & 0 deletions .github/workflows/org-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,82 @@ jobs:
json-validity: false
vite-plus:
uses: nyuchi/.github/.github/workflows/reusable-vite-plus.yml@bb87d28e55c0c3c385121113900b698eb6ce8ab2 # v0.5.0

# Every pull request names the issue it serves. A closing keyword (Closes,
# Fixes, Resolves) makes GitHub close the issue when the PR merges into the
# default branch; Refs attaches an issue that stays open (partial work); and
# "No issue: <reason>" is the explicit opt-out for trivial changes. Bots are
# exempt. GitHub only acts on a closing keyword when the merge lands on the
# default branch, so a PR into staging closes nothing until the release PR to
# main repeats it: that PR is checked for the lines it is missing.
issue-link:
name: issue attached
if: github.event_name == 'pull_request' && github.event.pull_request.user.type != 'Bot'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- name: The PR closes or references an issue, or says why not
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
BODY: ${{ github.event.pull_request.body }}
BASE: ${{ github.event.pull_request.base.ref }}
DEFAULT: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
close='(close[sd]?|fix(e[sd])?|resolve[sd]?)[: ]+([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)?#[0-9]+'
refs='(refs?|related to|part of)[: ]+([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)?#[0-9]+'
if ! printf '%s' "$BODY" | grep -qiE "\b($close|$refs)|^no issue:[[:space:]]*[^[:space:]]"; then
msg="Add 'Closes #123' to the description, 'Refs #123' for partial work,"
msg="$msg or 'No issue: <reason>' for a trivial change."
echo "::error title=No issue attached::$msg"
exit 1
fi
echo "Issue attached."
if [ "$BASE" = "$DEFAULT" ]; then
have="$(printf '%s' "$BODY" | grep -oiE "$close" | grep -oE '#[0-9]+' | sort -u || true)"
want="$( { gh api "repos/$REPO/compare/$DEFAULT...staging" --jq '.commits[].sha' 2>/dev/null || true; } |
while read -r sha; do gh api "repos/$REPO/commits/$sha/pulls" --jq '.[].body // ""' 2>/dev/null || true; done |
grep -oiE "$close" | grep -oE '#[0-9]+' | sort -u || true)"
missing="$(comm -13 <(printf '%s\n' "$have") <(printf '%s\n' "$want") | grep . || true)"
if [ -n "$missing" ]; then
lines="$(printf '%s\n' "$missing" | sed 's/^/Closes /')"
msg="PRs in this release close issues this description does not repeat,"
echo "::warning title=Release is missing closing lines::$msg so they stay open. Add:"
printf '%s\n' "$lines"
fi
fi

# CLAUDE.md and .claude/ are development tooling. They are never tracked and
# never released, so a pull request must not add or change them (deleting
# them is fine). Only files the PR touches are checked, so a repo that still
# tracks one is not blocked: it is asked to remove it, and nothing new can
# arrive. A release PR into the default branch is a pull request too, so this
# is also what keeps them off main.
agent-files:
name: no agent-local files
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- name: No CLAUDE.md or .claude/ in the diff
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
touched="$(gh api "repos/$REPO/pulls/$PR/files" --paginate \
--jq '.[] | select(.status != "removed") | .filename' |
grep -E '(^|/)(CLAUDE(\.local)?\.md|\.claude/)' || true)"
if [ -n "$touched" ]; then
msg="CLAUDE.md and .claude/ are development tooling and never reach main or production."
echo "::error title=Agent-local files::$msg Remove these from the pull request and keep them in .gitignore:"
printf '%s\n' "$touched"
exit 1
fi
echo "No CLAUDE.md or .claude/ added or changed."
Loading