1313 - ' .gitignore'
1414 pull_request :
1515 branches : [main]
16- # Always run on PRs (for review), but individual jobs can skip if needed
1716
1817# Cancel in-progress runs when a new workflow with the same group name starts
1918concurrency :
@@ -25,7 +24,6 @@ permissions:
2524 contents : read
2625
2726jobs :
28- # Separate lint job - runs once instead of on every matrix combination
2927 lint :
3028 name : Lint and format check
3129 runs-on : ubuntu-latest
5452 - name : Run ruff format check
5553 run : uv run ruff format --check peakbagger tests
5654
55+ typecheck :
56+ name : Typecheck
57+ runs-on : ubuntu-latest
58+ steps :
59+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
60+
61+ - name : Set up Python
62+ uses : actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
63+ with :
64+ python-version : " 3.14"
65+ cache : pip
66+ cache-dependency-path : pyproject.toml
67+
68+ - name : Install uv
69+ uses : astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7
70+ with :
71+ enable-cache : true
72+ cache-dependency-glob : " pyproject.toml"
73+
74+ - name : Install dependencies
75+ run : uv sync --extra dev
76+
5777 - name : Run ty
5878 run : uvx ty check peakbagger
59- continue-on-error : true
6079
6180 test :
6281 name : Test on ${{ matrix.os }} - Python ${{ matrix.python-version }}
94113 run : uv sync --extra dev
95114
96115 - name : Run tests with coverage
97- run : uv run pytest --cov=peakbagger --cov-report=xml --cov-report=term
116+ run : uv run pytest --cov=peakbagger --cov-report=xml --cov-report=term --cov-fail-under=85
98117
99118 - name : Upload coverage to Codecov
100119 if : matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14'
@@ -104,6 +123,47 @@ jobs:
104123 file : ./coverage.xml
105124 fail_ci_if_error : false
106125
126+ sast :
127+ name : SAST
128+ runs-on : ubuntu-latest
129+ steps :
130+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
131+ - name : Install Semgrep
132+ run : pip install semgrep
133+ - name : Run Semgrep
134+ run : semgrep scan --config auto --error .
135+
136+ complexity :
137+ name : Complexity
138+ runs-on : ubuntu-latest
139+ steps :
140+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
141+ - name : Install lizard
142+ run : pip install lizard==1.17.13
143+ - name : Check complexity
144+ run : lizard -l python -C 15 -L 60 -a 5 -w -x 'test_*.py' -x '*_test.py' -x '*/cli.py' peakbagger/
145+
146+ build :
147+ name : Build
148+ runs-on : ubuntu-latest
149+ steps :
150+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
151+
152+ - name : Set up Python
153+ uses : actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
154+ with :
155+ python-version : " 3.14"
156+ cache : pip
157+ cache-dependency-path : pyproject.toml
158+
159+ - name : Install uv
160+ uses : astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7
161+ with :
162+ enable-cache : true
163+ cache-dependency-glob : " pyproject.toml"
164+
165+ - run : uv build
166+
107167 validate-pr-title :
108168 name : Validate PR title
109169 runs-on : ubuntu-latest
@@ -135,48 +195,31 @@ jobs:
135195 bot
136196 dependencies
137197
138- # Sentinel job that always runs - use this as the required status check
198+ # Sentinel job - the ONLY required check in branch protection
139199 required-checks :
140200 name : Required Checks
141201 runs-on : ubuntu-latest
142- needs : [lint, test, validate-pr-title]
202+ needs : [lint, typecheck, test, sast, complexity, build , validate-pr-title]
143203 if : always()
144204 steps :
145205 - name : Check results
146206 run : |
147- lint_result="${{ needs.lint.result }}"
148- test_result="${{ needs.test.result }}"
149- pr_title_result="${{ needs.validate-pr-title.result }}"
150-
151- echo "Lint result: $lint_result"
152- echo "Test result: $test_result"
153- echo "PR title validation result: $pr_title_result"
154-
155- # Lint must always pass
156- if [[ "$lint_result" == "failure" ]]; then
157- echo "❌ Lint failed"
158- exit 1
159- elif [[ "$lint_result" == "cancelled" ]]; then
160- echo "❌ Lint was cancelled"
161- exit 1
162- fi
163-
164- # Tests must always pass
165- if [[ "$test_result" == "failure" ]]; then
166- echo "❌ Tests failed"
167- exit 1
168- elif [[ "$test_result" == "cancelled" ]]; then
169- echo "❌ Tests were cancelled"
170- exit 1
171- fi
172-
173- # PR title validation must pass (if it ran)
174- if [[ "$pr_title_result" == "failure" ]]; then
175- echo "❌ PR title validation failed"
176- exit 1
177- elif [[ "$pr_title_result" == "cancelled" ]]; then
178- echo "❌ PR title validation was cancelled"
207+ results=("${{ needs.lint.result }}" "${{ needs.typecheck.result }}" \
208+ "${{ needs.test.result }}" "${{ needs.sast.result }}" \
209+ "${{ needs.complexity.result }}" "${{ needs.build.result }}")
210+
211+ for result in "${results[@]}"; do
212+ if [[ "$result" == "failure" || "$result" == "cancelled" ]]; then
213+ echo "Required job failed or was cancelled: $result"
214+ exit 1
215+ fi
216+ done
217+
218+ # PR title validation must pass if it ran
219+ pr_result="${{ needs.validate-pr-title.result }}"
220+ if [[ "$pr_result" == "failure" || "$pr_result" == "cancelled" ]]; then
221+ echo "PR title validation failed"
179222 exit 1
180223 fi
181224
182- echo "✅ All required checks passed"
225+ echo "All required checks passed"
0 commit comments