pr-checks.yml 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. name: PR Checks
  2. on:
  3. pull_request:
  4. branches: [main, dev]
  5. types: [opened, edited, synchronize, reopened]
  6. permissions:
  7. contents: read
  8. jobs:
  9. pr-title-check:
  10. name: Validate PR Title
  11. runs-on: ubuntu-latest
  12. outputs:
  13. title-valid: ${{ steps.validate.outputs.valid }}
  14. steps:
  15. - name: Check PR title format
  16. id: validate
  17. uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
  18. with:
  19. script: |
  20. const prTitle = context.payload.pull_request.title;
  21. // Conventional commit patterns
  22. const patterns = {
  23. feat: /^feat(\(.+\))?!?:\s.+/,
  24. fix: /^fix(\(.+\))?!?:\s.+/,
  25. docs: /^docs(\(.+\))?:\s.+/,
  26. style: /^style(\(.+\))?:\s.+/,
  27. refactor: /^refactor(\(.+\))?:\s.+/,
  28. perf: /^perf(\(.+\))?:\s.+/,
  29. test: /^test(\(.+\))?:\s.+/,
  30. chore: /^chore(\(.+\))?:\s.+/,
  31. ci: /^ci(\(.+\))?:\s.+/,
  32. build: /^build(\(.+\))?:\s.+/,
  33. revert: /^revert(\(.+\))?:\s.+/,
  34. alpha: /^\[alpha\]\s.+/,
  35. beta: /^\[beta\]\s.+/,
  36. rc: /^\[rc\]\s.+/
  37. };
  38. // Check if title matches any pattern
  39. const matchedType = Object.entries(patterns).find(([type, pattern]) =>
  40. pattern.test(prTitle)
  41. );
  42. if (!matchedType) {
  43. const validExamples = [
  44. '✅ feat(evals): add new evaluator',
  45. '✅ fix(agents): correct delegation logic',
  46. '✅ docs(readme): update installation guide',
  47. '✅ test(evals): add execution-balance tests',
  48. '✅ chore(deps): update dependencies',
  49. '✅ feat!: breaking API change',
  50. '✅ [alpha] experimental feature'
  51. ];
  52. const message = `
  53. ## ❌ Invalid PR Title Format
  54. **Current title:** \`${prTitle}\`
  55. ### Required Format
  56. PR titles must follow [Conventional Commits](https://www.conventionalcommits.org/) format:
  57. \`\`\`
  58. <type>(<scope>): <description>
  59. \`\`\`
  60. ### Valid Types
  61. - **feat** - New feature (triggers minor version bump: 0.3.0 → 0.4.0)
  62. - **fix** - Bug fix (triggers patch version bump: 0.3.0 → 0.3.1)
  63. - **docs** - Documentation changes (triggers patch bump)
  64. - **test** - Test additions/changes (triggers patch bump)
  65. - **refactor** - Code refactoring (triggers patch bump)
  66. - **chore** - Maintenance tasks (triggers patch bump)
  67. - **ci** - CI/CD changes (triggers patch bump)
  68. - **perf** - Performance improvements (triggers patch bump)
  69. - **style** - Code style changes (triggers patch bump)
  70. - **build** - Build system changes (triggers patch bump)
  71. - **revert** - Revert previous commit (triggers patch bump)
  72. ### Breaking Changes
  73. - **feat!:** or **fix!:** - Breaking change (triggers major version bump: 0.3.0 → 1.0.0)
  74. - **BREAKING CHANGE:** in description
  75. ### Pre-release Tags
  76. - **[alpha]** - Alpha release (0.3.0 → 0.3.1-alpha.1)
  77. - **[beta]** - Beta release (0.3.0 → 0.3.1-beta.1)
  78. - **[rc]** - Release candidate (0.3.0 → 0.3.1-rc.1)
  79. ### Valid Examples
  80. ${validExamples.map(ex => `- ${ex}`).join('\n')}
  81. ### Why This Matters
  82. - ✅ Enables automatic semantic versioning
  83. - ✅ Generates meaningful changelogs
  84. - ✅ Makes commit history searchable
  85. - ✅ Clarifies the impact of changes
  86. ### How to Fix
  87. Edit your PR title to match the format above.
  88. `;
  89. core.info(message);
  90. await core.summary.addRaw(message).write();
  91. core.setFailed(message);
  92. } else {
  93. const [type] = matchedType;
  94. let versionBump = 'patch (0.3.0 → 0.3.1)';
  95. if (type === 'feat' && prTitle.includes('!')) {
  96. versionBump = 'major (0.3.0 → 1.0.0) - BREAKING CHANGE';
  97. } else if (type === 'feat') {
  98. versionBump = 'minor (0.3.0 → 0.4.0)';
  99. } else if (type === 'fix' && prTitle.includes('!')) {
  100. versionBump = 'major (0.3.0 → 1.0.0) - BREAKING CHANGE';
  101. } else if (type === 'alpha') {
  102. versionBump = 'alpha (0.3.0 → 0.3.1-alpha.1)';
  103. } else if (type === 'beta') {
  104. versionBump = 'beta (0.3.0 → 0.3.1-beta.1)';
  105. } else if (type === 'rc') {
  106. versionBump = 'rc (0.3.0 → 0.3.1-rc.1)';
  107. }
  108. const message = `
  109. ## ✅ PR Title Valid
  110. **Title:** \`${prTitle}\`
  111. **Type:** \`${type}\`
  112. **Version bump:** ${versionBump}
  113. When this PR is merged using **"Squash and Merge"**, the version will be automatically bumped.
  114. `;
  115. core.info(message);
  116. // Set output for summary
  117. core.summary
  118. .addHeading('✅ PR Title Validation Passed', 2)
  119. .addRaw(`**Title:** \`${prTitle}\``)
  120. .addBreak()
  121. .addRaw(`**Type:** \`${type}\``)
  122. .addBreak()
  123. .addRaw(`**Version bump:** ${versionBump}`)
  124. .write();
  125. // Set output for dependent jobs
  126. core.setOutput('valid', 'true');
  127. }
  128. check-changes:
  129. name: Detect Changed Files
  130. runs-on: ubuntu-latest
  131. needs: pr-title-check
  132. if: needs.pr-title-check.outputs.title-valid == 'true'
  133. outputs:
  134. has-evals: ${{ steps.filter.outputs.has-evals }}
  135. has-docs: ${{ steps.filter.outputs.has-docs }}
  136. has-workflows: ${{ steps.filter.outputs.has-workflows }}
  137. steps:
  138. - name: Checkout code
  139. uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
  140. with:
  141. fetch-depth: 0
  142. persist-credentials: false
  143. - name: Setup Bun
  144. uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
  145. with:
  146. bun-version: 1.3.14
  147. - name: Check changed files
  148. id: filter
  149. run: |
  150. echo "Changed files:" >&2
  151. git diff --name-only -z "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}" |
  152. tee >(while IFS= read -r -d '' path; do printf ' %q\n' "$path" >&2; done) |
  153. bun run scripts/validation/detect-pr-changes.ts
  154. build-check:
  155. name: Build & Validate
  156. runs-on: ubuntu-latest
  157. timeout-minutes: 5
  158. needs: [pr-title-check, check-changes]
  159. if: |
  160. needs.pr-title-check.outputs.title-valid == 'true' &&
  161. needs.check-changes.outputs.has-evals == 'true'
  162. steps:
  163. - name: Checkout code
  164. uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
  165. with:
  166. persist-credentials: false
  167. - name: Setup Node.js
  168. uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
  169. with:
  170. node-version: '20'
  171. cache: 'npm'
  172. cache-dependency-path: 'evals/framework/package-lock.json'
  173. - name: Install dependencies
  174. working-directory: evals/framework
  175. run: npm ci
  176. - name: Build framework
  177. working-directory: evals/framework
  178. run: npm run build
  179. - name: Validate test suites
  180. working-directory: evals/framework
  181. run: npm run validate:suites:all
  182. - name: Run deterministic tests
  183. working-directory: evals/framework
  184. run: npm run test:ci
  185. - name: Summary
  186. if: success()
  187. run: |
  188. echo "## ✅ Build Check Passed" >> $GITHUB_STEP_SUMMARY
  189. echo "" >> $GITHUB_STEP_SUMMARY
  190. echo "- ✅ TypeScript compilation successful" >> $GITHUB_STEP_SUMMARY
  191. echo "- ✅ Test suite validation passed" >> $GITHUB_STEP_SUMMARY
  192. echo "- ✅ Deterministic tests passed (\`npm run test:ci\`)" >> $GITHUB_STEP_SUMMARY
  193. echo "" >> $GITHUB_STEP_SUMMARY
  194. echo "**Note:** \`test:ci\` runs the offline, deterministic Vitest allowlist only." >> $GITHUB_STEP_SUMMARY
  195. echo "Model- or network-dependent agent tests (\`eval:sdk\`, integration suites) are not run on PRs." >> $GITHUB_STEP_SUMMARY
  196. - name: Failure summary
  197. if: failure()
  198. run: |
  199. echo "## ❌ Build Check Failed" >> $GITHUB_STEP_SUMMARY
  200. echo "" >> $GITHUB_STEP_SUMMARY
  201. echo "Please check the logs above for details." >> $GITHUB_STEP_SUMMARY
  202. echo "" >> $GITHUB_STEP_SUMMARY
  203. echo "**Common fixes:**" >> $GITHUB_STEP_SUMMARY
  204. echo "- TypeScript errors: Fix type issues in \`evals/framework/src/\`" >> $GITHUB_STEP_SUMMARY
  205. echo "- YAML validation: Check test files in \`evals/agents/*/tests/\`" >> $GITHUB_STEP_SUMMARY
  206. summary:
  207. name: PR Checks Summary
  208. runs-on: ubuntu-latest
  209. needs: [pr-title-check, check-changes, build-check]
  210. if: always()
  211. steps:
  212. - name: Generate summary
  213. run: |
  214. echo "## 📊 PR Checks Summary" >> $GITHUB_STEP_SUMMARY
  215. echo "" >> $GITHUB_STEP_SUMMARY
  216. # PR Title Check
  217. if [ "${{ needs.pr-title-check.result }}" == "success" ]; then
  218. echo "✅ **PR Title:** Valid conventional commit format" >> $GITHUB_STEP_SUMMARY
  219. else
  220. echo "❌ **PR Title:** Invalid format - please fix" >> $GITHUB_STEP_SUMMARY
  221. fi
  222. # Changed Files Detection
  223. if [ "${{ needs.check-changes.result }}" == "success" ]; then
  224. echo "✅ **Changed Files:** Detected successfully" >> $GITHUB_STEP_SUMMARY
  225. if [ "${{ needs.check-changes.outputs.has-evals }}" == "true" ]; then
  226. echo " - 📦 Evals changes detected" >> $GITHUB_STEP_SUMMARY
  227. fi
  228. if [ "${{ needs.check-changes.outputs.has-docs }}" == "true" ]; then
  229. echo " - 📚 Docs changes detected" >> $GITHUB_STEP_SUMMARY
  230. fi
  231. if [ "${{ needs.check-changes.outputs.has-workflows }}" == "true" ]; then
  232. echo " - ⚙️ Workflow changes detected" >> $GITHUB_STEP_SUMMARY
  233. fi
  234. elif [ "${{ needs.check-changes.result }}" == "failure" ]; then
  235. echo "❌ **Changed Files:** Detection failed - check logs" >> $GITHUB_STEP_SUMMARY
  236. elif [ "${{ needs.check-changes.result }}" == "cancelled" ]; then
  237. echo "🚫 **Changed Files:** Detection cancelled" >> $GITHUB_STEP_SUMMARY
  238. elif [ "${{ needs.check-changes.result }}" == "skipped" ]; then
  239. echo "⏭️ **Changed Files:** Skipped (title validation failed)" >> $GITHUB_STEP_SUMMARY
  240. else
  241. echo "❓ **Changed Files:** Unknown result" >> $GITHUB_STEP_SUMMARY
  242. fi
  243. # Build Check
  244. if [ "${{ needs.build-check.result }}" == "success" ]; then
  245. echo "✅ **Build & Validate:** Passed" >> $GITHUB_STEP_SUMMARY
  246. elif [ "${{ needs.build-check.result }}" == "skipped" ]; then
  247. if [ "${{ needs.check-changes.result }}" != "success" ]; then
  248. echo "⏭️ **Build & Validate:** Skipped (change detection did not succeed)" >> $GITHUB_STEP_SUMMARY
  249. elif [ "${{ needs.check-changes.outputs.has-evals }}" != "true" ]; then
  250. echo "⏭️ **Build & Validate:** Skipped (no evals changes)" >> $GITHUB_STEP_SUMMARY
  251. else
  252. echo "⏭️ **Build & Validate:** Skipped (prerequisite not met)" >> $GITHUB_STEP_SUMMARY
  253. fi
  254. elif [ "${{ needs.build-check.result }}" == "failure" ]; then
  255. echo "❌ **Build & Validate:** Failed - check logs" >> $GITHUB_STEP_SUMMARY
  256. elif [ "${{ needs.build-check.result }}" == "cancelled" ]; then
  257. echo "🚫 **Build & Validate:** Cancelled" >> $GITHUB_STEP_SUMMARY
  258. fi
  259. echo "" >> $GITHUB_STEP_SUMMARY
  260. # Overall status
  261. if [ "${{ needs.pr-title-check.result }}" == "success" ] && \
  262. [ "${{ needs.check-changes.result }}" == "success" ] && \
  263. { [ "${{ needs.build-check.result }}" == "success" ] || \
  264. { [ "${{ needs.build-check.result }}" == "skipped" ] && \
  265. [ "${{ needs.check-changes.outputs.has-evals }}" != "true" ]; }; }; then
  266. echo "### ✅ All Required Checks Passed!" >> $GITHUB_STEP_SUMMARY
  267. echo "" >> $GITHUB_STEP_SUMMARY
  268. echo "This PR is ready for review." >> $GITHUB_STEP_SUMMARY
  269. else
  270. echo "### ❌ Some Checks Failed" >> $GITHUB_STEP_SUMMARY
  271. echo "" >> $GITHUB_STEP_SUMMARY
  272. echo "Please fix the failing checks before merging." >> $GITHUB_STEP_SUMMARY
  273. exit 1
  274. fi