Skip to content
Merged
2 changes: 2 additions & 0 deletions .github/actions/overture-projection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
35 changes: 35 additions & 0 deletions .github/actions/overture-projection/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions .github/actions/overture-projection/scripts/fetch-diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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(', ')}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const {
diffCharBudget,
buildIgnorePatterns,
isIgnored,
isTestFile,
isFixtureFile,
applyFileBudget,
} = require('../diff');

Expand Down Expand Up @@ -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
// ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'. */
Expand Down
28 changes: 28 additions & 0 deletions .github/actions/overture-projection/scripts/lib/diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
10 changes: 8 additions & 2 deletions .github/actions/overture-projection/scripts/lib/prompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
33 changes: 27 additions & 6 deletions .github/workflows/overture-projection.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading