Skip to content
Merged
Show file tree
Hide file tree
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
22 changes: 22 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Coverage for this package's own Python. Run:
# python -m coverage run -m unittest discover -s tests -p "test_*.py"
# python -m coverage combine
# python -m coverage report
#
# Coverage shows which code the tests execute, not that they would catch a
# defect there; red drills cover that separately.

[run]
branch = true
source = scripts
# Tests run the documented command-line entry points in child processes.
patch = subprocess
parallel = true

[report]
show_missing = true
precision = 1
# Measured 83.1% (Windows) and 83.3% (Linux) on 2026-09-24. The one-point
# margin absorbs branch-analysis differences between supported Pythons. Raise
# this floor as tests improve; never lower it to admit untested code.
fail_under = 82
38 changes: 38 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Proposes updates for everything this repository pins by version or SHA:
# workflow actions, frozen pre-commit hook revisions, and the hash-locked tools
# in requirements-dev.txt (compiled from requirements-dev.in by uv).
#
# The cooldown lets a new release age before it is proposed, so a compromised
# or yanked version is usually caught upstream before it reaches CI. Every
# proposal still has to pass the full cross-platform gate before merging.
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
actions:
patterns: ["*"]

- package-ecosystem: pre-commit
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
hooks:
patterns: ["*"]

- package-ecosystem: uv
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
python-tools:
patterns: ["*"]
102 changes: 88 additions & 14 deletions .github/workflows/cross-platform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,55 @@ name: Cross-platform package checks
permissions:
contents: read

# Supersede an older run for the same pull request; never cancel main or a
# release gate, whose results are recorded as evidence.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint:
name: Repository gates (pre-commit, full-history secrets)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Check out repository with history
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.14"
- name: Install hash-locked tools
run: python -m pip install --disable-pip-version-check --require-hashes -r requirements-dev.txt
- name: Install pinned PSScriptAnalyzer
shell: pwsh
run: |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module -Name PSScriptAnalyzer -RequiredVersion 1.25.0 -Scope CurrentUser -Force
Import-Module -Name PSScriptAnalyzer -RequiredVersion 1.25.0
- name: Run repository pre-commit gates
run: python -m pre_commit run --all-files --show-diff-on-failure
- name: Scan reachable history for secrets
env:
GITLEAKS_VERSION: 8.30.0
# SHA-256 of gitleaks_8.30.0_linux_x64.tar.gz from the upstream release.
GITLEAKS_SHA256: 79a3ab579b53f71efd634f3aaf7e04a0fa0cf206b7ed434638d1547a2470a66e
run: |
set -euo pipefail
archive="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
curl --fail --silent --show-error --location --output "${RUNNER_TEMP}/${archive}" \
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${archive}"
echo "${GITLEAKS_SHA256} ${RUNNER_TEMP}/${archive}" | sha256sum --check --strict
tar -xzf "${RUNNER_TEMP}/${archive}" -C "${RUNNER_TEMP}" gitleaks
"${RUNNER_TEMP}/gitleaks" git --redact --no-banner .

powershell:
name: PowerShell (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
Expand All @@ -23,7 +68,9 @@ jobs:
- macos-latest
steps:
- name: Check out repository
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
- name: Run PowerShell smoke tests
shell: pwsh
run: ./tests/cross-platform-smoke.ps1 -RedDrills
Expand All @@ -34,9 +81,12 @@ jobs:
windows-powershell:
name: Windows PowerShell 5.1
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Check out repository
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
- name: Run Windows PowerShell smoke tests
shell: powershell
run: .\tests\cross-platform-smoke.ps1 -RedDrills
Expand All @@ -47,32 +97,38 @@ jobs:
python-quality:
name: Python ${{ matrix.python }} (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
python: ["3.12", "3.14"]
steps:
- name: Check out repository
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: ${{ matrix.python }}
- name: Install pinned quality tools
run: python -m pip install --disable-pip-version-check ruff==0.16.4 mypy==2.3.1
- name: Check Python scripts
- name: Install hash-locked tools
run: python -m pip install --disable-pip-version-check --require-hashes -r requirements-dev.txt
- name: Type-check with the shipped mypy template
run: |
python -m ruff check scripts tests
python -m ruff format --check scripts tests
python -m compileall -q scripts tests
python -m mypy --strict --python-version 3.12 scripts/delivery scripts/verify-delivery.py scripts/update-delivery.py
- name: Run native delivery tests and validator red drills
run: python -m unittest discover -s tests -p 'test_*.py'
python -m mypy --config-file templates/python/mypy.ini --python-version 3.12 scripts/delivery scripts/verify-delivery.py scripts/update-delivery.py scripts/verify-format-safe.py
python -m mypy --config-file templates/python/mypy.ini --python-version 3.12 --explicit-package-bases tests
- name: Run tests, validator red drills, and the coverage floor
run: |
python -m coverage run -m unittest discover -s tests -p "test_*.py"
python -m coverage combine
python -m coverage report

posix:
name: Bash and PowerShell parity (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
Expand All @@ -81,7 +137,9 @@ jobs:
- macos-latest
steps:
- name: Check out repository
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
- name: Compare native scanner results
shell: bash
run: |
Expand All @@ -93,8 +151,15 @@ jobs:
bash scripts/detect-stack.sh . > "$scratch/bash.json"
pwsh -NoLogo -NoProfile -File scripts/detect-stack.ps1 . > "$scratch/powershell.json"

python - "$scratch/bash.json" "$scratch/powershell.json" <<'PY'
# A POSIX directory name may contain characters JSON must escape.
awkward="$scratch/"$'quote"back\\slash\ttab'
mkdir -p "$awkward/src"
touch "$awkward/src/App.PY"
bash scripts/detect-stack.sh "$awkward" > "$scratch/awkward.json"

python - "$scratch/bash.json" "$scratch/powershell.json" "$scratch/awkward.json" "$awkward" <<'PY'
import json
import os
import sys

with open(sys.argv[1], encoding="utf-8") as bash_file:
Expand All @@ -106,9 +171,18 @@ jobs:
"git",
"languages",
"existing_config",
"lockfiles",
"toolchain_pins",
"tools_installed",
"python_runtime",
):
if bash_scan[section] != powershell_scan[section]:
raise SystemExit(f"scanner mismatch in {section}")

with open(sys.argv[3], encoding="utf-8") as awkward_file:
awkward_scan = json.load(awkward_file)
if os.path.realpath(awkward_scan["root"]) != os.path.realpath(sys.argv[4]):
raise SystemExit("scanner root was not escaped as the exact directory name")
if awkward_scan["languages"]["python"] != 1:
raise SystemExit("uppercase extension was not counted")
PY
12 changes: 9 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,34 @@ jobs:
name: Reuse cross-platform quality gates
permissions:
contents: read
uses: ./.github/workflows/cross-platform.yml
# The self-repository `$/` form is not adopted until a tagged release run
# has exercised it; this path cannot be tested without publishing.
uses: ./.github/workflows/cross-platform.yml # zizmor: ignore[self-repository]

publish:
name: Build, attest, and publish
needs: quality
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
attestations: write
contents: write
id-token: write
steps:
- name: Check out tagged source
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
# The build reads the local tag; publication authenticates with
# GH_TOKEN only in its own step, so no credential stays in .git.
persist-credentials: false
- name: Build release artifacts
shell: pwsh
env:
RELEASE_TAG: ${{ github.ref_name }}
run: ./scripts/build-release.ps1 -OutputDirectory dist -Version $env:RELEASE_TAG -RequireTag
- name: Attest release archives
uses: actions/attest@v4
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: dist/high-quality-projects-skill-${{ github.ref_name }}.*
- name: Publish GitHub Release
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ venv/
.env
.env.local
coverage/
.coverage
.coverage.*
.pytest_cache/
.mypy_cache/
.ruff_cache/
Expand Down
113 changes: 113 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# This repository's own gates: the shipped template's hooks that apply to a
# Python, PowerShell, shell, and GitHub Actions package. CI runs the same file.
#
# python -m pip install --require-hashes -r requirements-dev.txt
# pre-commit install
# pre-commit run --all-files
#
# Revisions are frozen to commit SHAs. Dependabot proposes updates; locally,
# `pre-commit autoupdate --freeze` does the same. Local hooks run tools from
# the active environment, so install requirements-dev.txt first.

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-symlinks
- id: destroyed-symlinks
# .gitattributes owns line endings; PowerShell stays CRLF by policy.
- id: mixed-line-ending
args: [--fix=no]
- id: check-added-large-files
args: [--maxkb=1024]
- id: check-json
exclude: ^templates/typescript/tsconfig\.strict\.json$
- id: check-yaml
- id: check-toml
- id: check-xml
files: \.props$
types: [text]
- id: detect-private-key

# Staged changes only. CI scans full history with `gitleaks git` separately.
- repo: https://github.com/gitleaks/gitleaks
rev: 6eaad039603a4de39fddd1cf5f727391efe9974e # frozen: v8.30.0
hooks:
- id: gitleaks

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: aab412d509121cb5f7533134b7e67f9fab59c682 # frozen: v0.16.4
hooks:
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format

- repo: https://github.com/jendrikseipp/vulture
rev: b0f67ba0044693aa9ec0d38fe460590facc98004 # frozen: v2.16
hooks:
- id: vulture
args: [scripts, tests]

# B404 flags every `import subprocess`; each call site is still checked by
# B603/B607 and carries its own reviewed exception.
- repo: https://github.com/PyCQA/bandit
rev: 92ae8b82fb422a639f0ed8d99e96cea769594e08 # frozen: 1.9.4
hooks:
- id: bandit
args: [-q, -s, B404]

- repo: https://github.com/shellcheck-py/shellcheck-py
rev: 745eface02aef23e168a8afb6b5737818efbea95 # frozen: v0.11.0.1
hooks:
- id: shellcheck
args: [--severity=style, --enable=all]

- repo: https://github.com/scop/pre-commit-shfmt
rev: 05c1426671b9237fb5e1444dd63aa5731bec0dfb # frozen: v3.13.1-1
hooks:
- id: shfmt
args: [-i, '4', -ci, -sr, -d]

- repo: https://github.com/rhysd/actionlint
rev: 914e7df21a07ef503a81201c76d2b11c789d3fca # frozen: v1.7.12
hooks:
- id: actionlint

- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: fa412071e4f5d44d44f9e365f4676f9df92456a2 # frozen: v1.30.1
hooks:
- id: zizmor
args: [--no-progress, --offline]

- repo: local
hooks:
# Entry scripts import `delivery`; tests import `scripts.delivery`. The two
# module layouts need separate mypy runs, both with the shipped template.
- id: mypy-scripts
name: mypy (package scripts, shipped template)
entry: python -m mypy --config-file templates/python/mypy.ini --python-version 3.12
scripts/delivery scripts/verify-delivery.py scripts/update-delivery.py
scripts/verify-format-safe.py
language: system
pass_filenames: false
files: ^(scripts/.*\.py|templates/python/mypy\.ini)$

- id: mypy-tests
name: mypy (tests, shipped template)
entry: python -m mypy --config-file templates/python/mypy.ini --python-version 3.12
--explicit-package-bases tests
language: system
pass_filenames: false
files: ^(scripts/.*\.py|tests/.*\.py|templates/python/mypy\.ini)$

- id: psscriptanalyzer
name: PSScriptAnalyzer (shipped template)
entry: pwsh -NoLogo -NoProfile -NonInteractive -File tests/powershell-analysis.ps1
language: system
files: \.(ps1|psm1|psd1)$
Loading
Loading