feat(US0488, D0127): sprint next materialises the head charter agains… #545
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: Lint | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # The whole-corpus markdown lane is periodic and pre-release, never per-commit: the | |
| # pre-commit gate already runs ~197s against a 120s budget, and a guard whose cost is | |
| # paid on every commit gets switched off. | |
| schedule: | |
| - cron: '17 4 * * 1' | |
| workflow_dispatch: | |
| # Least privilege: the jobs only check out and run linters/tests - nothing writes to the | |
| # repo, so the workflow token needs read only (BG0058). Without this the token defaults to | |
| # the repository/org setting, which may be the legacy permissive token. | |
| permissions: | |
| contents: read | |
| jobs: | |
| ci: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Set up Python | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Install Python deps (before any Python run) | |
| # CR0207: `npm test` runs the config-driven tests, which fail (not skip) without PyYAML. | |
| # Install it on the pinned interpreter BEFORE the first Python invocation, so the suite | |
| # is not validated by accident of the runner image happening to preinstall PyYAML. | |
| # BG0107: pytest is a genuine dependency of the bench harness's class-D grader | |
| # (tools/bench/audit_quiz.py shells `python -m pytest` inside fixture workspaces). | |
| run: python -m pip install --quiet pyyaml pytest | |
| - name: Lint (markdown, style, links) | |
| run: npm run lint | |
| - name: Run script unit tests (tools/skill-tests.sh adds the green-run noise gate) | |
| run: | | |
| bash tools/skill-tests.sh | |
| python3 -m unittest discover -s tools/tests | |
| - name: Portable artefact gate (same gate the pre-commit hook runs) | |
| run: | | |
| # BG0096: the hook and CONTRIBUTING claimed CI re-runs this gate; now it does. | |
| python3 .claude/skills/sdlc-studio/scripts/gate.py --root . | |
| - name: Coverage gate (runtime scripts, >= 80%) | |
| run: | | |
| # PyYAML is required: the config-driven tests (provenance/validate adopt_after cutoff, | |
| # transition done-gate, conformance) read .config.yaml via config._yaml() and FAIL (not | |
| # skip) without it, which makes `coverage run` exit non-zero and fails this step before | |
| # the threshold is ever checked. Coverage itself is healthy (~82%). See US0047. | |
| python -m pip install --quiet coverage pyyaml | |
| coverage run --source=.claude/skills/sdlc-studio/scripts -m unittest discover -s .claude/skills/sdlc-studio/scripts/tests | |
| coverage report --omit='*/tests/*' --fail-under=80 | |
| - name: Python security scan (bandit) | |
| run: | | |
| python -m pip install --quiet bandit | |
| bandit -r .claude/skills/sdlc-studio/scripts -ll -x '*/tests/*' -q | |
| # The whole markdown corpus under the STRICT rule set - every tracked .md file, | |
| # dot-directories included. The per-commit lanes cannot see inside `.claude/` with a | |
| # `**/*.md` glob, so the payload is only ever linted under its own relaxed config; this | |
| # job is where a rule that config switches off is still enforced. Findings are attributed | |
| # against the latest tag, so the report says what the release introduced rather than | |
| # reciting the whole backlog. | |
| corpus: | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| with: | |
| # Full history and tags: the baseline is a tag, and attribution needs to | |
| # materialise it. A shallow clone would report every finding unattributed. | |
| fetch-depth: 0 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Markdown corpus lint (strict rules, attributed) | |
| run: npm run lint:corpus | |
| windows-smoke: | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - name: install.ps1 - list targets and dry run (offline) | |
| shell: pwsh | |
| run: | | |
| & ./install.ps1 -ListTargets | |
| & ./install.ps1 -Target all -DryRun | |
| - name: install.ps1 - real local install with stale-copy sweep | |
| shell: pwsh | |
| run: | | |
| # Pre-place a stale sdlc-studio copy where the sweep should find it. | |
| $stale = Join-Path $HOME '.gemini/skills/sdlc-studio' | |
| New-Item -ItemType Directory -Force (Join-Path $stale 'templates') | Out-Null | |
| Set-Content -Path (Join-Path $stale 'SKILL.md') -Value "---`nname: sdlc-studio`n---`n" | |
| Set-Content -Path (Join-Path $stale 'templates/version.yaml') -Value 'skill_version: "0.0.1"' | |
| $proj = Join-Path $env:RUNNER_TEMP 'sdlc-smoke' | |
| New-Item -ItemType Directory -Force $proj | Out-Null | |
| Push-Location $proj | |
| & "$env:GITHUB_WORKSPACE/install.ps1" -Target claude -Local | |
| if (-not (Test-Path '.claude/skills/sdlc-studio/SKILL.md')) { | |
| throw 'install.ps1 did not place SKILL.md' | |
| } | |
| # The sweep must have refreshed the stale gemini copy. | |
| $swept = Get-Content (Join-Path $stale 'templates/version.yaml') -Raw | |
| if ($swept -match '0\.0\.1') { | |
| throw 'sweep did not refresh the stale gemini copy' | |
| } | |
| Write-Host 'Windows install + sweep refresh OK' | |
| Pop-Location | |
| - name: install.ps1 - NoSweep leaves other copies alone | |
| shell: pwsh | |
| run: | | |
| # Reset the gemini copy to a stale version, install with -NoSweep, | |
| # and assert it was left untouched. | |
| $stale = Join-Path $HOME '.gemini/skills/sdlc-studio' | |
| Set-Content -Path (Join-Path $stale 'templates/version.yaml') -Value 'skill_version: "0.0.1"' | |
| $proj = Join-Path $env:RUNNER_TEMP 'sdlc-smoke-nosweep' | |
| New-Item -ItemType Directory -Force $proj | Out-Null | |
| Push-Location $proj | |
| & "$env:GITHUB_WORKSPACE/install.ps1" -Target claude -Local -NoSweep | |
| $kept = Get-Content (Join-Path $stale 'templates/version.yaml') -Raw | |
| if ($kept -notmatch '0\.0\.1') { | |
| throw 'NoSweep still modified the gemini copy' | |
| } | |
| Write-Host 'NoSweep opt-out OK' | |
| Pop-Location |