Story 1.2: Create CI Pipeline Lint Stage
Sprint Key: 1-2-create-ci-pipeline-lint-stage
Epic: 1
PRD: testing-framework
Story 1.2: Create CI Pipeline Lint Stage
Status: ready-for-dev
Story
As a developer, I want the CI pipeline to run lint checks across all components on every merge request, so that code quality violations are caught before review.
Acceptance Criteria
-
Given a
.gitlab-ci.ymlexists at the repository root, when a merge request is opened or updated, then alintstage runs parallel lint jobs for all components -
lint:backend— runsnpm ci && npm run lintincomponents/gov-chat-backend/ -
lint:frontend— runsnpm ci && npm run lintincomponents/gov-chat-frontend/ -
lint:doc-repo— runsnpm ci && npm run lintincomponents/document-repository/ -
lint:python— runs Ruff check + format check ongenie-ai-overlay/ -
lint:dart— runsflutter analyzeonmobile/genie_ai_mobile/ - Each job uses the appropriate Docker image (
node:20-alpinefor JS,python:3.10-slimfor Python,ghcr.io/cirruslabs/flutter:3.29.3for Dart) - Jobs fail the pipeline if any lint errors are found (non-zero exit)
- Path-based
rules:changestrigger only relevant linters on MRs — full suite runs onmainbranch pushes regardless of path changes - Each Node.js job uses
cachekeyed on its ownpackage-lock.jsonhash - The existing
flutter:testandpatrol:e2ejobs remain untouched
Tasks / Subtasks
-
Task 1: Add lintstage to.gitlab-ci.yml(AC: #1 (closed))-
Insert lintas the first stage beforetestande2e -
Add shared hidden job templates ( .lint_node,.lint_python,.lint_flutter) for DRY configuration
-
-
Task 2: Create lint:backendjob (AC: #2 (closed), #7 (closed), #8 (closed), #9 (closed), #10 (closed))-
Use node:20-alpineimage -
Cache keyed on components/gov-chat-backend/package-lock.json -
before_script:cd components/gov-chat-backend && npm ci -
script:npm run lint -
rules:changes: trigger oncomponents/gov-chat-backend/**/*and.gitlab-ci.yml
-
-
Task 3: Create lint:frontendjob (AC: #3 (closed), #7 (closed), #8 (closed), #9 (closed), #10 (closed))-
Use node:20-alpineimage -
Cache keyed on components/gov-chat-frontend/package-lock.json -
before_script:cd components/gov-chat-frontend && npm ci -
script:npm run lint -
rules:changes: trigger oncomponents/gov-chat-frontend/**/*and.gitlab-ci.yml
-
-
Task 4: Create lint:doc-repojob (AC: #4 (closed), #7 (closed), #8 (closed), #9 (closed), #10 (closed))-
Use node:20-alpineimage -
Cache keyed on components/document-repository/package-lock.json -
before_script:cd components/document-repository && npm ci -
script:npm run lint -
rules:changes: trigger oncomponents/document-repository/**/*and.gitlab-ci.yml
-
-
Task 5: Create lint:pythonjob (AC: #5 (closed), #7 (closed), #8 (closed), #9 (closed))-
Use python:3.10-slimimage -
Install ruffvia pip (no venv needed — single tool, no dependency conflicts) -
script:ruff check genie-ai-overlay/ANDruff format --check genie-ai-overlay/ -
rules:changes: trigger ongenie-ai-overlay/**/*.pyandgenie-ai-overlay/pyproject.tomland.gitlab-ci.yml -
Optional: generate GitLab codequality report via --output-format=gitlab
-
-
Task 6: Create lint:dartjob (AC: #6 (closed), #7 (closed), #8 (closed), #9 (closed))-
Reuse the existing Flutter image/version ( ghcr.io/cirruslabs/flutter:3.29.3) and caching pattern fromflutter:test -
script:flutter analyze(NOTflutter analyze --no-pub— must runflutter pub getfirst) -
rules:changes: trigger onmobile/genie_ai_mobile/**/*and.gitlab-ci.yml
-
-
Task 7: Add mainbranch full-suite rule to all lint jobs (AC: #9 (closed))-
Each job's rulesmust include:- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHwithwhen: on_success -
This ensures full lint suite runs on every push to main
-
-
Task 8: Validate (AC: all) -
Run gitlab-ci-lintor validate YAML syntax -
Verify existing flutter:testandpatrol:e2ejobs are NOT modified -
Ensure all npm run lintcommands match the existing package.json scripts in each component
-
Dev Notes
Existing .gitlab-ci.yml State
The file currently has two stages: test and e2e. It contains:
-
flutter:test— runsflutter analyze+flutter test --coverageon MRs touchingmobile/ -
patrol:e2e— Patrol E2E tests requiring Android emulator runner
You are MODIFYING this file, not creating it from scratch. Insert the lint stage before test and add the 5 new lint jobs. Do NOT restructure or rename existing jobs.
Lint Commands Per Component
Each component already has a working npm run lint script in its package.json:
| Component | Directory | Command | Tool |
|---|---|---|---|
| Backend | components/gov-chat-backend/ |
eslint . |
ESLint 10 (flat config) |
| Frontend | components/gov-chat-frontend/ |
eslint src/ |
ESLint 10 (flat config) + eslint-plugin-vue |
| Doc-repo | components/document-repository/ |
eslint . |
ESLint 10 (flat config) |
| Python | genie-ai-overlay/ |
ruff check / ruff format --check
|
Ruff (Python 3.10 target) |
| Mobile | mobile/genie_ai_mobile/ |
flutter analyze |
Flutter analyze |
Do NOT install ESLint/Ruff/Flutter — they run inside Docker images. The npm ci step installs everything from package-lock.json, which includes ESLint as a devDependency. Ruff needs only pip install ruff. Flutter is bundled in the Docker image.
Docker Images
-
Node.js jobs:
node:20-alpine— lightweight, has npm. All 3 JS components use Node.js 22 in production butnode:20-alpineis sufficient for linting (ESLint is not Node-version-sensitive). -
Python job:
python:3.10-slim— matches thetarget-version = "py3.10"ingenie-ai-overlay/pyproject.toml[tool.ruff] config. -
Flutter job:
ghcr.io/cirruslabs/flutter:3.29.3— same image as the existingflutter:testjob. Note: the existing job uses Flutter 3.41.7 downloaded at runtime but the image provides 3.29.3 — keep consistent with existing pattern.
Path-Based Trigger Rules
Use rules with changes for MR-only triggering + always-on for main:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "components/gov-chat-backend/**/*"
- ".gitlab-ci.yml"
when: on_success
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: on_success
This pattern means:
- On MRs: only run if relevant files changed
- On
main: always run (full suite)
Caching Strategy
Each Node.js job caches its own node_modules keyed on its own package-lock.json:
cache:
key:
files:
- components/gov-chat-backend/package-lock.json
prefix: lint-backend
paths:
- components/gov-chat-backend/node_modules/
For Python, Ruff is a single binary (pip install ruff takes ~2s) — caching is unnecessary overhead. Skip it.
For Flutter, reuse the existing caching pattern from flutter:test (pubspec.lock keyed).
Ruff Dual Check
Run BOTH ruff check (lint) and ruff format --check (formatting). The root package.json has separate scripts for both:
-
lint:py→ruff check genie-ai-overlay/ -
format:py:check→ruff format --check genie-ai-overlay/
In CI, chain them: ruff check genie-ai-overlay/ && ruff format --check genie-ai-overlay/
GitLab Codequality Integration (Optional Enhancement)
Ruff supports --output-format=gitlab which produces a JSON file displayable in MR diffs:
script:
- ruff check --output-format=gitlab --output-file=codequality-report.json genie-ai-overlay/
- ruff format --check genie-ai-overlay/
artifacts:
reports:
codequality: codequality-report.json
This is a nice-to-have — include it if straightforward but do not over-engineer.
Hidden Job Templates (DRY)
Use GitLab CI extends with hidden jobs (prefixed with .) to avoid repeating stage, image, interruptible, etc.:
.lint_node:
stage: lint
image: node:20-alpine
interruptible: true
Then each job extends the template and overrides before_script, script, cache, and rules.
Story 1-1 Learnings (MUST Apply)
-
Flutter has pre-existing analysis errors (36 errors as of story 1-1).
flutter analyzewill exit non-zero. Decide: useallow_failure: truefor lint:dart initially, or document the pre-existing errors. Recommendation: useallow_failure: trueuntil pre-existing errors are resolved — add a TODO comment explaining why. -
Reports directory pattern —
reports/and**/reports/are already in.gitignore(added in story 1-1). No action needed for lint artifacts. -
Flutter SDK download pattern — the existing
flutter:testjob downloads Flutter SDK from archive. Forlint:dart, use the same image-based approach but consider whetherflutter pub getis needed beforeflutter analyze. It IS needed becauseflutter analyzeresolves imports.
What NOT To Do
- Do NOT create a new
.gitlab-ci.yml— modify the existing one - Do NOT change the existing
flutter:testorpatrol:e2ejobs - Do NOT add format checking (Prettier/dart format) as blocking jobs — only lint/analyze. Format checks can be
allow_failure: trueif added at all, but the AC only requires lint. - Do NOT use
needs:keyword (that's for DAG mode, not needed here — stage-based ordering is sufficient) - Do NOT add
artifactsto lint jobs (no reports to collect — lint output is in job logs) - Do NOT modify any
package.json,eslint.config.js, orpyproject.tomlfiles — this story only touches.gitlab-ci.yml
References
- [Source:
.gitlab-ci.yml] — existing CI configuration - [Source:
_bmad-output/planning-artifacts/epics.mdEpic 1 Story 1.2] — acceptance criteria - [Source:
_bmad-output/planning-artifacts/prd.mdFR1, NFR1, NFR11] — functional requirements - [Source:
_bmad-output/planning-artifacts/architecture.mdCI Pipeline Integration] — pipeline stage structure - [Source:
_bmad-output/project-context.mdLinting & Formatting, Testing Rules] — lint commands and conventions - [Source:
1-1-configure-junit-xml-reporting-for-all-test-runners.md] — previous story learnings - [Source:
components/gov-chat-backend/package.json] — lint script:eslint . - [Source:
components/gov-chat-frontend/package.json] — lint script:eslint src/ - [Source:
components/document-repository/package.json] — lint script:eslint . - [Source:
genie-ai-overlay/pyproject.toml[tool.ruff]] — ruff config: target py3.10, line-length 120 - [Source:
mobile/genie_ai_mobile/analysis_options.yaml] — extendsflutter_lints/flutter.yaml
Project Structure Notes
-
.gitlab-ci.ymlis at repository root — this is the only file modified in this story - ESLint configs per component are flat configs (
eslint.config.js) — no.eslintrcfiles - All 3 JS components share base rules via
../shared/eslint-rules-base— this is already configured, no changes needed - Ruff is configured in
genie-ai-overlay/pyproject.toml— no separate config file
Dev Agent Record
Agent Model Used
Debug Log References
Completion Notes List
File List
-
UPDATE: .gitlab-ci.yml— add lint stage and 5 parallel lint jobs