-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
145 lines (126 loc) · 5.43 KB
/
Copy pathci-quality-gate.yml
File metadata and controls
145 lines (126 loc) · 5.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
---
name: CI Quality Gate
'on':
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:
inputs:
ref:
description: Branch to run quality gate against
required: false
repository_dispatch:
types: [ci-quality]
concurrency:
group: quality-gate-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
quality:
name: Lint, Tests, Docs, Security
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 25
steps:
- name: Resolve ref
id: ref
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ github.event.inputs.ref }}" ]]; then
echo "target_ref=${{ github.event.inputs.ref }}" >> "$GITHUB_OUTPUT"
elif [[ "${{ github.event_name }}" == "repository_dispatch" && -n "${{ github.event.client_payload.ref }}" ]]; then
echo "target_ref=${{ github.event.client_payload.ref }}" >> "$GITHUB_OUTPUT"
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
# Use commit SHA for PRs — branch names from forks don't exist in the base repo
echo "target_ref=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
else
echo "target_ref=${{ github.head_ref || github.ref_name }}" >> "$GITHUB_OUTPUT"
fi
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.ref.outputs.target_ref }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install tooling
run: |
python -m pip install --upgrade pip
pip install yamllint==1.35.1 check-jsonschema==0.28.4 safety==3.2.4
pip install -r requirements-dev.txt
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: YAML lint (.github/workflows)
run: |
# yamllint cannot properly parse JavaScript template literals in YAML
# Skip pr-issue-auto-close.yml which contains complex template strings
find .github/workflows -name "*.yml" ! -name "pr-issue-auto-close.yml" -exec yamllint -d '{extends: default, rules: {line-length: {max: 160}}}' {} +
- name: Validate GitHub workflow schemas
run: |
# Exclude pr-issue-auto-close.yml (complex JS template literals cause parsing errors)
# Exclude smart-sync.yml (uses projects_v2_item event not yet in official schema)
find .github/workflows -name "*.yml" \
! -name "pr-issue-auto-close.yml" \
! -name "smart-sync.yml" \
-exec check-jsonschema --builtin-schema github-workflows {} + || true
- name: Python syntax check (blocking)
run: |
# Covers every top-level skill domain + scripts/. When adding a new
# domain folder, add it here (audit gate G9: this list previously
# skipped 8 post-v2.7 domains).
python -m compileall \
marketing-skill product-team c-level-advisor \
engineering-team ra-qm-team engineering \
business-growth finance project-management \
productivity marketing research \
business-operations commercial research-ops \
compliance-os markdown-html scripts
- name: Validate plugin.json manifests (blocking — guards #539 + #686)
run: |
python scripts/check_plugin_json.py --all
# ---- Audit guardrails (newgen-2026-06 gates) ----------------------
# BLOCKING since PR-2 (flipped ahead of the 2026-07-01 SLA — every
# advisory run was green through PR #835). If a gate misfires on a
# legitimate edge case, extend its in-repo allowlist
# (scripts/check_paths_allowlist.txt, scripts/smoke_exceptions.txt)
# rather than re-adding continue-on-error.
- name: Path-existence linter (gate G1 — blocking)
run: |
python3 scripts/check_paths.py --all
- name: Dual-publish drift guard (gate G4 — blocking)
run: |
python3 scripts/check_dual_publish.py
- name: Script --help smoke gate (gate G8 — blocking)
run: |
python3 scripts/smoke_scripts.py
- name: JSON-output sample gate (gate G9 — advisory)
continue-on-error: true
run: |
python3 scripts/smoke_json_output.py
- name: Counter derivation check (gate G3 — blocking)
run: |
python3 scripts/derive_counters.py --check
- name: Safety dependency audit (requirements*.txt)
run: |
set -e
files=$(find . -name "requirements*.txt" 2>/dev/null || true)
if [[ -z "$files" ]]; then
echo "No requirements files found; skipping safety scan."
exit 0
fi
for f in $files; do
echo "Auditing $f"
if ! safety check --full-report --file "$f"; then
echo "::warning file=$f::safety found vulnerabilities in $f (advisory)"
fi
done
- name: Markdown link spot-check
run: |
# Non-blocking: external links (claude.ai) may timeout, anchor links can't be validated
npx --yes markdown-link-check@3.12.2 README.md || true
- name: Summarize results
if: always()
run: |
echo "Quality gate completed with status: ${{ job.status }}"