diff --git a/.github/actions/overture-projection/README.md b/.github/actions/overture-projection/README.md index 311cb4d..3025eda 100644 --- a/.github/actions/overture-projection/README.md +++ b/.github/actions/overture-projection/README.md @@ -4,6 +4,8 @@ Posts an AI-generated code review comment on a pull request. Skills drive what t Supports [GitHub Models](https://docs.github.com/en/github-models) (default) and [Anthropic](https://docs.anthropic.com/en/docs/about-claude/models) as model providers. +> **⚠️ GitHub Models sunset notice (2026-06-16):** GitHub has stopped onboarding new GitHub Models customers and has announced full retirement, with timeline TBD. Existing OvertureMaps usage continues for now. When a sunset date is published, migrate by setting `model-provider: anthropic` and adding the `ANTHROPIC_API_KEY` org secret — the Anthropic path is already fully wired. See the [changelog](https://github.blog/changelog/2026-06-16-github-models-is-no-longer-available-to-new-customers/). + ## How it works 1. **Load skills** — sparse-checkouts `omf-devex/skills/`, parses frontmatter, filters to `pr-reviewer` surface. Raw content is stored; nothing is fetched yet. diff --git a/.github/actions/overture-projection/action.yml b/.github/actions/overture-projection/action.yml index bf53c4c..4e3f110 100644 --- a/.github/actions/overture-projection/action.yml +++ b/.github/actions/overture-projection/action.yml @@ -131,6 +131,41 @@ inputs: go.sum *.lock *.snap + *.png + *.jpg + *.jpeg + *.gif + *.ico + *.webp + *.bmp + *.tiff + *.svg + *.woff + *.woff2 + *.ttf + *.eot + *.otf + *.zip + *.tar + *.gz + *.tgz + *.jar + *.war + *.class + *.pyc + *.pyo + *.mp4 + *.mp3 + *.wav + *.pdf + *.min.js + *.min.css + *.geojson + *.parquet + *.geoparquet + *.pmtiles + *.mbtiles + *.csv comment-mode: description: >- Controls how the review is posted. 'update' posts a single issue comment diff --git a/.github/actions/overture-projection/scripts/fetch-diff.js b/.github/actions/overture-projection/scripts/fetch-diff.js index 681acd2..ddaa80b 100644 --- a/.github/actions/overture-projection/scripts/fetch-diff.js +++ b/.github/actions/overture-projection/scripts/fetch-diff.js @@ -34,7 +34,7 @@ module.exports = async ({ github, context, core }) => { const fs = require('fs'); const path = require('path'); - const { buildIgnorePatterns, isIgnored, applyFileBudget } = require('./lib/diff'); + const { buildIgnorePatterns, isIgnored, isTestFile, isFixtureFile, applyFileBudget } = require('./lib/diff'); const { resolveRepo, resolvePrNumber } = require('./lib/github'); const { DEFAULT_MAX_DIFF_CHARS } = require('./lib/defaults'); @@ -81,7 +81,11 @@ module.exports = async ({ github, context, core }) => { core.info(`⏭️ Ignored ${ignoredFiles.length} file(s): ${ignoredFiles.map(f => f.filename).join(', ')}`); } - const { included: files, skipped: budgetSkippedFiles } = applyFileBudget(includedFiles, fetchCeiling); + // ponytail: stable sort — fixtures last, then tests, source files first + const isLowPriority = f => isTestFile(f) || isFixtureFile(f); + const prioritized = [...includedFiles.filter(f => !isLowPriority(f)), ...includedFiles.filter(isLowPriority)]; + + const { included: files, skipped: budgetSkippedFiles } = applyFileBudget(prioritized, fetchCeiling); core.info(`📐 Fetch ceiling: ${fetchCeiling} chars — fetched ${files.length} file(s), ceiling-skipped ${budgetSkippedFiles.length} file(s)`); if (budgetSkippedFiles.length > 0) { core.info(`⚠️ Ceiling-skipped (not fetched): ${budgetSkippedFiles.map(f => f.filename).join(', ')}`); diff --git a/.github/actions/overture-projection/scripts/lib/__tests__/diff.test.js b/.github/actions/overture-projection/scripts/lib/__tests__/diff.test.js index a18c85c..ec0711e 100644 --- a/.github/actions/overture-projection/scripts/lib/__tests__/diff.test.js +++ b/.github/actions/overture-projection/scripts/lib/__tests__/diff.test.js @@ -6,6 +6,8 @@ const { diffCharBudget, buildIgnorePatterns, isIgnored, + isTestFile, + isFixtureFile, applyFileBudget, } = require('../diff'); @@ -81,6 +83,65 @@ describe('isIgnored', () => { }); }); +// --------------------------------------------------------------------------- +// isTestFile +// --------------------------------------------------------------------------- + +describe('isTestFile', () => { + it('matches __tests__ directory', () => { + assert.equal(isTestFile({ filename: 'src/__tests__/foo.js' }), true); + }); + + it('matches *.test.* files', () => { + assert.equal(isTestFile({ filename: 'src/foo.test.ts' }), true); + }); + + it('matches *.spec.* files', () => { + assert.equal(isTestFile({ filename: 'src/foo.spec.js' }), true); + }); + + it('matches test_* prefix', () => { + assert.equal(isTestFile({ filename: 'tests/test_parser.py' }), true); + }); + + it('matches *_test suffix', () => { + assert.equal(isTestFile({ filename: 'parser_test.go' }), true); + }); + + it('does not match regular source files', () => { + assert.equal(isTestFile({ filename: 'src/parser.ts' }), false); + assert.equal(isTestFile({ filename: 'src/routes.ts' }), false); + }); +}); + +// --------------------------------------------------------------------------- +// isFixtureFile +// --------------------------------------------------------------------------- + +describe('isFixtureFile', () => { + it('matches fixtures/ directory', () => { + assert.equal(isFixtureFile({ filename: 'tests/fixtures/sample.json' }), true); + }); + + it('matches testdata/ directory', () => { + assert.equal(isFixtureFile({ filename: 'pkg/testdata/input.json' }), true); + }); + + it('matches test-fixtures/ directory', () => { + assert.equal(isFixtureFile({ filename: 'src/test-fixtures/mock.json' }), true); + }); + + it('matches __fixtures__/ directory', () => { + assert.equal(isFixtureFile({ filename: 'src/__fixtures__/response.json' }), true); + }); + + it('does not match regular source or config files', () => { + assert.equal(isFixtureFile({ filename: 'src/parser.ts' }), false); + assert.equal(isFixtureFile({ filename: 'package.json' }), false); + assert.equal(isFixtureFile({ filename: 'tools.json' }), false); + }); +}); + // --------------------------------------------------------------------------- // applyFileBudget // --------------------------------------------------------------------------- diff --git a/.github/actions/overture-projection/scripts/lib/__tests__/prompt.test.js b/.github/actions/overture-projection/scripts/lib/__tests__/prompt.test.js index 7a56949..e096504 100644 --- a/.github/actions/overture-projection/scripts/lib/__tests__/prompt.test.js +++ b/.github/actions/overture-projection/scripts/lib/__tests__/prompt.test.js @@ -256,6 +256,13 @@ describe('buildUserPrompt', () => { assert.match(result, /Tests: ✅/); }); + it('shows Tests: ✅ (not reviewed — budget-cut) when tests are only in budgetSkippedFiles', () => { + const result = buildUserPrompt(makePRData({ + budgetSkippedFiles: ['src/__tests__/parser.test.ts'], + })); + assert.match(result, /Tests: ✅ \(not reviewed — budget-cut\)/); + }); + it('shows Tests: ❌ none in diff when no test files are present', () => { const result = buildUserPrompt(makePRData()); assert.match(result, /Tests: ❌ none in diff/); diff --git a/.github/actions/overture-projection/scripts/lib/defaults.js b/.github/actions/overture-projection/scripts/lib/defaults.js index 9684741..a4b0051 100644 --- a/.github/actions/overture-projection/scripts/lib/defaults.js +++ b/.github/actions/overture-projection/scripts/lib/defaults.js @@ -15,7 +15,11 @@ // ── Model IDs ───────────────────────────────────────────────────────────────── -/** Default model provider. */ +/** Default model provider. + * NOTE: GitHub Models is being retired (announced 2026-06-16, timeline TBD). + * To migrate: set DEFAULT_PROVIDER to 'anthropic' and ensure ANTHROPIC_API_KEY is set. + * See: https://github.blog/changelog/2026-06-16-github-models-is-no-longer-available-to-new-customers/ + */ const DEFAULT_PROVIDER = 'github-models'; /** Default review model when model-provider is 'github-models'. */ diff --git a/.github/actions/overture-projection/scripts/lib/diff.js b/.github/actions/overture-projection/scripts/lib/diff.js index 7fc0da8..17e3bfb 100644 --- a/.github/actions/overture-projection/scripts/lib/diff.js +++ b/.github/actions/overture-projection/scripts/lib/diff.js @@ -141,9 +141,37 @@ function applyFileBudget(files, totalChars) { return { included, skipped }; } +/** + * Returns `true` if the file is a test file (should be deprioritized in the + * diff budget so source files are reviewed first when context is tight). + * + * Matches: __tests__/ directories, *.test.*, *.spec.*, test_*.*, *_test.* + * + * @param {{ filename: string }} file + * @returns {boolean} + */ +function isTestFile(file) { + return /(__tests__|\.test\.|\.spec\.|\/test_|_test\.)/i.test(file.filename); +} + +/** + * Returns `true` if the file is a fixture or data file (should be deprioritized + * in the diff budget behind source and test files). + * + * Matches: fixtures/, testdata/, test-fixtures/, __fixtures__/ directories. + * + * @param {{ filename: string }} file + * @returns {boolean} + */ +function isFixtureFile(file) { + return /(\/fixtures\/|\/testdata\/|\/test-fixtures\/|__fixtures__)/i.test(file.filename); +} + module.exports = { diffCharBudget, buildIgnorePatterns, isIgnored, + isTestFile, + isFixtureFile, applyFileBudget, }; diff --git a/.github/actions/overture-projection/scripts/lib/prompt.js b/.github/actions/overture-projection/scripts/lib/prompt.js index ea388c9..b7e68ff 100644 --- a/.github/actions/overture-projection/scripts/lib/prompt.js +++ b/.github/actions/overture-projection/scripts/lib/prompt.js @@ -161,8 +161,14 @@ function buildUserPromptParts(prData) { prData.files.every(f => DOCS_EXTENSIONS.has('.' + f.filename.split('.').pop().toLowerCase())); const prTypeNote = docsOnly ? 'PR type: docs-only' : 'PR type: code'; - const hasTests = prData.files.some(f => isTestFile(f.filename)); - const testsNote = docsOnly ? '' : ` | Tests: ${hasTests ? '✅' : '❌ none in diff'}`; + const testsInDiff = prData.files.some(f => isTestFile(f.filename)); + const testsSkipped = prData.budgetSkippedFiles?.some(f => isTestFile(f)); + const hasTests = testsInDiff || testsSkipped; + const testsNote = docsOnly ? '' : ` | Tests: ${ + !hasTests ? '❌ none in diff' : + testsInDiff ? '✅' : + '✅ (not reviewed — budget-cut)' + }`; const authorNote = prData.authorAssociation ? ` | Author: ${prData.authorAssociation}` : ''; const prHeader = diff --git a/.github/workflows/overture-projection.yml b/.github/workflows/overture-projection.yml index e37b3ca..d20f758 100644 --- a/.github/workflows/overture-projection.yml +++ b/.github/workflows/overture-projection.yml @@ -136,18 +136,39 @@ on: description: omf-devex ref to load skills from required: false default: main + model_provider: + description: "Model provider" + required: false + type: choice + default: github-models + options: + - github-models + - anthropic model: - description: "Review model ID (e.g. gpt-4.1 or claude-opus-4-6). Defaults automatically by provider when not set." + description: "Review model. GitHub Models options work with github-models provider; claude-* options require model_provider=anthropic." required: false + type: choice default: "" + options: + - "" # provider default (gpt-4.1 / claude-opus-4-6) + - gpt-4.1 # github-models — best quality, 8k ctx + - gpt-4.1-mini # github-models — faster, cheaper + - gpt-4.1-nano # github-models — fastest, cheapest + - gpt-4o # github-models — previous gen + - o4-mini # github-models — reasoning model, slower + - claude-opus-4-6 # anthropic — best quality, 200k ctx + - claude-sonnet-4-5 # anthropic — mid-tier + - claude-haiku-4-6 # anthropic — fast, cheap selection_model: - description: "Skill selection model ID. Defaults automatically by provider when not set." + description: "Skill selection model. Leave blank to use provider default (gpt-4.1-mini / claude-haiku-4-6)." required: false + type: choice default: "" - model_provider: - description: "'github-models' (default) or 'anthropic'" - required: false - default: github-models + options: + - "" # provider default + - gpt-4.1-mini # github-models default + - gpt-4.1-nano # github-models — cheapest + - claude-haiku-4-6 # anthropic default max_input_tokens: description: "Max input tokens. Defaults automatically by provider when not set (6200 for github-models, 190000 for anthropic)." required: false