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
140 changes: 140 additions & 0 deletions .claude/ci-cd/01-github-actions-ci-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# 01 — GitHub Actions CI Workflow (PR Gate)

## Summary
There is no CI. Nothing runs `dart format`, `flutter analyze`, or `flutter test` when code is
pushed or a PR is opened, so regressions and lint/format drift can land on `main` unnoticed.
This doc creates the core CI workflow: a fast, offline gate that every push and PR must pass.

## Severity & impact
**Critical.** This workflow is the foundation the entire engineering process rests on — it's
what makes the test suite (`../tests/`) and every future change actually *enforced* rather
than aspirational.

## Files to create
- `.github/workflows/ci.yml`
- `tool/check_coverage.sh` (if not already created in `../tests/08-coverage-and-quality-gates.md`)

## Current state
- `.github/` contains only `pull_request_template.md` and unrelated `modernize/` files.
- No `workflows/` directory.

## Target design

A single `ci.yml` with one job (optionally a matrix). It must:
1. Check out the repo.
2. Install a pinned Flutter version (which includes the matching Dart).
3. `flutter pub get`.
4. Verify formatting (`dart format --set-exit-if-changed`).
5. `flutter analyze` (treat infos/warnings as failures via `--fatal-infos`).
6. `flutter test --coverage`.
7. Enforce the coverage floor.

```yaml
# .github/workflows/ci.yml
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

# Cancel superseded runs on the same ref to save minutes.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze-and-test:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: 3.29.0 # pin; must bundle Dart >= 3.7.2
cache: true # cache the Flutter SDK + pub

- name: Install dependencies
run: flutter pub get

- name: Verify formatting
run: dart format --output=none --set-exit-if-changed .

- name: Analyze
run: flutter analyze --fatal-infos

- name: Run tests with coverage
run: flutter test --coverage

- name: Install lcov
run: sudo apt-get update && sudo apt-get install -y lcov

- name: Enforce coverage floor
run: bash tool/check_coverage.sh 85 # match the floor in ../tests/08

- name: Upload coverage artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/lcov.info
if-no-files-found: warn
```

### Notes & rationale
- **Flutter, not Dart-only:** the package depends on the Flutter SDK; `flutter_test` and the
`flutter/services.dart` asset stubbing in tests require it.
- **`--fatal-infos`:** the project keeps `flutter analyze` perfectly clean today; make CI
enforce that so it stays clean. If a specific info is intentional, suppress it in code with a
justified `// ignore:` rather than weakening the gate.
- **Formatting gate:** `dart format` is the canonical Dart formatter; failing on drift keeps
diffs clean. Run `dart format .` locally before pushing.
- **Coverage floor:** reuses `tool/check_coverage.sh` from `../tests/08`. If that script isn't
created yet, create it per that doc. The floor (85) must match.
- **Caching:** `subosito/flutter-action`'s `cache: true` caches the SDK and pub packages,
cutting cold-start time substantially.
- **Concurrency:** cancels stale runs on force-push/rebase to save CI minutes.

### Optional: multi-version matrix
Once green, consider a matrix to catch SDK-specific breakage:
```yaml
strategy:
matrix:
flutter-version: ['3.29.0', '3.x'] # pinned floor + latest stable
```
Keep the pinned floor matching the package's minimum supported SDK.

## Step-by-step implementation
1. Ensure `tool/check_coverage.sh` exists (from `../tests/08`); if not, create it there first.
2. Create `.github/workflows/ci.yml` with the YAML above.
3. Pin `flutter-version` to a stable release bundling Dart ≥ 3.7.2; verify locally that
`flutter --version` reports a matching Dart.
4. Run the gate locally to confirm it passes before pushing:
```bash
dart format --output=none --set-exit-if-changed .
flutter analyze --fatal-infos
flutter test --coverage
bash tool/check_coverage.sh 85
```
5. Push to a branch, open a PR, confirm the workflow runs and all steps pass.
6. Make the `analyze-and-test` job a **required status check** on `main` (see doc 04).

## Acceptance criteria
- `ci.yml` runs on every push to `main` and every PR targeting `main`.
- It installs Flutter, runs `flutter pub get`, checks formatting, runs `flutter analyze
--fatal-infos`, runs `flutter test --coverage`, and enforces the coverage floor.
- The job fails if any step fails (format drift, analyzer issue, test failure, or coverage
below floor).
- Coverage `lcov.info` is uploaded as an artifact.
- Runs complete in a few minutes with caching.

## Related docs
- [02 — coverage reporting](02-coverage-reporting.md) (surface coverage on the PR)
- [03 — release and publish](03-release-and-publish.md) (a separate, tag-driven workflow)
- [04 — repo hygiene](04-repo-hygiene-and-automation.md) (make this a required check)
- [../tests/08-coverage-and-quality-gates.md](../tests/08-coverage-and-quality-gates.md) (the floor + script)
108 changes: 108 additions & 0 deletions .claude/ci-cd/02-coverage-reporting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# 02 — Coverage Reporting

## Summary
Doc 01 produces a `coverage/lcov.info` and enforces a floor, but the number is invisible
unless you dig into CI logs/artifacts. This doc makes coverage **visible**: a per-PR summary
comment and/or diff coverage, and a README badge, so reviewers can see at a glance whether a
change is tested and whether new code is covered.

## Severity & impact
**High (process).** Visibility is what keeps coverage from silently rotting and gives
reviewers a fast signal. It also makes the "production-grade" claim demonstrable to outsiders
via a badge.

## Files to create / change
- `.github/workflows/ci.yml` (add a reporting step) **or** a dedicated `coverage.yml`
- `README.md` (add a coverage badge)
- Possibly `codecov.yml` (if using Codecov)

## Options (pick one)

### Option A — Codecov (recommended for public packages)
Rich PR comments, diff coverage, sunburst, a badge, and free for open source.
```yaml
# add after "Run tests with coverage" in ci.yml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage/lcov.info
fail_ci_if_error: false # don't fail the build on uploader hiccups
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # required for some setups
```
- For public repos, the token is sometimes optional but increasingly recommended — add
`CODECOV_TOKEN` as a repo secret to be safe.
- Add `codecov.yml` to tune targets and turn the gate informational vs. blocking:
```yaml
# codecov.yml
coverage:
status:
project:
default:
target: 85% # match the floor
threshold: 1% # allow tiny dips
patch:
default:
target: 80% # new/changed lines must be reasonably covered
comment:
layout: "reach, diff, files"
behavior: default
```
- Badge in `README.md`:
```markdown
[![codecov](https://codecov.io/gh/ahmadexe/agenix/branch/main/graph/badge.svg)](https://codecov.io/gh/ahmadexe/agenix)
```

### Option B — Coveralls
Similar to Codecov; uses `coverallsapp/github-action`. Fine alternative; pick if you already
use Coveralls elsewhere.

### Option C — Zero-dependency PR comment (no third party)
If you'd rather not send coverage to an external service, post the summary as a PR comment from
within the workflow:
```yaml
- name: Coverage summary
run: |
sudo apt-get update && sudo apt-get install -y lcov
lcov --summary coverage/lcov.info | tee coverage_summary.txt

- name: Comment coverage on PR
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: coverage
path: coverage_summary.txt
```
- Pros: no external service, no data leaves GitHub. Cons: no diff coverage, no badge from the
service (you can still self-host a badge via shields.io + an endpoint, but that's more work).

## Recommendation
Use **Option A (Codecov)** for a public package — diff coverage on PRs is the single most
useful signal for keeping new code tested, and the badge backs the "production-grade" claim.
Use Option C if the maintainer prefers no third-party data sharing.

## Patch/diff coverage matters
The **project** floor (doc 01) prevents overall regression, but **patch** coverage (are the
*new* lines tested?) is what actually drives good habits. Whichever option you choose, surface
patch/diff coverage on the PR (Codecov/Coveralls do this natively; Option C does not).

## Step-by-step implementation
1. Choose an option. For Codecov: create the Codecov project, add `CODECOV_TOKEN` secret, add
the upload step to `ci.yml`, add `codecov.yml`.
2. Add the coverage badge to `README.md` (top, near the pub.dev badge).
3. Open a PR and confirm: coverage uploads, the PR shows a coverage comment / diff coverage,
and the badge renders on `main`.
4. Decide whether the coverage status is **blocking** or **informational**. Recommended:
project status blocking (matches the doc-01 floor), patch status informational at first,
then blocking once the suite is mature.

## Acceptance criteria
- Coverage from each CI run is published and visible on the PR (comment and/or status).
- A coverage badge renders in `README.md`.
- New-code (patch/diff) coverage is visible to reviewers (Options A/B).
- If using a third party, the token is stored as a GitHub secret, never committed.

## Related docs
- [01 — CI workflow](01-github-actions-ci-workflow.md) (produces `lcov.info`)
- [../tests/08-coverage-and-quality-gates.md](../tests/08-coverage-and-quality-gates.md) (floor + honest denominator)
- [04 — repo hygiene](04-repo-hygiene-and-automation.md) (make coverage a required check, optional)
Loading
Loading