Skip to content

Commit e42bde5

Browse files
authored
Merge pull request #52 from PMDevSolutions/49-add-cicd-workflow-for-automated-pipeline-validation
feat(ci): add CI/CD workflow for automated pipeline validation
2 parents 6a11dba + bf28c45 commit e42bde5

17 files changed

Lines changed: 1020 additions & 280 deletions

.github/workflows/ci.yml

Lines changed: 182 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,62 @@ on:
66
pull_request:
77
branches: [main]
88

9+
concurrency:
10+
group: ci-${{ github.ref }}
11+
cancel-in-progress: true
12+
913
jobs:
14+
# ── Lightweight structural validation (no Node required) ──────────────
1015
validate:
11-
name: Validate Framework
16+
name: Validate Structure
1217
runs-on: ubuntu-latest
18+
timeout-minutes: 2
1319
steps:
1420
- uses: actions/checkout@v4
1521

16-
- name: Check shell scripts syntax
22+
- name: Check shell script syntax
1723
run: |
1824
errors=0
1925
for f in scripts/*.sh; do
2026
if [ -f "$f" ]; then
21-
echo "Checking $f..."
22-
bash -n "$f" || errors=$((errors + 1))
27+
bash -n "$f" || { echo "FAIL: $f"; errors=$((errors + 1)); }
2328
fi
2429
done
30+
echo "Checked $(ls scripts/*.sh 2>/dev/null | wc -l) scripts, $errors failed"
2531
exit $errors
2632
2733
- name: Validate JSON configs
2834
run: |
2935
errors=0
30-
for f in .claude/pipeline.config.json package.json templates/**/*.json; do
36+
for f in .claude/pipeline.config.json package.json; do
3137
if [ -f "$f" ]; then
32-
echo "Checking $f..."
33-
python3 -m json.tool "$f" > /dev/null || errors=$((errors + 1))
38+
python3 -m json.tool "$f" > /dev/null || { echo "FAIL: $f"; errors=$((errors + 1)); }
3439
fi
3540
done
41+
for f in templates/**/*.json; do
42+
if [ -f "$f" ]; then
43+
python3 -m json.tool "$f" > /dev/null || { echo "FAIL: $f"; errors=$((errors + 1)); }
44+
fi
45+
done
46+
echo "$errors JSON validation failures"
3647
exit $errors
3748
49+
- name: Validate pipeline.config.json structure
50+
run: |
51+
# Verify required top-level keys exist
52+
required_keys='["visualDiff","iterationLoop","tdd","e2e","qualityGate","appTypes","orchestration","caching"]'
53+
python3 -c "
54+
import json, sys
55+
with open('.claude/pipeline.config.json') as f:
56+
config = json.load(f)
57+
required = json.loads('$required_keys')
58+
missing = [k for k in required if k not in config]
59+
if missing:
60+
print(f'Missing required keys: {missing}')
61+
sys.exit(1)
62+
print(f'All {len(required)} required keys present')
63+
"
64+
3865
- name: Check required files exist
3966
run: |
4067
exit_code=0
@@ -46,6 +73,8 @@ jobs:
4673
"scripts/run-tests.sh"
4774
"scripts/check-types.sh"
4875
"scripts/visual-diff.js"
76+
"scripts/verify-tokens.sh"
77+
"scripts/check-security.sh"
4978
)
5079
for f in "${required_files[@]}"; do
5180
if [ -f "$f" ]; then
@@ -57,21 +86,152 @@ jobs:
5786
done
5887
exit $exit_code
5988
60-
- name: Check scripts are executable
89+
- name: Validate agent frontmatter
6190
run: |
62-
for f in scripts/*.sh; do
63-
if [ -f "$f" ]; then
64-
if [ ! -x "$f" ]; then
65-
echo "WARNING: $f is not executable"
66-
else
67-
echo " $f: OK"
91+
errors=0
92+
count=0
93+
for f in .claude/agents/*.md; do
94+
[ -f "$f" ] || continue
95+
count=$((count + 1))
96+
97+
# Extract YAML frontmatter between --- delimiters
98+
frontmatter=$(sed -n '/^---$/,/^---$/p' "$f" | sed '1d;$d')
99+
100+
if [ -z "$frontmatter" ]; then
101+
echo "FAIL: $f — no YAML frontmatter found"
102+
errors=$((errors + 1))
103+
continue
104+
fi
105+
106+
# Check required fields (tools is optional — omitted means "all tools")
107+
for field in name description; do
108+
if ! echo "$frontmatter" | grep -qE "^${field}:"; then
109+
echo "FAIL: $f — missing required field: $field"
110+
errors=$((errors + 1))
68111
fi
112+
done
113+
done
114+
echo "Checked $count agents, $errors failures"
115+
exit $errors
116+
117+
- name: Validate skill structure
118+
run: |
119+
errors=0
120+
count=0
121+
for f in .claude/skills/*.md; do
122+
[ -f "$f" ] || continue
123+
[ "$(basename "$f")" = "README.md" ] && continue
124+
count=$((count + 1))
125+
126+
frontmatter=$(sed -n '/^---$/,/^---$/p' "$f" | sed '1d;$d')
127+
128+
if [ -z "$frontmatter" ]; then
129+
echo "FAIL: $f — no frontmatter found"
130+
errors=$((errors + 1))
131+
continue
132+
fi
133+
134+
for field in name description; do
135+
if ! echo "$frontmatter" | grep -qE "^${field}:"; then
136+
echo "FAIL: $f — missing required field: $field"
137+
errors=$((errors + 1))
138+
fi
139+
done
140+
done
141+
echo "Checked $count skills, $errors failures"
142+
[ $count -eq 0 ] && echo "Note: no skill .md files found in .claude/skills/"
143+
exit $errors
144+
145+
- name: Validate templates
146+
run: |
147+
errors=0
148+
149+
# Check template JSON files parse correctly
150+
for f in templates/**/*.json; do
151+
if [ -f "$f" ]; then
152+
python3 -m json.tool "$f" > /dev/null 2>&1 || {
153+
echo "FAIL: $f — invalid JSON"
154+
errors=$((errors + 1))
155+
}
69156
fi
70157
done
71158
159+
# Check key template directories exist
160+
for dir in templates/shared templates/nextjs templates/vite; do
161+
if [ -d "$dir" ]; then
162+
echo " $dir: OK"
163+
else
164+
echo " $dir: MISSING"
165+
errors=$((errors + 1))
166+
fi
167+
done
168+
169+
# Check shared configs exist
170+
for f in templates/shared/eslint.config.js templates/shared/prettier.config.js templates/shared/tsconfig.json templates/shared/tailwind.config.ts; do
171+
if [ -f "$f" ]; then
172+
echo " $f: OK"
173+
else
174+
echo " $f: MISSING"
175+
errors=$((errors + 1))
176+
fi
177+
done
178+
179+
echo "$errors template validation failures"
180+
exit $errors
181+
182+
# ── Script test suite (needs Node + dependencies) ─────────────────────
183+
script-tests:
184+
name: Script Tests
185+
runs-on: ubuntu-latest
186+
timeout-minutes: 3
187+
steps:
188+
- uses: actions/checkout@v4
189+
190+
- name: Setup Node.js
191+
uses: actions/setup-node@v4
192+
with:
193+
node-version: "20"
194+
195+
- name: Install pnpm
196+
uses: pnpm/action-setup@v4
197+
with:
198+
version: 9
199+
200+
- name: Install dependencies
201+
run: pnpm install --frozen-lockfile
202+
203+
- name: Run script tests
204+
run: pnpm vitest run scripts/__tests__/ --reporter=verbose
205+
206+
# ── Lint & format check (needs Node + project with eslint/prettier) ───
207+
lint:
208+
name: Lint & Format
209+
runs-on: ubuntu-latest
210+
timeout-minutes: 3
211+
steps:
212+
- uses: actions/checkout@v4
213+
214+
- name: Setup Node.js
215+
uses: actions/setup-node@v4
216+
with:
217+
node-version: "20"
218+
219+
- name: Install pnpm
220+
uses: pnpm/action-setup@v4
221+
with:
222+
version: 9
223+
224+
- name: Install dependencies
225+
run: pnpm install --frozen-lockfile
226+
227+
- name: Run lint and format check
228+
run: bash scripts/lint-and-format.sh --check
229+
230+
# ── Token verification ────────────────────────────────────────────────
72231
token-verification:
73232
name: Verify Design Tokens
74233
runs-on: ubuntu-latest
234+
timeout-minutes: 2
75235
steps:
76236
- uses: actions/checkout@v4
77237

@@ -83,16 +243,18 @@ jobs:
83243
echo "No app source found — skipping token check"
84244
fi
85245
246+
# ── Security scanning ─────────────────────────────────────────────────
86247
security-scan:
87-
name: Security Scanning
248+
name: Security Scan
88249
runs-on: ubuntu-latest
250+
timeout-minutes: 3
89251
steps:
90252
- uses: actions/checkout@v4
91253

92254
- name: Setup Node.js
93255
uses: actions/setup-node@v4
94256
with:
95-
node-version: '20'
257+
node-version: "20"
96258

97259
- name: Install pnpm
98260
uses: pnpm/action-setup@v4
@@ -129,9 +291,11 @@ jobs:
129291
if-no-files-found: ignore
130292
retention-days: 30
131293

294+
# ── Visual regression (PR only) ───────────────────────────────────────
132295
visual-regression:
133-
name: Visual Regression Test
296+
name: Visual Regression
134297
runs-on: ubuntu-latest
298+
timeout-minutes: 5
135299
if: github.event_name == 'pull_request'
136300
steps:
137301
- uses: actions/checkout@v4
@@ -141,7 +305,7 @@ jobs:
141305
- name: Setup Node.js
142306
uses: actions/setup-node@v4
143307
with:
144-
node-version: '20'
308+
node-version: "20"
145309

146310
- name: Install pnpm
147311
uses: pnpm/action-setup@v4

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ on:
44
workflow_dispatch:
55
inputs:
66
release_type:
7-
description: 'Release type'
7+
description: "Release type"
88
required: true
9-
default: 'auto'
9+
default: "auto"
1010
type: choice
1111
options:
1212
- auto
@@ -30,7 +30,7 @@ jobs:
3030
- name: Setup Node.js
3131
uses: actions/setup-node@v4
3232
with:
33-
node-version: '20'
33+
node-version: "20"
3434

3535
- name: Install pnpm
3636
uses: pnpm/action-setup@v4

.prettierignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
node_modules/
2+
dist/
3+
build/
4+
pnpm-lock.yaml
5+
CHANGELOG.md
6+
.claude/
7+
templates/
8+
docs/
9+
app/
10+
*.md

.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"semi": true,
3+
"singleQuote": false,
4+
"tabWidth": 2,
5+
"trailingComma": "all",
6+
"printWidth": 100
7+
}

commitlint.config.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
export default {
2-
extends: ['@commitlint/config-conventional'],
2+
extends: ["@commitlint/config-conventional"],
33
rules: {
4-
'type-enum': [
4+
"type-enum": [
55
2,
6-
'always',
6+
"always",
77
[
8-
'feat',
9-
'fix',
10-
'docs',
11-
'style',
12-
'refactor',
13-
'perf',
14-
'test',
15-
'build',
16-
'ci',
17-
'chore',
18-
'revert',
8+
"feat",
9+
"fix",
10+
"docs",
11+
"style",
12+
"refactor",
13+
"perf",
14+
"test",
15+
"build",
16+
"ci",
17+
"chore",
18+
"revert",
1919
],
2020
],
21-
'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
22-
'header-max-length': [2, 'always', 100],
21+
"subject-case": [2, "never", ["start-case", "pascal-case", "upper-case"]],
22+
"header-max-length": [2, "always", 100],
2323
},
2424
};

eslint.config.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import js from "@eslint/js";
2+
import eslintConfigPrettier from "eslint-config-prettier";
3+
4+
export default [
5+
js.configs.recommended,
6+
eslintConfigPrettier,
7+
{
8+
languageOptions: {
9+
ecmaVersion: 2022,
10+
sourceType: "module",
11+
globals: {
12+
// Node.js globals
13+
console: "readonly",
14+
process: "readonly",
15+
Buffer: "readonly",
16+
__dirname: "readonly",
17+
__filename: "readonly",
18+
setTimeout: "readonly",
19+
clearTimeout: "readonly",
20+
setInterval: "readonly",
21+
clearInterval: "readonly",
22+
URL: "readonly",
23+
},
24+
},
25+
rules: {
26+
"no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
27+
},
28+
},
29+
{
30+
ignores: ["node_modules/", "dist/", "build/", ".claude/", "templates/", "docs/", "app/"],
31+
},
32+
];

0 commit comments

Comments
 (0)