feat: enhance container environment isolation via credential proxy #307
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Skill PR Validation | |
| on: | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - '.claude/skills/**' | |
| - 'skills-engine/**' | |
| jobs: | |
| # ── Job 1: Policy gate ──────────────────────────────────────────────── | |
| # Block PRs that add NEW skill files while also modifying source code. | |
| # Skill PRs should contain instructions for Claude, not raw source edits. | |
| policy-check: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for mixed skill + source changes | |
| run: | | |
| ADDED_SKILLS=$(git diff --name-only --diff-filter=A origin/main...HEAD \ | |
| | grep '^\\.claude/skills/' || true) | |
| CHANGED=$(git diff --name-only origin/main...HEAD) | |
| SOURCE=$(echo "$CHANGED" \ | |
| | grep -E '^src/|^container/|^package\.json|^package-lock\.json' || true) | |
| if [ -n "$ADDED_SKILLS" ] && [ -n "$SOURCE" ]; then | |
| echo "::error::PRs that add new skills should not modify source files." | |
| echo "" | |
| echo "New skill files:" | |
| echo "$ADDED_SKILLS" | |
| echo "" | |
| echo "Source files:" | |
| echo "$SOURCE" | |
| echo "" | |
| echo "Please split into separate PRs. See CONTRIBUTING.md." | |
| exit 1 | |
| fi | |
| - name: Comment on failure | |
| if: failure() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: `This PR adds a skill while also modifying source code. A skill PR should not change source files—the skill should contain **instructions** for Claude to follow. | |
| If you're fixing a bug or simplifying code, please submit that as a separate PR. | |
| See [CONTRIBUTING.md](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/CONTRIBUTING.md) for details.` | |
| }) | |
| # ── Job 2: Detect which skills changed ──────────────────────────────── | |
| detect-changed: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| skills: ${{ steps.detect.outputs.skills }} | |
| has_skills: ${{ steps.detect.outputs.has_skills }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changed skills | |
| id: detect | |
| run: | | |
| CHANGED_SKILLS=$(git diff --name-only origin/main...HEAD \ | |
| | grep '^\\.claude/skills/' \ | |
| | sed 's|^\.claude/skills/||' \ | |
| | cut -d/ -f1 \ | |
| | sort -u \ | |
| | jq -R . | jq -s .) | |
| echo "skills=$CHANGED_SKILLS" >> "$GITHUB_OUTPUT" | |
| if [ "$CHANGED_SKILLS" = "[]" ]; then | |
| echo "has_skills=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_skills=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "Changed skills: $CHANGED_SKILLS" | |
| # ── Job 3: Validate each changed skill in isolation ─────────────────── | |
| validate-skills: | |
| needs: detect-changed | |
| if: needs.detect-changed.outputs.has_skills == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| skill: ${{ fromJson(needs.detect-changed.outputs.skills) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - run: npm ci | |
| - name: Initialize skills system | |
| run: >- | |
| npx tsx -e | |
| "import { initNanoclawDir } from './skills-engine/index'; initNanoclawDir();" | |
| - name: Apply skill | |
| run: npx tsx scripts/apply-skill.ts ".claude/skills/${{ matrix.skill }}" | |
| - name: Typecheck after apply | |
| run: npx tsc --noEmit | |
| - name: Run skill tests | |
| run: | | |
| TEST_CMD=$(npx tsx -e " | |
| import { parse } from 'yaml'; | |
| import fs from 'fs'; | |
| const m = parse(fs.readFileSync('.claude/skills/${{ matrix.skill }}/manifest.yaml', 'utf-8')); | |
| if (m.test) console.log(m.test); | |
| ") | |
| if [ -n "$TEST_CMD" ]; then | |
| echo "Running: $TEST_CMD" | |
| eval "$TEST_CMD" | |
| else | |
| echo "No test command defined, skipping" | |
| fi | |
| # ── Summary gate for branch protection ──────────────────────────────── | |
| skill-validation-summary: | |
| needs: | |
| - policy-check | |
| - detect-changed | |
| - validate-skills | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check results | |
| run: | | |
| echo "policy-check: ${{ needs.policy-check.result }}" | |
| echo "validate-skills: ${{ needs.validate-skills.result }}" | |
| if [ "${{ needs.policy-check.result }}" = "failure" ]; then | |
| echo "::error::Policy check failed" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.validate-skills.result }}" = "failure" ]; then | |
| echo "::error::Skill validation failed" | |
| exit 1 | |
| fi | |
| echo "All skill checks passed" |