|
1 | 1 | --- |
2 | 2 | name: pre-push-test-gate |
3 | 3 | confidence: high |
| 4 | +source: .github/hooks/pre-push |
4 | 5 | description: > |
5 | | - Enforces build cleanliness and test passage before any git push. |
6 | | - Delegates to the build-repair prompt (.github/prompts/build-repair.prompt.md) |
7 | | - as the authoritative gate. Established after the Shared project test batch |
8 | | - (04714a4) shipped two broken tests directly to main. |
| 6 | + Knowledge skill documenting the 5-gate pre-push hook that enforces build |
| 7 | + cleanliness and full test passage before any git push. The hook is installed |
| 8 | + at .git/hooks/pre-push and mirrors CI gates locally. |
9 | 9 | --- |
10 | 10 |
|
11 | 11 | ## Pre-Push Test Gate |
12 | 12 |
|
13 | | -### Why This Exists |
| 13 | +### Overview |
14 | 14 |
|
15 | | -On 2026-02-25, two unit tests were pushed directly to `main` without local verification. |
16 | | -Both tests had wrong expectations and failed in CI. This skill enforces the gate that |
17 | | -prevents that from recurring. |
| 15 | +The pre-push hook (`.github/hooks/pre-push`) enforces **5 gates** that mirror CI. It runs automatically on every `git push` and blocks the push if any gate fails. |
18 | 16 |
|
19 | | -### The Gate |
| 17 | +> 📋 **For the step-by-step execution playbook, see:** `.squad/playbooks/pre-push-process.md` |
20 | 18 |
|
21 | | -Before any `git push`, an agent MUST run the **Build Repair Skill**: |
| 19 | +### The 5 Gates |
22 | 20 |
|
23 | | -> **`.github/prompts/build-repair.prompt.md`** |
| 21 | +| Gate | Name | What It Does | Blocks If | |
| 22 | +|------|------|-------------|-----------| |
| 23 | +| **0** | Branch protection | Checks current branch | Push is to `main` or `dev` | |
| 24 | +| **1** | Untracked source files | Scans for untracked `.razor`/`.cs` files | Untracked source files found (prompts y/N) | |
| 25 | +| **2** | Release build | `dotnet build IssueTrackerApp.slnx --configuration Release` | Build fails (3 retries) | |
| 26 | +| **3** | Unit/Arch/bUnit tests | Runs 6 test projects in Release mode | Any test project fails (3 retries) | |
| 27 | +| **4** | Integration tests | Runs 4 integration test projects (Docker required) | Any test project fails (3 retries) | |
24 | 28 |
|
25 | | -That prompt already defines the full gate: |
26 | | -1. Restore dependencies (`dotnet restore`) |
27 | | -2. Build the solution (`dotnet build --no-restore`) — zero errors, zero warnings |
28 | | -3. Fix any build errors before continuing |
29 | | -4. Run unit tests — all must pass |
30 | | -5. Fix test failures before continuing |
| 29 | +### Gate 3 — Unit Test Projects (6 total) |
31 | 30 |
|
32 | | -Only push when the build-repair prompt reports **"Build succeeded"** with **zero warnings** |
33 | | -and **all tests pass**. |
| 31 | +``` |
| 32 | +tests/Architecture.Tests/Architecture.Tests.csproj |
| 33 | +tests/Domain.Tests/Domain.Tests.csproj |
| 34 | +tests/Web.Tests.Bunit/Web.Tests.Bunit.csproj |
| 35 | +tests/Persistence.MongoDb.Tests/Persistence.MongoDb.Tests.csproj |
| 36 | +tests/Web.Tests/Web.Tests.csproj |
| 37 | +tests/Persistence.AzureStorage.Tests/Persistence.AzureStorage.Tests.csproj |
| 38 | +``` |
34 | 39 |
|
35 | | -### Agent Checklist |
| 40 | +### Gate 4 — Integration Test Projects (4 total, Docker required) |
36 | 41 |
|
37 | | -Before any `git push`, an agent MUST: |
| 42 | +``` |
| 43 | +tests/Persistence.MongoDb.Tests.Integration/Persistence.MongoDb.Tests.Integration.csproj |
| 44 | +tests/Web.Tests.Integration/Web.Tests.Integration.csproj |
| 45 | +tests/Persistence.AzureStorage.Tests.Integration/Persistence.AzureStorage.Tests.Integration.csproj |
| 46 | +tests/AppHost.Tests/AppHost.Tests.csproj |
| 47 | +``` |
38 | 48 |
|
39 | | -- [ ] Open `.github/prompts/build-repair.prompt.md` and execute it fully |
40 | | -- [ ] Confirm final output: `Build succeeded. 0 Warning(s). 0 Error(s).` |
41 | | -- [ ] Confirm final test output: `Passed! Failed: 0` |
42 | | -- [ ] Only then execute `git push` |
| 49 | +These use Testcontainers (mongo:7.0, Azurite) and Aspire DCP. Docker daemon MUST be running. |
43 | 50 |
|
44 | | -Do NOT push if either check reports failures. Fix first. |
| 51 | +### Retry Behavior |
45 | 52 |
|
46 | | -### Hook (Local Enforcement) |
| 53 | +Gates 2, 3, and 4 allow **3 attempts**. Between attempts the hook pauses and prompts: |
| 54 | +> "Fix the errors and press Enter to retry, or Ctrl+C to abort" |
47 | 55 |
|
48 | | -The `.git/hooks/pre-push` hook runs unit tests as a local tripwire. |
49 | | -Install once per clone — **Shell (Linux/macOS/Git Bash)**: |
| 56 | +### Hook Installation |
| 57 | + |
| 58 | +The hook source is committed at `.github/hooks/pre-push`. Install once per clone: |
50 | 59 |
|
51 | 60 | ```bash |
52 | | -cat > .git/hooks/pre-push << 'EOF' |
53 | | -#!/usr/bin/env bash |
54 | | -set -euo pipefail |
55 | | -echo "🔎 pre-push: running build-repair gate (Domain.Tests + Web.Tests)…" |
56 | | -if dotnet test tests/Domain.Tests tests/Web.Tests --configuration Release --verbosity quiet 2>&1; then |
57 | | - echo "✅ Gate passed — push allowed." |
58 | | -else |
59 | | - echo "❌ Gate FAILED. Run .github/prompts/build-repair.prompt.md and fix before pushing." |
60 | | - exit 1 |
61 | | -fi |
62 | | -EOF |
| 61 | +cp .github/hooks/pre-push .git/hooks/pre-push |
63 | 62 | chmod +x .git/hooks/pre-push |
64 | 63 | ``` |
65 | 64 |
|
66 | | -**PowerShell (Windows):** |
67 | | -```powershell |
68 | | -@' |
69 | | -#!/usr/bin/env bash |
70 | | -set -euo pipefail |
71 | | -echo "🔎 pre-push: running build-repair gate (Domain.Tests + Web.Tests)…" |
72 | | -if dotnet test tests/Domain.Tests tests/Web.Tests --configuration Release --verbosity quiet 2>&1; then |
73 | | - echo "✅ Gate passed — push allowed." |
74 | | -else |
75 | | - echo "❌ Gate FAILED. Run .github/prompts/build-repair.prompt.md and fix before pushing." |
76 | | - exit 1 |
77 | | -fi |
78 | | -'@ | Set-Content -NoNewline .git/hooks/pre-push |
79 | | -``` |
80 | | - |
81 | | -> The hook is not committed — install on every fresh clone. The build-repair prompt |
82 | | -> is the authoritative process; the hook is a fast local tripwire. |
| 65 | +> ⚠️ Do NOT create inline hook scripts. Always copy from `.github/hooks/pre-push` to get the full 5-gate version. |
83 | 66 |
|
84 | | -### Failure Taxonomy (known patterns) |
| 67 | +### Failure Taxonomy (Known Patterns) |
85 | 68 |
|
86 | 69 | | Symptom | Root Cause | Fix | |
87 | 70 | |---------|-----------|-----| |
88 | | -| `DateTime` equality failure in `*.Empty` tests | `Empty` property calls `DateTime.UtcNow` each time — two calls produce different values | Assert individual fields, not whole-record equality | |
89 | | -| Unexpected trailing `_` in slug tests | `GenerateSlug` appends `_` when string ends with punctuation AND has internal punctuation | Verify actual output against implementation before asserting | |
90 | | -| Record equality fails on nested DTO | Nested DTO `Empty` also uses `UtcNow` — same root cause | Flatten assertions to field-level | |
| 71 | +| Warning treated as error (Gate 2) | `TreatWarningsAsErrors=true` in Directory.Build.props | Fix the warning — do not suppress | |
| 72 | +| Architecture test failure (Gate 3) | Naming convention violation | Commands → `Command`, queries → `Query`, handlers → `Handler` (must be `sealed`), validators → `Validator` | |
| 73 | +| bUnit test failure (Gate 3) | API change in bUnit 2.x | Use `Render<T>()` not `RenderComponent<T>()` | |
| 74 | +| `DateTime` equality failure (Gate 3) | `Empty` property calls `DateTime.UtcNow` each time | Assert individual fields, not whole-record equality | |
| 75 | +| Docker not running (Gate 4) | Testcontainers can't start | `sudo systemctl start docker` or start Docker Desktop | |
| 76 | +| Container timeout (Gate 4) | Slow image pull or low resources | Pre-pull `mongo:7.0`; increase Docker memory | |
| 77 | +| Untracked `.razor`/`.cs` (Gate 1) | New files not staged | `git add <files>` before pushing | |
| 78 | + |
| 79 | +### Related Documents |
| 80 | + |
| 81 | +- **Hook source:** `.github/hooks/pre-push` |
| 82 | +- **Execution playbook:** `.squad/playbooks/pre-push-process.md` |
| 83 | +- **Build repair prompt:** `.github/prompts/build-repair.prompt.md` |
| 84 | +- **Contributing guide:** `CONTRIBUTING.md` (Pre-Push Gates section) |
| 85 | +- **Ceremonies:** `.squad/ceremonies.md` (Build Repair Check) |
0 commit comments