🎨 Palette: [UX improvement] Surface background process errors to user #26
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: Minimal CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # Cancel in-progress runs for same PR | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| test: | |
| name: Tests | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| python-version: ['3.10', '3.11', '3.12'] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install pytest-cov | |
| pip install -e ".[dev]" | |
| - name: Run tests | |
| run: | | |
| pytest tests/ -xvs --tb=short --cov=src --cov-report=xml | |
| env: | |
| PYTHONPATH: src | |
| - name: Upload coverage | |
| uses: codecov/codecov-action@v4 | |
| if: matrix.python-version == '3.11' | |
| with: | |
| files: ./coverage.xml | |
| flags: unittests | |
| name: codecov-umbrella | |
| fail_ci_if_error: false | |
| security: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| cache: 'pip' | |
| - name: Install security tools | |
| run: | | |
| python -m pip install bandit safety pip-audit | |
| - name: Run Bandit (SAST) | |
| id: bandit | |
| run: | | |
| bandit -r src/ -f json -o bandit-report.json || true | |
| continue-on-error: true | |
| - name: Run Safety (Dependency Check) | |
| id: safety | |
| run: | | |
| # The safety command might output non-json warnings to stdout before the JSON output | |
| output=$(safety check --json || true) | |
| # Use Node.js to safely extract the JSON object from stdin to avoid template literal escaping issues | |
| echo "$output" | node -e " | |
| const text = require('fs').readFileSync(0, 'utf-8'); | |
| const start = text.indexOf('{'); | |
| const end = text.lastIndexOf('}'); | |
| if (start !== -1 && end !== -1 && end > start) { | |
| console.log(text.substring(start, end + 1)); | |
| } else { | |
| console.log('{}'); | |
| } | |
| " > safety-report.json | |
| continue-on-error: true | |
| - name: Run Pip-audit | |
| id: pip-audit | |
| run: | | |
| pip-audit --desc --format json > pip-audit-report.json || true | |
| if [ ! -s pip-audit-report.json ]; then echo "{}" > pip-audit-report.json; fi | |
| continue-on-error: true | |
| - name: Parse security reports | |
| run: | | |
| python << 'EOF' | |
| import json | |
| import sys | |
| # Bandit | |
| try: | |
| with open('bandit-report.json') as f: | |
| bandit = json.load(f) | |
| print(f"Bandit: {bandit.get('metrics', {}).get('SEVERITY.HIGH', 0)} HIGH issues") | |
| except: | |
| pass | |
| # Safety | |
| try: | |
| with open('safety-report.json') as f: | |
| safety = json.load(f) | |
| vulns = safety.get('vulnerabilities', []) | |
| print(f"Safety: {len(vulns)} known vulnerabilities") | |
| except: | |
| pass | |
| # Pip-audit | |
| try: | |
| with open('pip-audit-report.json') as f: | |
| pip_audit = json.load(f) | |
| deps = pip_audit.get('dependencies', []) | |
| vuln_deps = [d for d in deps if d.get('vulnerabilities')] | |
| print(f"Pip-audit: {len(vuln_deps)} vulnerable packages") | |
| except: | |
| pass | |
| EOF | |
| - name: Upload security reports | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: security-reports | |
| path: | | |
| bandit-report.json | |
| safety-report.json | |
| pip-audit-report.json | |
| - name: Comment PR with security results | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const parseReport = (file) => { | |
| try { | |
| if (!fs.existsSync(file)) return {}; | |
| const content = fs.readFileSync(file, 'utf8').trim(); | |
| return content ? JSON.parse(content) : {}; | |
| } catch (e) { | |
| return {}; | |
| } | |
| }; | |
| const bandit = parseReport('bandit-report.json'); | |
| const safety = parseReport('safety-report.json'); | |
| const pipAudit = parseReport('pip-audit-report.json'); | |
| let body = '## 🔒 Security Scan Results\n\n'; | |
| body += '### Bandit (SAST)\n'; | |
| body += `- ${(bandit.metrics && bandit.metrics['SEVERITY.HIGH']) || 0} HIGH severity issues\n\n`; | |
| body += '### Safety (Dependencies)\n'; | |
| body += `- ${(safety.vulnerabilities && safety.vulnerabilities.length) || 0} known vulnerabilities\n\n`; | |
| body += '### Pip-audit (Dependencies)\n'; | |
| body += `- ${(pipAudit.dependencies && pipAudit.dependencies.filter(d => d.vulnerabilities && d.vulnerabilities.length).length) || 0} vulnerable packages\n`; | |
| // Use core.summary to avoid GITHUB_TOKEN write permission issues on forks | |
| await core.summary | |
| .addRaw(body) | |
| .write(); | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| lint: | |
| name: Code Quality | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| cache: 'pip' | |
| - name: Install linting tools | |
| run: | | |
| python -m pip install ruff black mypy | |
| - name: Run Ruff (Fast Python Linter) | |
| run: | | |
| ruff check src/ tests/ --output-format=github | |
| continue-on-error: true | |
| - name: Check formatting (Black) | |
| run: | | |
| black --check src/ tests/ --diff | |
| continue-on-error: true | |
| - name: Type checking (MyPy) | |
| run: | | |
| mypy src/ --ignore-missing-imports || true | |
| continue-on-error: true | |
| build: | |
| name: Build Test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -e ".[gguf]" | |
| - name: Verify import | |
| run: | | |
| python -c "import ledgermind; print('✓ Imports work')" | |
| - name: Create distribution | |
| run: | | |
| pip install build | |
| python -m build |