|
| 1 | +name: CI |
| 2 | + |
| 3 | +on: |
| 4 | + push: |
| 5 | + branches: [main] |
| 6 | + pull_request: |
| 7 | + branches: [main] |
| 8 | + |
| 9 | +jobs: |
| 10 | + lint: |
| 11 | + runs-on: ubuntu-latest |
| 12 | + steps: |
| 13 | + - uses: actions/checkout@v4 |
| 14 | + |
| 15 | + - uses: actions/setup-python@v5 |
| 16 | + with: |
| 17 | + python-version: "3.13" |
| 18 | + cache: pip |
| 19 | + |
| 20 | + - name: Install dependencies |
| 21 | + run: | |
| 22 | + pip install -r requirements.txt |
| 23 | + pip install ruff==0.9.* |
| 24 | +
|
| 25 | + - name: Lint with ruff |
| 26 | + run: ruff check summary.py |
| 27 | + |
| 28 | + validate-action: |
| 29 | + runs-on: ubuntu-latest |
| 30 | + steps: |
| 31 | + - uses: actions/checkout@v4 |
| 32 | + |
| 33 | + - uses: actions/setup-python@v5 |
| 34 | + with: |
| 35 | + python-version: "3.13" |
| 36 | + |
| 37 | + - name: Install PyYAML |
| 38 | + run: pip install pyyaml |
| 39 | + |
| 40 | + - name: Validate action.yml structure |
| 41 | + run: python -c "import yaml; yaml.safe_load(open('action.yml'))" |
| 42 | + |
| 43 | + - name: Verify prompt templates exist |
| 44 | + run: | |
| 45 | + for f in triage.md triage_tiered.md summarize_file.md check_style.md synthesize_summary.md refine_summary.md; do |
| 46 | + test -f "prompts/$f" || { echo "Missing prompt template: $f"; exit 1; } |
| 47 | + done |
| 48 | +
|
| 49 | + - name: Verify action.yml env vars match summary.py expectations |
| 50 | + run: | |
| 51 | + python -c " |
| 52 | + import yaml, re |
| 53 | +
|
| 54 | + with open('action.yml') as f: |
| 55 | + action = yaml.safe_load(f) |
| 56 | +
|
| 57 | + # Collect env vars the action passes to summary.py |
| 58 | + steps = action['runs']['steps'] |
| 59 | + summary_step = [s for s in steps if 'summary.py' in s.get('run', '')] |
| 60 | + assert summary_step, 'Could not find summary.py step in action.yml' |
| 61 | + env_vars = set(summary_step[0].get('env', {}).keys()) |
| 62 | +
|
| 63 | + # Collect env vars summary.py reads |
| 64 | + with open('summary.py') as f: |
| 65 | + src = f.read() |
| 66 | + # Match os.environ.get/os.environ[]/os.getenv patterns |
| 67 | + read_vars = set(re.findall(r'os\.environ(?:\.get)?\(?[\"\\']([A-Z_]+)', src)) |
| 68 | + read_vars |= set(re.findall(r'os\.getenv\([\"\\']([A-Z_]+)', src)) |
| 69 | +
|
| 70 | + # GITHUB_TOKEN is checked via 'in os.environ' — also capture that |
| 71 | + read_vars |= set(re.findall(r'[\"\\']([A-Z_]+)[\"\\'] in os\.environ', src)) |
| 72 | +
|
| 73 | + # Every var summary.py reads should be provided by action.yml |
| 74 | + missing = read_vars - env_vars |
| 75 | + if missing: |
| 76 | + print(f'FAIL: summary.py reads env vars not provided by action.yml: {missing}') |
| 77 | + exit(1) |
| 78 | + print(f'OK: all {len(read_vars)} env vars summary.py reads are provided by action.yml') |
| 79 | + " |
| 80 | +
|
| 81 | + dry-run: |
| 82 | + runs-on: ubuntu-latest |
| 83 | + steps: |
| 84 | + - uses: actions/checkout@v4 |
| 85 | + |
| 86 | + - uses: actions/setup-python@v5 |
| 87 | + with: |
| 88 | + python-version: "3.13" |
| 89 | + cache: pip |
| 90 | + |
| 91 | + - name: Install dependencies |
| 92 | + run: pip install -r requirements.txt |
| 93 | + |
| 94 | + - name: Create minimal test diff |
| 95 | + run: | |
| 96 | + cat > pr.diff << 'DIFF' |
| 97 | + diff --git a/Test.lean b/Test.lean |
| 98 | + --- a/Test.lean |
| 99 | + +++ b/Test.lean |
| 100 | + @@ -1,3 +1,4 @@ |
| 101 | + import Mathlib |
| 102 | + +theorem test_thm : True := sorry |
| 103 | + def foo := 1 |
| 104 | + def bar := 2 |
| 105 | + DIFF |
| 106 | +
|
| 107 | + - name: Verify core logic without API calls |
| 108 | + run: | |
| 109 | + python -c " |
| 110 | + import summary |
| 111 | +
|
| 112 | + # --- DiffAnalyzer --- |
| 113 | + analyzer = summary.DiffAnalyzer(['def', 'theorem', 'lemma']) |
| 114 | + with open('pr.diff') as f: |
| 115 | + diff = f.read() |
| 116 | + stats, added, removed, affected, ad, rd, afd, warnings = analyzer.analyze(diff) |
| 117 | + assert stats['files_changed'] == 1, f'Expected 1 file, got {stats[\"files_changed\"]}' |
| 118 | + assert stats['lines_added'] >= 1, 'Expected at least 1 line added' |
| 119 | + print('DiffAnalyzer: OK') |
| 120 | +
|
| 121 | + # --- split_diff_into_files --- |
| 122 | + files = summary.split_diff_into_files(diff) |
| 123 | + assert 'Test.lean' in files, f'Expected Test.lean in files, got {list(files.keys())}' |
| 124 | + print('split_diff_into_files: OK') |
| 125 | +
|
| 126 | + # --- Config fingerprint --- |
| 127 | + fp1 = summary._compute_config_fingerprint('model-a', 'prompt-a') |
| 128 | + fp2 = summary._compute_config_fingerprint('model-b', 'prompt-a') |
| 129 | + fp3 = summary._compute_config_fingerprint('model-a', 'prompt-b') |
| 130 | + assert fp1 != fp2, 'Fingerprint should change when model changes' |
| 131 | + assert fp1 != fp3, 'Fingerprint should change when prompt changes' |
| 132 | + print('Config fingerprint: OK') |
| 133 | +
|
| 134 | + # --- Prompt templates load --- |
| 135 | + for name in ['triage.md', 'triage_tiered.md', 'summarize_file.md', 'check_style.md', 'synthesize_summary.md', 'refine_summary.md']: |
| 136 | + t = summary._read_prompt_template(name) |
| 137 | + assert len(t) > 0, f'Prompt template {name} is empty' |
| 138 | + print('Prompt templates: OK') |
| 139 | +
|
| 140 | + # --- Title validation --- |
| 141 | + ok, t, msg = summary.validate_pr_title('feat(Sumcheck): add completeness proof') |
| 142 | + assert ok and t == 'feat', f'Expected valid feat, got {ok}, {t}' |
| 143 | + ok3, t3, msg3 = summary.validate_pr_title('fix: correct off-by-one') |
| 144 | + assert ok3 and t3 == 'fix', f'Expected valid fix without scope, got {ok3}, {t3}' |
| 145 | + ok2, t2, msg2 = summary.validate_pr_title('random title') |
| 146 | + assert not ok2 and msg2, f'Expected invalid, got {ok2}' |
| 147 | + print('Title validation: OK') |
| 148 | +
|
| 149 | + # --- Sorry delta formatting --- |
| 150 | + delta = summary._format_sorry_delta(['a'], ['b', 'c']) |
| 151 | + assert 'delta: -1' in delta, f'Expected negative delta in: {delta}' |
| 152 | + empty = summary._format_sorry_delta([], []) |
| 153 | + assert empty == '', 'Expected empty string for no sorries' |
| 154 | + print('Sorry delta: OK') |
| 155 | +
|
| 156 | + print('All checks passed.') |
| 157 | + " |
0 commit comments