From 3922e1ae944efabf571bfa3453aea10f2c1fab77 Mon Sep 17 00:00:00 2001 From: aaronbcarlisle Date: Sat, 26 Sep 2026 04:59:43 -0400 Subject: [PATCH 1/2] ci: add PR automation, issue/PR templates and Dependabot Ported from ffxiv-raid-planner and adapted to this repo: - pr-automation.yml: assign the PR author; label from the conventional commit prefix (now also matching scoped/breaking titles like "feat(syntax)!:") and from changed paths (syntax, plugin, testing, ci/cd, documentation). Fork-guarded, re-runs on new commits, and leaves existing assignees alone - Bug report / feature request templates asking for a code snippet, the syntax group under the cursor and :PythonSyntaxInfo - PR template with test, docs/changelog and fork-guard checks - Dependabot for GitHub Actions (monthly) Co-Authored-By: Claude Opus 5.5 --- .github/ISSUE_TEMPLATE/bug_report.md | 39 ++++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.md | 23 ++++ .github/dependabot.yml | 9 ++ .github/pull_request_template.md | 32 +++++ .github/workflows/pr-automation.yml | 138 ++++++++++++++++++++++ 6 files changed, 246 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/dependabot.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/pr-automation.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..c02939f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug Report +about: Something highlighted wrong, or not highlighted at all +title: '[BUG] ' +labels: bug +assignees: '' +--- + +## What Happened +What is highlighted wrong (or not at all)? + +## Python Code +The smallest snippet that shows it: + +```python + +``` + +## Expected Highlighting +What you expected instead, e.g. "`str` should be colored as a type". + +## Syntax Group +Put the cursor on the wrong-looking text and run: + +```vim +:echo synIDattr(synID(line('.'), col('.'), 1), 'name') +``` + +Group shown: + +## Screenshot +If it helps, a screenshot of the highlighting. + +## Environment +- **Editor + version:** (first line of `vim --version` / `nvim --version`) +- **OS:** +- **Colorscheme:** +- **Output of `:PythonSyntaxInfo`:** +- **Other Python syntax plugins installed?** (e.g. vim-python/python-syntax, nvim-treesitter highlighting for Python) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..f54acfe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: 🔒 Report a security vulnerability + url: https://github.com/aaronbcarlisle/python-syntax-enhanced/security/advisories/new + about: Please report security issues privately — never in a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..99c1f1d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature Request +about: Suggest new highlighting or an option +title: '[FEATURE] ' +labels: enhancement +assignees: '' +--- + +## Feature Description +What should be highlighted (or configurable) that isn't today? + +## Python Code +A snippet showing the construct: + +```python + +``` + +## Desired Highlighting +How it should look, e.g. which parts should be colored as types, keywords, etc. + +## Python Version / PEP +If this is newer syntax, which Python version or PEP introduced it? diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..38bd62c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +# Keep the GitHub Actions used by the workflows up to date. Monthly, at most +# two open PRs; this repo has no other package ecosystems. +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + open-pull-requests-limit: 2 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e540fc1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,32 @@ +## Summary + + + +## Changes + + + +--- + +## Required Checks + +### Tests +- [ ] This PR does **not** change `syntax/`, `plugin/` or `ftplugin/` (skip below) +- [ ] Added or updated assertions in `tests/test_syntax.vim` (or `tests/test_loading.vim`) that fail without the change +- [ ] `sh tests/run.sh` passes locally + +### Docs +- [ ] This PR does **not** change user-visible behavior or options (skip below) +- [ ] README and `doc/python-syntax-enhanced.txt` updated (options, commands, color table) +- [ ] Changelog entry added in `doc/python-syntax-enhanced.txt` + +### Fork PR Guard (if workflows changed) +- [ ] This PR does **not** modify `.github/workflows/` (skip below) +- [ ] Any PR-write action (labels, comments, assignees) has a fork guard: + ```yaml + if: github.event.pull_request.head.repo.full_name == github.repository + ``` + +### General +- [ ] `git diff --check` passes (no whitespace errors) +- [ ] CI passes on Vim (Linux, macOS, Windows) and Neovim diff --git a/.github/workflows/pr-automation.yml b/.github/workflows/pr-automation.yml new file mode 100644 index 0000000..aea1a98 --- /dev/null +++ b/.github/workflows/pr-automation.yml @@ -0,0 +1,138 @@ +name: PR Automation + +on: + pull_request: + # synchronize: re-label when new commits change which paths are touched + types: [opened, edited, reopened, synchronize] + +jobs: + auto-assign-and-label: + name: Auto-assign and Label PR + # Fork PRs get a read-only token, so these writes would fail + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: write + contents: read + + steps: + - name: Auto-assign PR creator + uses: actions/github-script@v9 + with: + script: | + const pr = context.payload.pull_request; + if (pr.assignees.length > 0) { + console.log('PR already has assignees; leaving them as-is'); + return; + } + try { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + assignees: [pr.user.login], + }); + console.log(`Assigned PR to ${pr.user.login}`); + } catch (error) { + // Bots (e.g. dependabot) cannot be assigned; keep going to labels + console.error(`Failed to auto-assign PR creator: ${error.message}`); + } + + - name: Label from PR title and changed files + uses: actions/github-script@v9 + with: + script: | + const pr = context.payload.pull_request; + const labels = new Set(); + + // Conventional commit prefix, with optional scope and "!": + // "fix: ...", "feat(syntax): ...", "refactor!: ..." + const prefixLabels = { + fix: 'bug', + feat: 'enhancement', + docs: 'documentation', + chore: 'chore', + refactor: 'refactor', + test: 'testing', + style: 'style', + perf: 'performance', + ci: 'ci/cd', + build: 'build', + }; + const prefix = pr.title.toLowerCase().match(/^(\w+)(\([^)]*\))?!?:/); + if (prefix && prefixLabels[prefix[1]]) { + labels.add(prefixLabels[prefix[1]]); + } + + // Paths touched by the PR + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + per_page: 100, + }); + const paths = files.map(f => f.filename); + const touches = test => paths.some(test); + + if (touches(p => p.startsWith('syntax/'))) labels.add('syntax'); + if (touches(p => /^(plugin|ftplugin|autoload)\//.test(p))) labels.add('plugin'); + if (touches(p => p.startsWith('tests/'))) labels.add('testing'); + if (touches(p => p.startsWith('.github/'))) labels.add('ci/cd'); + if (touches(p => p.startsWith('doc/') || p.endsWith('.md'))) labels.add('documentation'); + + if (labels.size === 0) { + console.log('No labels to add'); + return; + } + + // Create any label that doesn't exist yet + const colors = { + 'bug': 'd73a4a', + 'enhancement': 'a2eeef', + 'documentation': '0075ca', + 'chore': 'fef2c0', + 'refactor': 'c5def5', + 'testing': '5319e7', + 'style': 'fbca04', + 'performance': '7057ff', + 'ci/cd': 'e99695', + 'build': 'bfdadc', + 'syntax': '1d76db', + 'plugin': 'd4c5f9', + }; + for (const name of labels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name, + }); + } catch (e) { + if (e.status !== 404) { + console.error(`Error checking label '${name}': ${e.message}`); + continue; + } + try { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name, + color: colors[name] || 'ededed', + }); + console.log(`Created label: ${name}`); + } catch (createError) { + console.error(`Failed to create label '${name}': ${createError.message}`); + } + } + } + + // addLabels only adds, so labels applied by hand are kept + const labelArray = [...labels]; + console.log(`Adding labels: ${labelArray.join(', ')}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + labels: labelArray, + }); From 2c87460ea5d26a7c82470781bc301412014c22cf Mon Sep 17 00:00:00 2001 From: aaronbcarlisle Date: Sat, 26 Sep 2026 05:06:13 -0400 Subject: [PATCH 2/2] ci: reconcile PR labels on each run; skip Dependabot PRs Addresses review feedback on #3: - Labels the workflow manages are now reconciled to the current title and changed paths, so a label from an earlier push or title (e.g. "testing" after tests/ changes are dropped) is removed. Labels outside the managed set are never touched. - Skip Dependabot PRs: they run with a read-only token, so addLabels would fail the job (Dependabot labels its own PRs). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/pr-automation.yml | 56 ++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pr-automation.yml b/.github/workflows/pr-automation.yml index aea1a98..0458c1a 100644 --- a/.github/workflows/pr-automation.yml +++ b/.github/workflows/pr-automation.yml @@ -8,8 +8,11 @@ on: jobs: auto-assign-and-label: name: Auto-assign and Label PR - # Fork PRs get a read-only token, so these writes would fail - if: github.event.pull_request.head.repo.full_name == github.repository + # Fork PRs and Dependabot PRs get a read-only token, so these writes would + # fail (Dependabot applies its own "dependencies" labels anyway) + if: >- + github.event.pull_request.head.repo.full_name == github.repository && + github.actor != 'dependabot[bot]' runs-on: ubuntu-latest permissions: pull-requests: write @@ -81,12 +84,10 @@ jobs: if (touches(p => p.startsWith('.github/'))) labels.add('ci/cd'); if (touches(p => p.startsWith('doc/') || p.endsWith('.md'))) labels.add('documentation'); - if (labels.size === 0) { - console.log('No labels to add'); - return; - } - - // Create any label that doesn't exist yet + // Every label this workflow can apply is owned by it: each run + // reconciles them to the current title and paths (so a label from + // an earlier push or title is removed), while labels outside this + // set (e.g. "wontfix", "good first issue") are never touched. const colors = { 'bug': 'd73a4a', 'enhancement': 'a2eeef', @@ -101,7 +102,38 @@ jobs: 'syntax': '1d76db', 'plugin': 'd4c5f9', }; - for (const name of labels) { + + const { data: current } = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + per_page: 100, + }); + const currentNames = new Set(current.map(l => l.name)); + + const stale = [...currentNames].filter(n => n in colors && !labels.has(n)); + for (const name of stale) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + name, + }); + console.log(`Removed stale label: ${name}`); + } catch (e) { + console.error(`Failed to remove label '${name}': ${e.message}`); + } + } + + const missing = [...labels].filter(n => !currentNames.has(n)); + if (missing.length === 0) { + console.log('Labels already up to date'); + return; + } + + // Create any label that doesn't exist in the repo yet + for (const name of missing) { try { await github.rest.issues.getLabel({ owner: context.repo.owner, @@ -127,12 +159,10 @@ jobs: } } - // addLabels only adds, so labels applied by hand are kept - const labelArray = [...labels]; - console.log(`Adding labels: ${labelArray.join(', ')}`); + console.log(`Adding labels: ${missing.join(', ')}`); await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: pr.number, - labels: labelArray, + labels: missing, });