pr-checks.yml 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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@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@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@v4
  165. with:
  166. persist-credentials: false
  167. - name: Setup Node.js
  168. uses: actions/setup-node@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: Summary
  183. if: success()
  184. run: |
  185. echo "## ✅ Build Check Passed" >> $GITHUB_STEP_SUMMARY
  186. echo "" >> $GITHUB_STEP_SUMMARY
  187. echo "- ✅ TypeScript compilation successful" >> $GITHUB_STEP_SUMMARY
  188. echo "- ✅ Test suite validation passed" >> $GITHUB_STEP_SUMMARY
  189. echo "" >> $GITHUB_STEP_SUMMARY
  190. echo "**Note:** Full agent tests are not run on PRs to save time and costs." >> $GITHUB_STEP_SUMMARY
  191. echo "Maintainers can run \`npm run test:ci\` locally if needed." >> $GITHUB_STEP_SUMMARY
  192. - name: Failure summary
  193. if: failure()
  194. run: |
  195. echo "## ❌ Build Check Failed" >> $GITHUB_STEP_SUMMARY
  196. echo "" >> $GITHUB_STEP_SUMMARY
  197. echo "Please check the logs above for details." >> $GITHUB_STEP_SUMMARY
  198. echo "" >> $GITHUB_STEP_SUMMARY
  199. echo "**Common fixes:**" >> $GITHUB_STEP_SUMMARY
  200. echo "- TypeScript errors: Fix type issues in \`evals/framework/src/\`" >> $GITHUB_STEP_SUMMARY
  201. echo "- YAML validation: Check test files in \`evals/agents/*/tests/\`" >> $GITHUB_STEP_SUMMARY
  202. summary:
  203. name: PR Checks Summary
  204. runs-on: ubuntu-latest
  205. needs: [pr-title-check, check-changes, build-check]
  206. if: always()
  207. steps:
  208. - name: Generate summary
  209. run: |
  210. echo "## 📊 PR Checks Summary" >> $GITHUB_STEP_SUMMARY
  211. echo "" >> $GITHUB_STEP_SUMMARY
  212. # PR Title Check
  213. if [ "${{ needs.pr-title-check.result }}" == "success" ]; then
  214. echo "✅ **PR Title:** Valid conventional commit format" >> $GITHUB_STEP_SUMMARY
  215. else
  216. echo "❌ **PR Title:** Invalid format - please fix" >> $GITHUB_STEP_SUMMARY
  217. fi
  218. # Changed Files Detection
  219. if [ "${{ needs.check-changes.result }}" == "success" ]; then
  220. echo "✅ **Changed Files:** Detected successfully" >> $GITHUB_STEP_SUMMARY
  221. if [ "${{ needs.check-changes.outputs.has-evals }}" == "true" ]; then
  222. echo " - 📦 Evals changes detected" >> $GITHUB_STEP_SUMMARY
  223. fi
  224. if [ "${{ needs.check-changes.outputs.has-docs }}" == "true" ]; then
  225. echo " - 📚 Docs changes detected" >> $GITHUB_STEP_SUMMARY
  226. fi
  227. if [ "${{ needs.check-changes.outputs.has-workflows }}" == "true" ]; then
  228. echo " - ⚙️ Workflow changes detected" >> $GITHUB_STEP_SUMMARY
  229. fi
  230. elif [ "${{ needs.check-changes.result }}" == "failure" ]; then
  231. echo "❌ **Changed Files:** Detection failed - check logs" >> $GITHUB_STEP_SUMMARY
  232. elif [ "${{ needs.check-changes.result }}" == "cancelled" ]; then
  233. echo "🚫 **Changed Files:** Detection cancelled" >> $GITHUB_STEP_SUMMARY
  234. elif [ "${{ needs.check-changes.result }}" == "skipped" ]; then
  235. echo "⏭️ **Changed Files:** Skipped (title validation failed)" >> $GITHUB_STEP_SUMMARY
  236. else
  237. echo "❓ **Changed Files:** Unknown result" >> $GITHUB_STEP_SUMMARY
  238. fi
  239. # Build Check
  240. if [ "${{ needs.build-check.result }}" == "success" ]; then
  241. echo "✅ **Build & Validate:** Passed" >> $GITHUB_STEP_SUMMARY
  242. elif [ "${{ needs.build-check.result }}" == "skipped" ]; then
  243. if [ "${{ needs.check-changes.result }}" != "success" ]; then
  244. echo "⏭️ **Build & Validate:** Skipped (change detection did not succeed)" >> $GITHUB_STEP_SUMMARY
  245. elif [ "${{ needs.check-changes.outputs.has-evals }}" != "true" ]; then
  246. echo "⏭️ **Build & Validate:** Skipped (no evals changes)" >> $GITHUB_STEP_SUMMARY
  247. else
  248. echo "⏭️ **Build & Validate:** Skipped (prerequisite not met)" >> $GITHUB_STEP_SUMMARY
  249. fi
  250. elif [ "${{ needs.build-check.result }}" == "failure" ]; then
  251. echo "❌ **Build & Validate:** Failed - check logs" >> $GITHUB_STEP_SUMMARY
  252. elif [ "${{ needs.build-check.result }}" == "cancelled" ]; then
  253. echo "🚫 **Build & Validate:** Cancelled" >> $GITHUB_STEP_SUMMARY
  254. fi
  255. echo "" >> $GITHUB_STEP_SUMMARY
  256. # Overall status
  257. if [ "${{ needs.pr-title-check.result }}" == "success" ] && \
  258. [ "${{ needs.check-changes.result }}" == "success" ] && \
  259. { [ "${{ needs.build-check.result }}" == "success" ] || \
  260. { [ "${{ needs.build-check.result }}" == "skipped" ] && \
  261. [ "${{ needs.check-changes.outputs.has-evals }}" != "true" ]; }; }; then
  262. echo "### ✅ All Required Checks Passed!" >> $GITHUB_STEP_SUMMARY
  263. echo "" >> $GITHUB_STEP_SUMMARY
  264. echo "This PR is ready for review." >> $GITHUB_STEP_SUMMARY
  265. else
  266. echo "### ❌ Some Checks Failed" >> $GITHUB_STEP_SUMMARY
  267. echo "" >> $GITHUB_STEP_SUMMARY
  268. echo "Please fix the failing checks before merging." >> $GITHUB_STEP_SUMMARY
  269. exit 1
  270. fi