Skip to content
Closed
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
126 changes: 126 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# CI — lint, test, build for Go backend and React Native/Expo frontend.
#
# Gates per coding-standards.md §7:
# Backend: golangci-lint, go test (short + integration), coverage ≥80% line,
# go build ./cmd/api/, gqlgen validate
# Frontend: tsc --noEmit, eslint, prettier --check, jest --ci --coverage ≥80%,
# graphql-codegen --check
#
# Ecosystem detection mirrors dependency-audit.yml so jobs are skipped when
# the corresponding source tree does not yet exist.
name: CI

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

permissions: {}

jobs:
# ── Detect which source trees are present ──────────────────────────────────
detect:
name: Detect ecosystems
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
go: ${{ steps.check.outputs.go }}
node: ${{ steps.check.outputs.node }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Detect ecosystems
id: check
run: |
if find . -name 'go.mod' -not -path '*/vendor/*' | grep -q .; then
echo "go=true" >> "$GITHUB_OUTPUT"
else
echo "go=false" >> "$GITHUB_OUTPUT"
fi
Comment on lines +37 to +41

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detect sets go=true if any go.mod exists anywhere in the repo, but the backend job assumes a root-module layout (go-version-file: go.mod and go build ./cmd/api/). In a monorepo (or if the Go module lives in a subdir), this will run and then fail because go.mod/cmd/api aren’t at repo root. Either restrict detection to the expected module path, or iterate over each go.mod directory and run setup/build/tests within that working directory (similar to how dependency-audit.yml loops projects).

Copilot uses AI. Check for mistakes.

if find . -name 'package.json' -not -path '*/node_modules/*' | grep -q .; then
echo "node=true" >> "$GITHUB_OUTPUT"
else
echo "node=false" >> "$GITHUB_OUTPUT"
fi
Comment on lines +43 to +47

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Node ecosystem detection is based on package.json, but the frontend job runs npm ci (which requires a lockfile) and the PR description says this mirrors dependency-audit.yml (which detects via package-lock.json). This can cause CI to run and then fail for repos that have a package.json but no package-lock.json (or where the lockfile is in a subdirectory). Detect package-lock.json instead (or switch install to npm install / run per found project directory).

Copilot uses AI. Check for mistakes.

# ── Go backend ─────────────────────────────────────────────────────────────
backend:
name: Backend CI
needs: detect
if: needs.detect.outputs.go == 'true'
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
cache: true

- name: Lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
with:
version: v2.1.6

- name: Build
run: go build ./cmd/api/

- name: Validate GraphQL schema
run: go run github.com/99designs/gqlgen validate

- name: Unit tests
run: go test ./... -short -count=1

- name: Integration tests
run: go test -tags=integration ./... -count=1

- name: Coverage check (≥80% line)
run: |
go test ./... -short -count=1 -coverprofile=coverage.out
pct=$(go tool cover -func=coverage.out \
| awk '/^total:/ { gsub(/%/, "", $NF); print $NF }')
echo "Coverage: ${pct}%"
awk -v p="$pct" 'BEGIN {
if (p+0 < 80) { print "Coverage " p "% is below required 80%"; exit 1 }
}'
Comment on lines +83 to +91

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The coverage gate runs go test with -short (and re-runs tests after the unit/integration steps). Per _bmad-output/planning-artifacts/coding-standards.md §7, the CI coverage command is go test -coverprofile=coverage.out (no -short). Using -short can make the enforced coverage diverge from the documented standard and from local CI expectations. Align the coverage command with the standard (and ideally avoid re-running the same tests multiple times).

Copilot uses AI. Check for mistakes.

# ── React Native / Expo frontend ───────────────────────────────────────────
frontend:
name: Frontend CI
needs: detect
if: needs.detect.outputs.node == 'true'
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: lts/*
cache: npm

- name: Install dependencies
run: npm ci

- name: Type check
run: npx tsc --noEmit

- name: Lint
run: npx eslint . --max-warnings 0

- name: Format check
run: npx prettier --check .
Comment on lines +110 to +120

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The frontend job runs all commands at repository root (npm ci, npx tsc, eslint, prettier, jest, graphql-codegen). If the repo contains a package.json in a subdirectory (which detect would find), the job will run but fail because it isn’t cd’d into that project. Consider restricting detection to a root package.json/lockfile, or looping over each detected Node project directory and running the gates within it.

Copilot uses AI. Check for mistakes.

- name: GraphQL codegen check
run: npx graphql-codegen --check

- name: Test + coverage (≥80% branch and line)
run: npx jest --ci --coverage --coverageThreshold='{"global":{"lines":80,"branches":80}}'
Loading