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
5 changes: 3 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
45 changes: 35 additions & 10 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<details><summary>prek output</summary>"
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 "</details>"
fi
if [ "${{ needs.typecheck.outputs.mypy_outcome }}" != "success" ]; then
Expand All @@ -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 "</details>"
fi
if [ "${{ needs.typecheck.outputs.pyrefly_outcome }}" != "success" ]; then
Expand All @@ -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 "</details>"
fi
if [ "${{ needs.test.outputs.pytest_outcome }}" != "success" ]; then
Expand All @@ -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 "</details>"
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
Expand Down Expand Up @@ -319,14 +324,33 @@ jobs:
const coveragePct = '${{ needs.test.outputs.coverage_pct }}' || 'unknown';
const passed = '${{ needs.test.outputs.coverage_outcome }}' === 'success';
const marker = '<!-- tapio-coverage-report -->';
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',
Expand All @@ -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.`,
'</details>',
'',
]),
Expand All @@ -347,14 +371,15 @@ jobs:

const body = [
marker,
...greeting,
...failuresSection,
'## Test Coverage Report',
'',
`${passed ? '✅' : '❌'} **${coveragePct}%** coverage (required: ≥80%)`,
'',
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
Expand Down
16 changes: 12 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down
Loading