From 501267f209c1c2c69598cf36fe243f8f78c58d48 Mon Sep 17 00:00:00 2001 From: Brylie Christopher Oxley Date: Sun, 28 Jun 2026 16:38:54 +0300 Subject: [PATCH 1/2] Document mise setup and link CI failures to CONTRIBUTING.md Two prek hooks (actionlint, markdownlint-cli2) run through `mise exec --` but neither the dev container nor the manual setup docs installed mise, so following CONTRIBUTING.md as written would leave those two hooks failing with "command not found". Add the mise devcontainer feature plus `mise install` to postCreateCommand, and document the manual install step. Also link each CI failure (prek/mypy/pyrefly/pytest/coverage) in the job summary and sticky PR comment to the relevant CONTRIBUTING.md section, so contributors land on the exact instructions for the check that actually failed instead of generic "see CI" feedback. --- .devcontainer/devcontainer.json | 5 +++-- .github/workflows/ci.yaml | 26 ++++++++++++++++---------- CONTRIBUTING.md | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e469024..42f068a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,9 +5,10 @@ }, "features": { "ghcr.io/va-h/devcontainers-features/uv:1": {}, - "ghcr.io/prulloac/devcontainer-features/ollama:1": {} + "ghcr.io/prulloac/devcontainer-features/ollama:1": {}, + "ghcr.io/devcontainers-extra/features/mise:1": {} }, - "postCreateCommand": "uv sync --dev && uv run prek install", + "postCreateCommand": "uv sync --dev && mise install && uv run prek install", "customizations": { "vscode": { "extensions": [ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0c0bee3..ec273e2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -255,13 +255,15 @@ jobs: echo "| coverage (≥80%) | ${{ needs.test.outputs.coverage_outcome == 'success' && '✅ Passed' || '❌ Failed' }} (${{ needs.test.outputs.coverage_pct }}%) |" echo + contributing_url="https://github.com/${{ github.repository }}/blob/main/CONTRIBUTING.md" + if [ "${{ needs.lint.outputs.prek_outcome }}" != "success" ]; then echo "
prek output" echo echo '```' echo "$PREK_OUTPUT" echo '```' - echo "Run \`uv run prek run --all-files\` locally to reproduce and fix." + echo "Run \`uv run prek run --all-files\` locally to reproduce and fix. See [Pre-commit Hooks (prek)]($contributing_url#pre-commit-hooks-prek) in CONTRIBUTING.md." echo "
" fi if [ "${{ needs.typecheck.outputs.mypy_outcome }}" != "success" ]; then @@ -270,7 +272,7 @@ jobs: echo '```' echo "$MYPY_OUTPUT" echo '```' - echo "Run \`uv run mypy tapio\` locally to reproduce and fix." + echo "Run \`uv run mypy tapio\` locally to reproduce and fix. See [Type Checking]($contributing_url#type-checking) in CONTRIBUTING.md." echo "" fi if [ "${{ needs.typecheck.outputs.pyrefly_outcome }}" != "success" ]; then @@ -279,7 +281,7 @@ jobs: echo '```' echo "$PYREFLY_OUTPUT" echo '```' - echo "Run \`uv run pyrefly check\` locally to reproduce and fix." + echo "Run \`uv run pyrefly check\` locally to reproduce and fix. See [Type Checking]($contributing_url#type-checking) in CONTRIBUTING.md." echo "" fi if [ "${{ needs.test.outputs.pytest_outcome }}" != "success" ]; then @@ -288,9 +290,12 @@ jobs: echo '```' echo "$PYTEST_OUTPUT" echo '```' - echo "Run \`uv run pytest\` locally to reproduce and fix." + echo "Run \`uv run pytest\` locally to reproduce and fix. See [Running Tests]($contributing_url#running-tests) in CONTRIBUTING.md." echo "" fi + if [ "${{ needs.test.outputs.coverage_outcome }}" != "success" ]; then + echo "See [Code Coverage]($contributing_url#code-coverage) in CONTRIBUTING.md for how to check coverage locally." + fi } >> "$GITHUB_STEP_SUMMARY" # Posts a sticky comment directly in the PR conversation (not just the Summary @@ -319,12 +324,13 @@ jobs: const coveragePct = '${{ needs.test.outputs.coverage_pct }}' || 'unknown'; const passed = '${{ needs.test.outputs.coverage_outcome }}' === 'success'; const marker = ''; + const contributingUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/CONTRIBUTING.md`; const failures = [ - { outcome: '${{ needs.lint.outputs.prek_outcome }}', name: 'prek', output: process.env.PREK_OUTPUT, fix: 'uv run prek run --all-files' }, - { outcome: '${{ needs.typecheck.outputs.mypy_outcome }}', name: 'mypy', output: process.env.MYPY_OUTPUT, fix: 'uv run mypy tapio' }, - { outcome: '${{ needs.typecheck.outputs.pyrefly_outcome }}', name: 'pyrefly', output: process.env.PYREFLY_OUTPUT, fix: 'uv run pyrefly check' }, - { outcome: '${{ needs.test.outputs.pytest_outcome }}', name: 'pytest', output: process.env.PYTEST_OUTPUT, fix: 'uv run pytest' }, + { outcome: '${{ needs.lint.outputs.prek_outcome }}', name: 'prek', output: process.env.PREK_OUTPUT, fix: 'uv run prek run --all-files', docs: `${contributingUrl}#pre-commit-hooks-prek`, docsLabel: 'Pre-commit Hooks (prek)' }, + { outcome: '${{ needs.typecheck.outputs.mypy_outcome }}', name: 'mypy', output: process.env.MYPY_OUTPUT, fix: 'uv run mypy tapio', docs: `${contributingUrl}#type-checking`, docsLabel: 'Type Checking' }, + { outcome: '${{ needs.typecheck.outputs.pyrefly_outcome }}', name: 'pyrefly', output: process.env.PYREFLY_OUTPUT, fix: 'uv run pyrefly check', docs: `${contributingUrl}#type-checking`, docsLabel: 'Type Checking' }, + { outcome: '${{ needs.test.outputs.pytest_outcome }}', name: 'pytest', output: process.env.PYTEST_OUTPUT, fix: 'uv run pytest', docs: `${contributingUrl}#running-tests`, docsLabel: 'Running Tests' }, ].filter((check) => check.outcome !== 'success'); const failuresSection = failures.length @@ -338,7 +344,7 @@ jobs: check.output || '(no output captured)', '```', '', - `Run \`${check.fix}\` locally to reproduce and fix.`, + `Run \`${check.fix}\` locally to reproduce and fix. See [${check.docsLabel}](${check.docs}) in CONTRIBUTING.md.`, '', '', ]), @@ -354,7 +360,7 @@ jobs: '', passed ? 'Coverage meets the project threshold. 🎉' - : '⚠️ Coverage is below the required 80% threshold. Please add tests for the new or changed code before this pull request can be merged.', + : `⚠️ Coverage is below the required 80% threshold. Please add tests for the new or changed code before this pull request can be merged. See [Code Coverage](${contributingUrl}#code-coverage) in CONTRIBUTING.md.`, ].join('\n'); // paginate() walks every page of comments, not just the first 30, so a diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 880e861..756322e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,8 +121,9 @@ The dev container includes: - Python 3.14 - `uv` package manager - Ollama for local LLM inference +- [`mise`](https://mise.jdx.dev/), which manages `actionlint` and `markdownlint-cli2` (used by two of the prek hooks below) - All required VS Code extensions (Python, Ruff, GitHub Copilot, etc.) -- Automatic dependency installation via `uv sync --dev` +- Automatic dependency installation (`uv sync --dev`) and tool installation (`mise install`) ### Using GitHub Codespaces (Cloud Alternative) @@ -150,9 +151,9 @@ For a completely cloud-based development environment that requires no local setu The Codespace includes the same development environment as the local dev container: -- Python 3.14, `uv` package manager, and Ollama +- Python 3.14, `uv` package manager, Ollama, and `mise` - All required VS Code extensions pre-installed -- Automatic dependency installation +- Automatic dependency and tool installation ### Manual Setup (Alternative) @@ -182,6 +183,13 @@ uv sync --dev 1. Install Ollama for local LLM inference: - Follow the installation instructions at [ollama.ai](https://ollama.ai) +1. Install [`mise`](https://mise.jdx.dev/), which manages the versions of `actionlint` and `markdownlint-cli2` used by two of the prek hooks below (without it, those two hooks fail with "command not found"): + +```bash +curl https://mise.run | sh +mise install # installs the tool versions pinned in mise.toml +``` + ### Installing Required Models Regardless of which setup method you chose, you'll need to install `llama3.2`, the base model this project uses for text generation: @@ -252,7 +260,7 @@ To run all hooks against the full codebase (useful before submitting a pull requ uv run prek run --all-files ``` -These are the same checks enforced in CI (excluding mypy and Pyrefly, which CI runs as separate steps against the project's own virtual environment). +These are the same checks enforced in CI (excluding mypy and Pyrefly, which CI runs as separate steps against the project's own virtual environment). Two of the hooks (`actionlint`, `markdownlint-cli2`) run through `mise exec --` and require [`mise`](https://mise.jdx.dev/) to be installed and have run `mise install` once — see [Manual Setup](#manual-setup-alternative) if you're missing it. ## Testing Guidelines From e67f3de44028dd2426454d7ea777d859a9e2e4d6 Mon Sep 17 00:00:00 2001 From: Brylie Christopher Oxley Date: Sun, 28 Jun 2026 16:47:20 +0300 Subject: [PATCH 2/2] @-mention the PR author in the CI failure comment When any check fails, the sticky comment now opens with a polite, @-mentioned summary of which stages need attention (e.g. "Lint (prek), Tests (pytest)") before pointing to the per-check details and CONTRIBUTING.md links already below it. Since the comment is updated in place on reruns rather than recreated, the mention only notifies once per PR. --- .github/workflows/ci.yaml | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ec273e2..5464da0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -325,14 +325,32 @@ jobs: const passed = '${{ needs.test.outputs.coverage_outcome }}' === 'success'; const marker = ''; const contributingUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/CONTRIBUTING.md`; + const author = context.payload.pull_request.user.login; const failures = [ - { outcome: '${{ needs.lint.outputs.prek_outcome }}', name: 'prek', output: process.env.PREK_OUTPUT, fix: 'uv run prek run --all-files', docs: `${contributingUrl}#pre-commit-hooks-prek`, docsLabel: 'Pre-commit Hooks (prek)' }, - { outcome: '${{ needs.typecheck.outputs.mypy_outcome }}', name: 'mypy', output: process.env.MYPY_OUTPUT, fix: 'uv run mypy tapio', docs: `${contributingUrl}#type-checking`, docsLabel: 'Type Checking' }, - { outcome: '${{ needs.typecheck.outputs.pyrefly_outcome }}', name: 'pyrefly', output: process.env.PYREFLY_OUTPUT, fix: 'uv run pyrefly check', docs: `${contributingUrl}#type-checking`, docsLabel: 'Type Checking' }, - { outcome: '${{ needs.test.outputs.pytest_outcome }}', name: 'pytest', output: process.env.PYTEST_OUTPUT, fix: 'uv run pytest', docs: `${contributingUrl}#running-tests`, docsLabel: 'Running Tests' }, + { outcome: '${{ needs.lint.outputs.prek_outcome }}', name: 'prek', label: 'Lint (prek)', output: process.env.PREK_OUTPUT, fix: 'uv run prek run --all-files', docs: `${contributingUrl}#pre-commit-hooks-prek`, docsLabel: 'Pre-commit Hooks (prek)' }, + { outcome: '${{ needs.typecheck.outputs.mypy_outcome }}', name: 'mypy', label: 'Type Checking (mypy)', output: process.env.MYPY_OUTPUT, fix: 'uv run mypy tapio', docs: `${contributingUrl}#type-checking`, docsLabel: 'Type Checking' }, + { outcome: '${{ needs.typecheck.outputs.pyrefly_outcome }}', name: 'pyrefly', label: 'Type Checking (pyrefly)', output: process.env.PYREFLY_OUTPUT, fix: 'uv run pyrefly check', docs: `${contributingUrl}#type-checking`, docsLabel: 'Type Checking' }, + { outcome: '${{ needs.test.outputs.pytest_outcome }}', name: 'pytest', label: 'Tests (pytest)', output: process.env.PYTEST_OUTPUT, fix: 'uv run pytest', docs: `${contributingUrl}#running-tests`, docsLabel: 'Running Tests' }, ].filter((check) => check.outcome !== 'success'); + const failedLabels = failures.map((check) => check.label); + if (!passed) { + failedLabels.push('Coverage'); + } + + // Mentioning the author only happens once per PR in practice: GitHub notifies + // on comment creation, and this step updates the same sticky comment on + // reruns rather than creating a new one, so re-running CI doesn't re-ping them. + const greeting = failedLabels.length + ? [ + `👋 Hi @${author}, thanks for the pull request! Before this can be merged, please address: **${failedLabels.join(', ')}**.`, + '', + 'Details and local reproduction commands are below, with links to the relevant CONTRIBUTING.md section for each.', + '', + ] + : []; + const failuresSection = failures.length ? [ '## CI Failures', @@ -353,6 +371,7 @@ jobs: const body = [ marker, + ...greeting, ...failuresSection, '## Test Coverage Report', '',