[Phase A] Ruff baseline (warning-only) — Python linting (#548)

* ci: add Ruff baseline (warning-only) — Phase A Introduce Ruff as the Python linter in non-blocking (Phase A) mode per the DevSecOps Guardrails epic. - Add pyproject.toml with [tool.ruff]: line-length 100, py312, rule subset E/F/I, and excludes for vendored/generated/archived paths. - Add non-blocking Lint workflow (.github/workflows/lint.yml) that emits GitHub annotations and a step summary; continue-on-error so it never fails the build. - Document the baseline (1235 violations, counts by rule) and local usage in docs/devsecops/ruff-baseline.md. No violations are fixed (Phase B) or enforced (Phase C) in this change. Closes #540 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: make Ruff job check green in Phase A (step-level continue-on-error) Move continue-on-error to the ruff check step so the job concludes success and the check appears green (warning-only), while still emitting annotations and the statistics step summary. Keeps the job non-blocking as intended for Phase A. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Juan Manuel Servera committed Jun 26, 2026 at 23:46 UTC 6a35b1513c21447f58e6ef17827c2273567add52
3 files changed +150
.github/workflows/lint.yml new
+60
@@ -0,0 +1,60 @@
1 +# Ruff lint — Phase A (warning-only / non-blocking)
2 +#
3 +# DevSecOps Guardrails epic (jmservera/SquadScope-Coordinator#33), issue #540.
4 +# This job reports Python lint violations as GitHub annotations only. It is
5 +# intentionally NON-BLOCKING (continue-on-error) for Phase A — it must never
6 +# fail a build. Enforcement is deferred to Phase C.
7 +
8 +name: Lint
9 +
10 +on:
11 + push:
12 + branches:
13 + - main
14 + pull_request:
15 + branches:
16 + - main
17 +
18 +permissions:
19 + contents: read
20 +
21 +concurrency:
22 + group: ${{ github.workflow }}-${{ github.ref }}
23 + cancel-in-progress: true
24 +
25 +jobs:
26 + ruff:
27 + name: Ruff (warning-only)
28 + runs-on: ubuntu-latest
29 + # Phase A: non-blocking. Annotations only — do not fail the build.
30 + continue-on-error: true
31 + steps:
32 + - name: Checkout code
33 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
34 + with:
35 + persist-credentials: false
36 +
37 + - name: Set up Python
38 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
39 + with:
40 + python-version: "3.12"
41 +
42 + - name: Install Ruff (pinned)
43 + run: python -m pip install ruff==0.15.7
44 +
45 + - name: Ruff check (GitHub annotations)
46 + # Phase A: warning-only. Tolerate violations so the check stays green
47 + # while still emitting annotations + the statistics summary below.
48 + continue-on-error: true
49 + run: ruff check . --output-format=github
50 +
51 + - name: Ruff baseline statistics (summary)
52 + if: always()
53 + run: |
54 + {
55 + echo "### Ruff baseline (warning-only)"
56 + echo ""
57 + echo '```'
58 + ruff check . --statistics || true
59 + echo '```'
60 + } >> "$GITHUB_STEP_SUMMARY"
docs/devsecops/ruff-baseline.md new
+63
@@ -0,0 +1,63 @@
1 +# Ruff Baseline (Phase A — warning-only)
2 +
3 +> Issue: jmservera/SquadScope#540 · Epic: jmservera/SquadScope-Coordinator#33
4 +> Mode: **warning-only / non-blocking**. Do not fix (Phase B) or enforce (Phase C) yet.
5 +
6 +Ruff is the Python linter/formatter for SquadScope. In Phase A it runs in CI as a
7 +**non-blocking** job (`continue-on-error: true`) that emits GitHub annotations only.
8 +
9 +## Configuration
10 +
11 +See `[tool.ruff]` in `pyproject.toml`:
12 +
13 +- `line-length = 100`
14 +- `target-version = "py312"`
15 +- Lint rule subset: `E` (pycodestyle errors), `F` (Pyflakes), `I` (import sorting)
16 +- Vendored/generated/archived paths excluded (`.venv`, `node_modules`, `public`,
17 + `resources`, `themes`, `scripts/archived`, `.worktrees`)
18 +
19 +## Baseline snapshot
20 +
21 +- **Tool:** ruff 0.15.7
22 +- **Date:** 2026-06-26
23 +- **Total violations:** 1235 (129 auto-fixable)
24 +
25 +| Count | Rule | Description |
26 +|------:|------|-------------|
27 +| 1080 | E501 | line-too-long |
28 +| 65 | F401 | unused-import |
29 +| 62 | I001 | unsorted-imports |
30 +| 14 | E402 | module-import-not-at-top-of-file |
31 +| 8 | F841 | unused-variable |
32 +| 2 | E741 | ambiguous-variable-name |
33 +| 2 | F541 | f-string-missing-placeholders |
34 +| 1 | F402 | import-shadowed-by-loop-var |
35 +| 1 | F821 | undefined-name |
36 +
37 +Regenerate with: `ruff check . --statistics`
38 +
39 +## Running locally
40 +
41 +```bash
42 +# Install (pinned to match CI)
43 +pip install ruff==0.15.7
44 +
45 +# Lint the repository (report only)
46 +ruff check .
47 +
48 +# Show counts by rule
49 +ruff check . --statistics
50 +
51 +# Auto-fix the safe subset (Phase B work — do not bulk-apply in Phase A)
52 +ruff check . --fix
53 +
54 +# Format check / apply (not enforced in Phase A)
55 +ruff format --check .
56 +ruff format .
57 +```
58 +
59 +## Phase plan
60 +
61 +- **Phase A (now):** baseline + non-blocking CI annotations. ← this PR
62 +- **Phase B:** fix violations (start with auto-fixable F401/I001/F541).
63 +- **Phase C:** pre-push hooks + blocking required status check.
pyproject.toml new
+27
@@ -0,0 +1,27 @@
1 +# Python tooling configuration for SquadScope.
2 +#
3 +# Ruff is introduced in warning-only (Phase A) mode — see issue #540 and the
4 +# DevSecOps Guardrails epic (jmservera/SquadScope-Coordinator#33). The CI job is
5 +# non-blocking for now; violations are reported as annotations only and are not
6 +# fixed yet (Phase B) nor enforced (Phase C).
7 +
8 +[tool.ruff]
9 +line-length = 100
10 +target-version = "py312"
11 +
12 +# Keep generated, vendored and archived code out of the baseline.
13 +extend-exclude = [
14 + ".venv",
15 + "venv",
16 + "node_modules",
17 + "public",
18 + "resources",
19 + "themes",
20 + "scripts/archived",
21 + ".worktrees",
22 +]
23 +
24 +[tool.ruff.lint]
25 +# Conservative Phase-A rule subset: pycodestyle errors (E), Pyflakes (F),
26 +# and import sorting (I). Broaden in later phases.
27 +select = ["E", "F", "I"]