@@ -9,77 +9,91 @@ permissions:
99 contents : read
1010
1111jobs :
12- # Prepare matrix for per-skill parallelization
1312 prepare :
1413 runs-on : ubuntu-latest
1514 if : github.event.issue.pull_request && contains(github.event.comment.body, '/test')
1615 outputs :
17- matrix : ${{ steps.generate- matrix.outputs.matrix }}
16+ matrix : ${{ steps.matrix.outputs.result }}
1817
1918 steps :
2019 - uses : actions/checkout@v4
21-
2220 - uses : astral-sh/setup-uv@v5
2321
24- - name : Detect skills and generate matrix
25- id : generate- matrix
22+ - name : Generate evaluation matrix
23+ id : matrix
2624 env :
2725 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
28- run : |
29- # Detect which skills changed
30- MODIFIED_SKILLS=$(python3 ci/detect_changes.py ${{ github.event.issue.number }} 2>/dev/null || echo "")
31- echo "Modified skills: $MODIFIED_SKILLS"
32-
33- # Extract filter from comment
34- COMMENT="${{ github.event.comment.body }}"
35- FILTER="all"
36- if echo "$COMMENT" | grep -q "/test copilot"; then
37- FILTER="copilot"
38- elif echo "$COMMENT" | grep -q "/test ollama"; then
39- FILTER="ollama"
40- elif echo "$COMMENT" | grep -q "/test gemini"; then
41- FILTER="gemini"
42- fi
43-
44- # Generate base matrix from config
45- BASE_MATRIX=$(uv run --with pyyaml ci/matrix_generator.py --filter-provider "$FILTER")
46-
47- # Expand matrix: one job per skill per model
48- python3 << 'PYTHON_EOF'
49- import json
50- import sys
51-
52- base_matrix_str = """$BASE_MATRIX"""
53- modified_skills_str = """$MODIFIED_SKILLS"""
54-
55- base_matrix = json.loads(base_matrix_str)
56- modified_skills = modified_skills_str.strip().split() if modified_skills_str.strip() else []
57-
58- matrix = {"include": []}
59-
60- if modified_skills and modified_skills[0]:
61- # Per-skill parallelization
62- for item in base_matrix["include"]:
63- for skill in modified_skills:
64- new_item = item.copy()
65- new_item["skill"] = skill
66- new_item["display_name"] = f"{item['display_name']} / {skill}"
67- matrix["include"].append(new_item)
68- else:
69- # Test all skills
70- matrix = base_matrix
71-
72- print(json.dumps(matrix, indent=2))
73- with open('/tmp/matrix.json', 'w') as f:
74- json.dump(matrix, f)
75- PYTHON_EOF
76-
77- MATRIX=$(cat /tmp/matrix.json)
78- echo "matrix<<EOF" >> $GITHUB_OUTPUT
79- echo "$MATRIX" >> $GITHUB_OUTPUT
80- echo "EOF" >> $GITHUB_OUTPUT
81-
82- # Run evaluations - one per skill per model (parallel)
26+ run : python3 << 'EOF_PYTHON'
27+ import subprocess
28+ import json
29+ import sys
30+ import os
31+
32+ # Detect modified skills
33+ result = subprocess.run(
34+ ["python3", "ci/detect_changes.py", "${{ github.event.issue.number }}"],
35+ capture_output=True, text=True
36+ )
37+ modified_skills = result.stdout.strip().split() if result.stdout.strip() else []
38+ print(f"Modified skills : {modified_skills}")
39+
40+ # Extract filter from comment
41+ comment = "${{ github.event.comment.body }}"
42+ filter_provider = "all"
43+ if "/test copilot" in comment :
44+ filter_provider = "copilot"
45+ elif "/test ollama" in comment :
46+ filter_provider = "ollama"
47+ elif "/test gemini" in comment :
48+ filter_provider = "gemini"
49+
50+ # Generate base matrix
51+ result = subprocess.run(
52+ ["uv", "run", "--with", "pyyaml", "ci/matrix_generator.py", "--filter-provider", filter_provider],
53+ capture_output=True, text=True, stderr=subprocess.DEVNULL
54+ )
55+
56+ if result.returncode != 0 :
57+ print(f"Error generating matrix : {result.stderr}", file=sys.stderr)
58+ sys.exit(1)
59+
60+ # Extract JSON from output (skip non-JSON lines)
61+ lines = result.stdout.strip().split('\n')
62+ json_lines = [l for l in lines if l.startswith('{') or l.startswith('[')]
63+ json_str = '\n'.join(json_lines)
64+
65+ try :
66+ base_matrix = json.loads(json_str)
67+ except json.JSONDecodeError as e :
68+ print(f"Error parsing matrix JSON : {e}", file=sys.stderr)
69+ print(f"Output was : {result.stdout}", file=sys.stderr)
70+ sys.exit(1)
71+
72+ # Expand matrix if skills were modified
73+ matrix = {"include" : []}
74+
75+ if modified_skills and modified_skills[0] :
76+ print(f"Expanding matrix : {len(modified_skills)} skills × {len(base_matrix['include'])} models")
77+ for item in base_matrix["include"] :
78+ for skill in modified_skills :
79+ new_item = item.copy()
80+ new_item["skill"] = skill
81+ new_item["display_name"] = f"{item['display_name']} / {skill}"
82+ matrix["include"].append(new_item)
83+ else :
84+ print(f"Testing all skills : {len(base_matrix['include'])} models")
85+ matrix = base_matrix
86+
87+ # Output matrix
88+ matrix_json = json.dumps(matrix)
89+ print(f"Generated {len(matrix['include'])} jobs")
90+ print(f"result={matrix_json}")
91+
92+ with open(os.environ.get('GITHUB_OUTPUT', ''), 'a') as f :
93+ f.write(f"result={matrix_json}\n")
94+
95+ EOF_PYTHON
96+
8397 evaluate :
8498 needs : prepare
8599 runs-on : ubuntu-latest
@@ -89,41 +103,31 @@ jobs:
89103
90104 steps :
91105 - uses : actions/checkout@v4
92-
93106 - uses : actions/setup-node@v4
94107 with :
95108 node-version : ' 20'
96-
97109 - name : Install Copilot CLI
98110 if : matrix.provider == 'copilot'
99111 run : npm install -g @github/copilot
100-
101112 - uses : astral-sh/setup-uv@v5
102113 with :
103114 enable-cache : true
104115
105- - name : Run evaluation for ${{ matrix.display_name }}
116+ - name : Evaluate ${{ matrix.display_name }}
106117 env :
107118 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
108119 COPILOT_GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
109120 OLLAMA_API_KEY : ${{ secrets.OLLAMA_API_KEY }}
110121 run : |
111122 CMD="uv run --project tests --frozen tests/evaluator.py"
112- CMD="$CMD --provider ${{ matrix.provider }}"
113- CMD="$CMD --model '${{ matrix.model }}'"
123+ CMD="$CMD --provider ${{ matrix.provider }} --model '${{ matrix.model }}'"
114124 CMD="$CMD --judge --verbose --report --threshold 50"
115-
116- if [ -n "${{ matrix.extra_args }}" ]; then
117- CMD="$CMD ${{ matrix.extra_args }}"
118- fi
119-
125+ [ -n "${{ matrix.extra_args }}" ] && CMD="$CMD ${{ matrix.extra_args }}"
120126 if [ -n "${{ matrix.skill }}" ]; then
121127 CMD="$CMD --skill ${{ matrix.skill }}"
122128 else
123129 CMD="$CMD --all"
124130 fi
125-
126- echo "Running: $CMD"
127131 eval "$CMD"
128132
129133 - name : Upload results
@@ -134,47 +138,21 @@ jobs:
134138 path : tests/results/
135139 retention-days : 1
136140
137- # Consolidate and post results
138141 consolidate :
139142 needs : evaluate
140143 runs-on : ubuntu-latest
141144 if : always()
142145
143146 steps :
144147 - uses : actions/checkout@v4
145-
146148 - uses : actions/download-artifact@v4
147149 with :
148150 path : tests/results/
149151
150- - name : Generate results comment
151- run : |
152- python3 << 'PYTHON_EOF'
153- from pathlib import Path
154- import json
155-
156- # Find all summary.json files
157- results_base = Path("tests/results")
158- comment = "## 📊 Skill Evaluation Results\n\n"
159-
160- summaries = list(results_base.glob("*/summary.json"))
161-
162- if summaries:
163- for summary_file in sorted(summaries):
164- try:
165- summary = json.loads(summary_file.read_text())
166- rel_path = str(summary_file.parent.relative_to(results_base))
167- comment += f"### {rel_path}\n"
168- comment += f"```json\n{json.dumps(summary, indent=2)}\n```\n\n"
169- except:
170- pass
171- else:
172- comment += "No results found.\n"
173-
174- Path("comment.md").write_text(comment)
175- PYTHON_EOF
176-
177- - name : Post results
152+ - name : Consolidate results
153+ run : python3 ci/consolidate_results.py || echo "# Results pending" > comment.md
154+
155+ - name : Post to PR
178156 uses : marocchino/sticky-pull-request-comment@v2
179157 if : hashFiles('comment.md') != ''
180158 with :
0 commit comments