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..5464da0 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,14 +324,33 @@ 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 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' }, - { 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', 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', @@ -338,7 +362,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.`, '', '', ]), @@ -347,6 +371,7 @@ jobs: const body = [ marker, + ...greeting, ...failuresSection, '## Test Coverage Report', '', @@ -354,7 +379,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