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
35 changes: 31 additions & 4 deletions .github/hooks/pre-push
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Pre-push gate: mirrors CI checks to catch failures before they reach GitHub
# Runs: branch protection → untracked-file check → Release build → unit/architecture tests → integration tests
# Runs: branch protection → untracked-file check → dotnet format → Release build → unit/architecture tests → integration tests
# NOTE: git provides refspecs on stdin; interactive prompts must use /dev/tty.
set -uo pipefail

Expand Down Expand Up @@ -49,7 +49,34 @@ if [[ -n "$UNTRACKED_SRC" ]]; then
fi
fi

# ── Gate 2: Release build (mirrors CI exactly) ─────────────────────────────
# ── Gate 2: dotnet format check ────────────────────────────────────────────
echo -e "\n${CYAN}🎨 Checking code formatting (dotnet format --verify-no-changes)...${RESET}"
dotnet format MyBlog.slnx --verify-no-changes 2>&1
FORMAT_EXIT=$?

if [[ $FORMAT_EXIT -ne 0 ]]; then
echo -e "${RED}❌ Formatting issues detected — one or more files require formatting changes.${RESET}"
echo -e "${YELLOW} Fix: dotnet format MyBlog.slnx${RESET}"
echo -e "${YELLOW} Then: git add -u && git commit (or --amend), then re-push.${RESET}"
echo ""
printf "Auto-fix formatting now? Modified files must be staged and committed before re-pushing. [y/N] " >/dev/tty
read -r FORMAT_FIX </dev/tty
if [[ "$FORMAT_FIX" == "y" || "$FORMAT_FIX" == "Y" ]]; then
echo -e "${CYAN} 🔧 Running dotnet format MyBlog.slnx...${RESET}"
dotnet format MyBlog.slnx
if [[ $? -ne 0 ]]; then
echo -e "${RED}❌ dotnet format encountered errors. Fix manually and re-push.${RESET}"
exit 1
fi
echo -e "${YELLOW} ⚠️ Formatting applied to working tree. Stage changes: git add -u${RESET}"
echo -e "${YELLOW} Then commit and re-push.${RESET}"
fi
echo -e "${RED}Push blocked: commit the formatting changes and re-push.${RESET}"
exit 1
fi
echo -e "${GREEN}✅ Formatting OK.${RESET}"

# ── Gate 3: Release build (mirrors CI exactly) ─────────────────────────────
MAX_ATTEMPTS=3
BUILD_ATTEMPT=0
BUILD_OK=false
Expand Down Expand Up @@ -85,7 +112,7 @@ if [[ "$BUILD_OK" != true ]]; then
exit 1
fi

# ── Gate 3: Unit + Architecture tests ──────────────────────────────────────
# ── Gate 4: Unit + Architecture tests ──────────────────────────────────────
TEST_PROJECTS=(
"tests/Architecture.Tests/Architecture.Tests.csproj"
"tests/Domain.Tests/Domain.Tests.csproj"
Expand Down Expand Up @@ -121,7 +148,7 @@ if [[ "$TESTS_OK" != true ]]; then
exit 1
fi

# ── Gate 4: Docker-backed integration tests ────────────────────────────────
# ── Gate 5: Docker-backed integration tests ────────────────────────────────
# Requires Docker daemon for Testcontainers-backed dependencies.
INTEGRATION_PROJECTS=(
"tests/Web.Tests.Integration/Web.Tests.Integration.csproj"
Expand Down
68 changes: 68 additions & 0 deletions .squad/agents/boromir/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,30 @@

## Learnings

### 2026-05-11 — Issue #289: dotnet format gate added to pre-push hook

**What was done:** Added Gate 2 (`dotnet format --verify-no-changes`) to the pre-push hook between Gate 1 (untracked files) and the former Gate 2 (now Gate 3 — Release build). Gates 2–4 (build, unit tests, integration) renumbered to 3–5.

**Key decisions:**

- Gate uses `--verify-no-changes` (check mode, not mutating) so it always blocks on dirty formatting
- On failure, hook offers interactive auto-fix (y/N via `/dev/tty`) — same pattern as Gate 1
- If auto-fix is chosen, files are formatted in working tree but push is still blocked; user must stage, commit, and re-push (correct behavior — staged changes belong in a commit)
- `dotnet format` exits with code **2** (not 1) when files would be changed; the hook checks `$FORMAT_EXIT -ne 0` which covers both non-zero codes

**Files changed:**

- `.github/hooks/pre-push` — added Gate 2, renumbered 2→3, 3→4, 4→5
- `scripts/install-hooks.sh` — updated gate count (5→6) and summary list
- `.squad/playbooks/pre-push-process.md` — updated pre-flight checklist, gate table, troubleshooting, and anti-patterns
- `.squad/skills/pre-push-test-gate/SKILL.md` — updated gate summary

**Validation:** Confirmed `dotnet format MyBlog.slnx --verify-no-changes` exits 2 when repo has formatting issues; exits 0 when clean. Bash syntax validated with `bash -n`.

**Note:** Repo had pre-existing formatting violations (whitespace and import ordering in test files). These are out of scope for this issue and should be tracked separately.

---

### 2026-05-08 — Sprint 18 Release PR #272

**What was done:** Opened release PR #272 to promote `dev` → `main` for Sprint 18 (AppHost
Expand Down Expand Up @@ -1322,3 +1346,47 @@ the runtime theme test can become interactive, toggle light/dark, navigate to

- **Sam:** Implement actual MongoDB collection clearing logic inside the command handler (connect to the mongodb resource endpoint, enumerate collections, drop non-system collections, return per-collection counts)
- **Gimli:** Write automated coverage for #247 AC4: verify (a) command annotation exists on mongodb resource in RunMode, (b) `ConfirmationMessage` is non-null, (c) `UpdateState` returns `Disabled` when `HealthStatus != Healthy`, (d) handler returns `Success = true` with zero-deletion message

---

## 2026-05-10 — Workflow Lints: Add Markdown & YAML Linting to CI

**Issue:** #287 — [Feature] Add markdown lint and YAML lint GitHub Actions workflows
**PR:** #288
**Branch:** squad/287-lint-workflows
**Status:** ✅ Complete — PR ready for review

### Work Completed

Added two new GitHub Actions workflows to the `.github/workflows/` directory:

1. **`lint-markdown.yml`**
- Uses `DavidAnson/markdownlint-cli2-action@v23`
- References existing `.markdownlint.json` (no duplication)
- Triggers: `push` to `[dev, insider]` + `pull_request` to `[dev, preview, main, insider]`
- Paths filtered to markdown files only

2. **`lint-yaml.yml`**
- Uses `ibiqlik/action-yamllint@v3`
- **Inline config** (no separate `.yamllint.yml` file) tuned to MyBlog conventions:
- `line-length: max: 200` (GitHub Actions workflows are verbose)
- `truthy: allowed-values: ['true', 'false', 'on']` (GitHub event triggers use `on:`)
- `brackets: min-spaces-inside: 0, max-spaces-inside: 1`
- Same trigger pattern as markdown workflow

### Design Decisions

- **Markdown config reuse:** The repo already has `.markdownlint.json` (used by pre-commit hook). Referencing it in the workflow avoids duplication and maintains a single source of truth.
- **YAML inline config:** No separate dotfile. The workflow is self-documenting and removes management overhead for a single linting rule set.
- **Checkout version:** `actions/checkout@v6` — consistent with all other MyBlog workflows.
- **Reference:** BlogApp workflows were consulted for pattern, but conventions adapted to MyBlog's branch model (`dev` + `insider` for push, expanded set for PR).

### Reference Decision

Decision #26: Lint Workflow Pattern for MyBlog (merged into `.squad/decisions.md`)

### Next Steps

- Review PR #288 for approval
- Merge to `dev` branch
- Workflows become active on next push/PR to dev, insider, or main
35 changes: 35 additions & 0 deletions .squad/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2231,3 +2231,38 @@ GitHub rejected `gh pr review --approve` (cannot approve own PR via same account
1. Squash merge PR #272
2. Tag `main` with `vX.Y.Z` after CI green
3. Run `squad-mark-released` workflow

---

### 26. Lint Workflow Pattern for MyBlog

**Date:** 2026-05-10
**Author:** Boromir (DevOps)
**Issue:** #287 | **PR:** #288
**Status:** ✅ Implemented

#### Decision

Added `lint-markdown.yml` and `lint-yaml.yml` to `.github/workflows/`.

#### Conventions Established

1. **Markdown linting** uses `DavidAnson/markdownlint-cli2-action@v23` + repo-root `.markdownlint.json` (already present). Config referenced via `config:` parameter — no duplication.

2. **YAML linting** uses `ibiqlik/action-yamllint@v3` with **inline** `config_data` — no separate `.yamllint.yml` file. Rules tuned to MyBlog workflow style:
- `line-length: max: 200` (GitHub Actions workflows are verbose)
- `truthy: allowed-values: ['true', 'false', 'on']` (GitHub event triggers use `on:`)
- `brackets: min-spaces-inside: 0, max-spaces-inside: 1`

3. **Trigger pattern** matches all existing MyBlog workflows:
- `push: branches: [dev, insider]`
- `pull_request: branches: [dev, preview, main, insider]`
- Path-filtered so they only run when relevant files change

4. **checkout version:** `actions/checkout@v6` — consistent with all other MyBlog workflows.

#### Rationale

- BlogApp was used as a reference but conventions were adapted to MyBlog branch model.
- Inline yamllint config avoids a proliferation of dotfiles; the workflow is self-documenting.
- Reusing `.markdownlint.json` respects existing tooling (it's also used by the pre-commit hook via `markdownlint-cli2` in `package.json`).
54 changes: 39 additions & 15 deletions .squad/playbooks/pre-push-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Owner:** Boromir (DevOps) + Aragorn (Lead)
**Ref:** `.github/hooks/pre-push`, `CONTRIBUTING.md`
**Last Updated:** 2026-04-19
**Last Updated:** 2026-05-11

---

Expand All @@ -20,7 +20,7 @@

## Overview

The pre-push hook (`.github/hooks/pre-push`) enforces 5 gates that mirror CI. This playbook documents what agents must do before pushing and how to troubleshoot failures.
The pre-push hook (`.github/hooks/pre-push`) enforces 6 gates that mirror CI. This playbook documents what agents must do before pushing and how to troubleshoot failures.

## Pre-Flight Checklist (Before `git push`)

Expand All @@ -41,15 +41,29 @@ Before running `git push`, verify:
git add <files>
```

3. **Release build passes locally** — Gate 2 runs Release (not Debug)
3. **Code is formatted** — Gate 2 runs `dotnet format --verify-no-changes`

```bash
dotnet format MyBlog.slnx --verify-no-changes
```

If formatting issues are found, fix with:

```bash
dotnet format MyBlog.slnx
git add -u
git commit # or --amend
```

4. **Release build passes locally** — Gate 3 runs Release (not Debug)

```bash
dotnet build IssueTrackerApp.slnx --configuration Release
```

If build fails, run `.github/prompts/build-repair.prompt.md` to fix.

4. **Unit tests pass** — Gate 3 runs 6 test projects
5. **Unit tests pass** — Gate 4 runs 6 test projects

```bash
dotnet test tests/Architecture.Tests/Architecture.Tests.csproj --configuration Release --no-build
Expand All @@ -60,25 +74,26 @@ Before running `git push`, verify:
dotnet test tests/Persistence.AzureStorage.Tests/Persistence.AzureStorage.Tests.csproj --configuration Release --no-build
```

5. **Docker is running** — Gate 4 requires Docker for integration tests
6. **Docker is running** — Gate 5 requires Docker for integration tests

```bash
docker info &>/dev/null && echo "Docker OK" || echo "Docker NOT running"
```

## The 5 Gates (What the Hook Runs)
## The 6 Gates (What the Hook Runs)

When you execute `git push`, the hook runs automatically:

| Gate | What | Blocks Push If |
| ----- | ---------------------- | ------------------------------------------------------------------------ |
| **0** | Branch protection | Current branch is `main` or `dev` |
| **1** | Untracked source files | `.razor`/`.cs` files not staged (prompts y/N) |
| **2** | Release build | `dotnet build --configuration Release` fails (3 attempts) |
| **3** | Unit/Arch/bUnit tests | Any of 6 test projects fail (3 attempts) |
| **4** | Integration tests | Any of 4 integration test projects fail; Docker not running (3 attempts) |
| **2** | dotnet format | Any file requires formatting changes (prompts auto-fix y/N) |
| **3** | Release build | `dotnet build --configuration Release` fails (3 attempts) |
| **4** | Unit/Arch/bUnit tests | Any of 6 test projects fail (3 attempts) |
| **5** | Integration tests | Any of 4 integration test projects fail; Docker not running (3 attempts) |
Comment on lines +92 to +94

### Gate 3 — Test Projects (Unit)
### Gate 4 — Test Projects (Unit)

```text
tests/Architecture.Tests/Architecture.Tests.csproj
Expand All @@ -89,7 +104,7 @@ tests/Web.Tests/Web.Tests.csproj
tests/Persistence.AzureStorage.Tests/Persistence.AzureStorage.Tests.csproj
```

### Gate 4 — Integration Test Projects (Docker Required)
### Gate 5 — Integration Test Projects (Docker Required)

```text
tests/Persistence.MongoDb.Tests.Integration/Persistence.MongoDb.Tests.Integration.csproj
Expand All @@ -102,15 +117,23 @@ These use Testcontainers (mongo:7.0, Azurite) and Aspire DCP. Docker daemon MUST

## Retry Behavior

The hook allows **3 attempts** for Gates 2, 3, and 4. Between attempts:
The hook allows **3 attempts** for Gates 3, 4, and 5. Between attempts:

- The hook pauses and prompts "Fix the errors and press Enter to retry, or Ctrl+C to abort"
- Fix the failing code, then press Enter
- The gate re-runs from scratch

## Troubleshooting

### Build Failure (Gate 2)
### Formatting Failure (Gate 2)

| Symptom | Fix |
| ------------------------ | ----------------------------------------------------------------- |
| Files differ from format | Run `dotnet format MyBlog.slnx`, then `git add -u && git commit` |
| Analyzer rule violation | Run `dotnet format MyBlog.slnx --diagnostics <rule-id>` to debug |
| dotnet format not found | Install .NET SDK matching `global.json`; format ships with SDK |

### Build Failure (Gate 3)

| Symptom | Fix |
| ------------------------ | ----------------------------------------------------- |
Expand All @@ -120,15 +143,15 @@ The hook allows **3 attempts** for Gates 2, 3, and 4. Between attempts:

**Escalation:** Run `.github/prompts/build-repair.prompt.md` for automated fix.

### Test Failure (Gate 3)
### Test Failure (Gate 4)

| Symptom | Fix |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Architecture test failure | Check naming conventions (commands → `Command`, queries → `Query`, handlers → `Handler`, validators → `Validator`) |
| bUnit test failure | Verify Blazor component rendering; check `Render<T>()` not `RenderComponent<T>()` (bUnit 2.x) |
| DateTime equality failure | Assert individual fields, not whole-record equality (UtcNow varies between calls) |

### Integration Test Failure (Gate 4)
### Integration Test Failure (Gate 5)

| Symptom | Fix |
| ------------------------- | --------------------------------------------------------------- |
Expand All @@ -148,6 +171,7 @@ chmod +x .git/hooks/pre-push
## Anti-Patterns

- ❌ **Bypassing the hook** with `git push --no-verify` — CI will catch it, wasting time
- ❌ **Committing unformatted code** — Gate 2 blocks the push; run `dotnet format MyBlog.slnx` first
- ❌ **Running Debug build only** — CI uses Release; Debug hides missing files
- ❌ **Pushing without Docker** — Gate 4 will block; start Docker first
- ❌ **Ignoring untracked files** — They're invisible to CI and will cause failures
Expand Down
7 changes: 4 additions & 3 deletions .squad/skills/pre-push-test-gate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ chmod +x .git/hooks/pre-push

- Gate 0: Block direct push to `main`
- Gate 1: Warn on untracked `.razor`/`.cs` files
- Gate 2: Release build (0 warnings, 0 errors)
- Gate 3: Unit + bUnit + Architecture tests (6 projects, no Docker)
- Gate 4: Integration + Playwright E2E — **AppHost.Tests included** (Docker required)
- Gate 2: `dotnet format --verify-no-changes` (formatting check; offers auto-fix)
- Gate 3: Release build (0 warnings, 0 errors)
- Gate 4: Unit + bUnit + Architecture tests (6 projects, no Docker)
- Gate 5: Integration + Playwright E2E — **AppHost.Tests included** (Docker required)
Comment on lines +70 to +72

**PowerShell (Windows):**

Expand Down
9 changes: 5 additions & 4 deletions scripts/install-hooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ echo ""
echo "Pre-commit hook gates on every 'git commit':"
echo " • Runs markdownlint on staged .md files (degrades gracefully if not installed)"
echo ""
echo "Pre-push hook enforces 5 gates on every 'git push':"
echo "Pre-push hook enforces 6 gates on every 'git push':"
echo " 0. Enforces branch naming — squad/{issue}-{slug} runs all gates;"
echo " sprint/{N}-{slug} passes Gate 0 and exits (skips feature gates)"
echo " 1. Warns about untracked .razor/.cs source files"
echo " 2. Release build (dotnet build MyBlog.slnx --configuration Release)"
echo " 3. Unit/arch tests (tests/Architecture.Tests, tests/Unit.Tests)"
echo " 4. Integration tests (tests/Integration.Tests — Docker required)"
echo " 2. dotnet format --verify-no-changes (formatting check; offers auto-fix)"
echo " 3. Release build (dotnet build MyBlog.slnx --configuration Release)"
echo " 4. Unit/arch tests (tests/Architecture.Tests, tests/Unit.Tests)"
echo " 5. Integration tests (tests/Integration.Tests — Docker required)"
Comment on lines +90 to +91
echo ""
echo "To skip in an emergency: git commit --no-verify / git push --no-verify"
Loading
Loading