Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## What does this PR change?

<!-- Brief description of the change and why it's being made -->

## Type of change

- [ ] New skill
- [ ] Skill update / bugfix
- [ ] Rule change (`claude/` or `reference/`)
- [ ] Docs / Config
- [ ] Meta (`plugin.json`, `commands/`, `validate-skills.sh`)

## Checklist

- [ ] `pre-commit run --all-files` passes
- [ ] `bash scripts/validate-skills.sh` passes
- [ ] Mirror up to date: `cp claude/*.md plugins/dev/rules/` run (for rule changes)
- [ ] `CHANGELOG.md` updated (for new skills or breaking changes)
80 changes: 68 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,106 @@ on:
push:
branches: [master]
pull_request:
branches: [master]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

permissions:
contents: read

jobs:
check-pr-size:
name: PR Size Check
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Fail if PR adds more than 400 lines
env:
BASE_REF: ${{ github.base_ref }}
run: |
ADDED=$(git diff "origin/$BASE_REF"...HEAD \
-- . ':(exclude)*.lock' ':(exclude)*-lock.json' \
':(exclude)CHANGELOG.md' \
| grep -cE '^\+[^+]' || echo 0)
echo "Lines added (excl. lockfiles/changelog): $ADDED"
if [ "$ADDED" -gt 400 ]; then
echo "::error::PR too large (${ADDED} lines added, limit: 400) — please split."
exit 1
fi

lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Markdown Lint
uses: DavidAnson/markdownlint-cli2-action@v16
uses: DavidAnson/markdownlint-cli2-action@b4c9feab76d8025d1e83c653fa3990936df0e6c8 # v16
with:
globs: "**/*.md"

- name: Link Check
uses: lycheeverse/lychee-action@v2
uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2
with:
args: --no-progress --exclude-loopback --exclude 'report-uri\.com' --exclude 'github\.com/.*/compare/' --exclude 'github\.com/.*/releases/tag/' --exclude 'github\.com/.*/actions/' --exclude 'pdos\.csail\.mit\.edu' --exclude 'css\.csail\.mit\.edu' '**/*.md'
args: >-
--no-progress --exclude-loopback
--exclude 'report-uri\.com'
--exclude 'github\.com/.*/compare/'
--exclude 'github\.com/.*/releases/tag/'
--exclude 'github\.com/.*/actions/'
--exclude 'pdos\.csail\.mit\.edu'
--exclude 'css\.csail\.mit\.edu'
'**/*.md'
fail: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

validate-skills:
name: Validate Skills
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Validate plugin skills
run: bash scripts/validate-skills.sh

- name: Check rule mirror sync
run: |
diff -r claude/ plugins/dev/rules/ && echo "Mirror in sync" || {
echo "plugins/dev/rules/ is out of sync with claude/ — run: cp claude/*.md plugins/dev/rules/"
diff -r claude/ plugins/dev/rules/ && echo "Mirror in sync" || {
echo "plugins/dev/rules/ is out of sync with claude/ — run: cp claude/*.md plugins/dev/rules/"
exit 1
}

secrets:
security:
name: Security
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0

- uses: gitleaks/gitleaks-action@v2
- name: gitleaks
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

ci-ok:
name: CI OK (All Green Gate)
runs-on: ubuntu-latest
if: always()
needs: [check-pr-size, lint, validate-skills, security]
steps:
- name: All checks passed
run: |
if echo '${{ toJSON(needs.*.result) }}' | grep -qE '"failure"|"cancelled"'; then
echo "One or more required jobs failed or were cancelled."
exit 1
fi
echo "All CI jobs passed."
29 changes: 29 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.27.2
hooks:
- id: gitleaks

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-json
- id: check-yaml
- id: check-toml
- id: check-merge-conflict
- id: check-added-large-files
args: [--maxkb=500]
- id: no-commit-to-branch
args: [--branch, master]

- repo: https://github.com/DavidAnson/markdownlint-cli2
rev: v0.17.2
hooks:
- id: markdownlint-cli2

- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args:
- --baseline
- .secrets.baseline
- --exclude-files
- (^|/)(pnpm-lock\.yaml|package-lock\.json|yarn\.lock|uv\.lock)$

- repo: local
hooks:
- id: validate-skills
Expand Down
43 changes: 43 additions & 0 deletions .secrets.baseline
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"version": "1.5.0",
"plugins_used": [
{"name": "ArtifactoryDetector"},
{"name": "AWSKeyDetector"},
{"name": "AzureStorageKeyDetector"},
{"name": "Base64HighEntropyString", "limit": 4.5},
{"name": "BasicAuthDetector"},
{"name": "CloudantDetector"},
{"name": "DiscordBotTokenDetector"},
{"name": "GitHubTokenDetector"},
{"name": "HexHighEntropyString", "limit": 3.0},
{"name": "IbmCloudIamDetector"},
{"name": "IbmCosHmacDetector"},
{"name": "JwtTokenDetector"},
{"name": "KeywordDetector", "keyword_exclude": ""},
{"name": "MailchimpDetector"},
{"name": "NpmDetector"},
{"name": "PrivateKeyDetector"},
{"name": "SendGridDetector"},
{"name": "SlackDetector"},
{"name": "SoftlayerDetector"},
{"name": "SquareOAuthDetector"},
{"name": "StripeDetector"},
{"name": "TwilioKeyDetector"}
],
"filters_used": [
{"path": "detect_secrets.filters.allowlist.is_line_allowlisted"},
{"path": "detect_secrets.filters.common.is_baseline_file", "filename": ".secrets.baseline"},
{"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", "min_level": 2},
{"path": "detect_secrets.filters.heuristic.is_indirect_reference"},
{"path": "detect_secrets.filters.heuristic.is_likely_id_secret"},
{"path": "detect_secrets.filters.heuristic.is_lock_file"},
{"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"},
{"path": "detect_secrets.filters.heuristic.is_potential_uuid"},
{"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"},
{"path": "detect_secrets.filters.heuristic.is_sequential_string"},
{"path": "detect_secrets.filters.heuristic.is_swagger_file"},
{"path": "detect_secrets.filters.heuristic.is_templated_secret"}
],
"results": {},
"generated_at": "2026-06-10T00:00:00Z"
}
Loading