ci: skip excluded files when applying package labels in pr-labeler (#36114)

A PR that only touches `uv.lock` currently gets the label of its' dir
because the file rule matches on the file prefix. This is misleading —
lockfile-only changes aren't meaningful package changes. The
`excludedFiles` list already existed in config (for size calculations),
but file rules didn't consult it.

## Changes
- Add `skipExcludedFiles` option to file rules in
`pr-labeler-config.json`, enabled for the four package rules
(`deepagents`, `cli`, `acp`, `evals`) so lockfile-only PRs don't trigger
package labels
- `matchFileLabels` in `pr-labeler.js` now filters out files whose
basename appears in the top-level `excludedFiles` list (currently just
`uv.lock`) before testing rules that opt in via `skipExcluded`
- Non-package rules (`github_actions`, `dependencies`) are unaffected —
they don't set the flag
This commit is contained in:
Mason Daugherty
2026-03-19 18:14:34 -04:00
committed by GitHub
parent 6ca9f5619c
commit a9d31b30f8
2 changed files with 31 additions and 24 deletions

View File

@@ -53,28 +53,28 @@
"infra": "infra"
},
"fileRules": [
{ "label": "core", "prefix": "libs/core/" },
{ "label": "langchain-classic", "prefix": "libs/langchain/" },
{ "label": "langchain", "prefix": "libs/langchain_v1/" },
{ "label": "standard-tests", "prefix": "libs/standard-tests/" },
{ "label": "model-profiles", "prefix": "libs/model-profiles/" },
{ "label": "text-splitters", "prefix": "libs/text-splitters/" },
{ "label": "integration", "prefix": "libs/partners/" },
{ "label": "anthropic", "prefix": "libs/partners/anthropic/" },
{ "label": "chroma", "prefix": "libs/partners/chroma/" },
{ "label": "deepseek", "prefix": "libs/partners/deepseek/" },
{ "label": "exa", "prefix": "libs/partners/exa/" },
{ "label": "fireworks", "prefix": "libs/partners/fireworks/" },
{ "label": "groq", "prefix": "libs/partners/groq/" },
{ "label": "huggingface", "prefix": "libs/partners/huggingface/" },
{ "label": "mistralai", "prefix": "libs/partners/mistralai/" },
{ "label": "nomic", "prefix": "libs/partners/nomic/" },
{ "label": "ollama", "prefix": "libs/partners/ollama/" },
{ "label": "openai", "prefix": "libs/partners/openai/" },
{ "label": "openrouter", "prefix": "libs/partners/openrouter/" },
{ "label": "perplexity", "prefix": "libs/partners/perplexity/" },
{ "label": "qdrant", "prefix": "libs/partners/qdrant/" },
{ "label": "xai", "prefix": "libs/partners/xai/" },
{ "label": "core", "prefix": "libs/core/", "skipExcludedFiles": true },
{ "label": "langchain-classic", "prefix": "libs/langchain/", "skipExcludedFiles": true },
{ "label": "langchain", "prefix": "libs/langchain_v1/", "skipExcludedFiles": true },
{ "label": "standard-tests", "prefix": "libs/standard-tests/", "skipExcludedFiles": true },
{ "label": "model-profiles", "prefix": "libs/model-profiles/", "skipExcludedFiles": true },
{ "label": "text-splitters", "prefix": "libs/text-splitters/", "skipExcludedFiles": true },
{ "label": "integration", "prefix": "libs/partners/", "skipExcludedFiles": true },
{ "label": "anthropic", "prefix": "libs/partners/anthropic/", "skipExcludedFiles": true },
{ "label": "chroma", "prefix": "libs/partners/chroma/", "skipExcludedFiles": true },
{ "label": "deepseek", "prefix": "libs/partners/deepseek/", "skipExcludedFiles": true },
{ "label": "exa", "prefix": "libs/partners/exa/", "skipExcludedFiles": true },
{ "label": "fireworks", "prefix": "libs/partners/fireworks/", "skipExcludedFiles": true },
{ "label": "groq", "prefix": "libs/partners/groq/", "skipExcludedFiles": true },
{ "label": "huggingface", "prefix": "libs/partners/huggingface/", "skipExcludedFiles": true },
{ "label": "mistralai", "prefix": "libs/partners/mistralai/", "skipExcludedFiles": true },
{ "label": "nomic", "prefix": "libs/partners/nomic/", "skipExcludedFiles": true },
{ "label": "ollama", "prefix": "libs/partners/ollama/", "skipExcludedFiles": true },
{ "label": "openai", "prefix": "libs/partners/openai/", "skipExcludedFiles": true },
{ "label": "openrouter", "prefix": "libs/partners/openrouter/", "skipExcludedFiles": true },
{ "label": "perplexity", "prefix": "libs/partners/perplexity/", "skipExcludedFiles": true },
{ "label": "qdrant", "prefix": "libs/partners/qdrant/", "skipExcludedFiles": true },
{ "label": "xai", "prefix": "libs/partners/xai/", "skipExcludedFiles": true },
{ "label": "github_actions", "prefix": ".github/workflows/" },
{ "label": "github_actions", "prefix": ".github/actions/" },
{ "label": "dependencies", "suffix": "pyproject.toml" },

View File

@@ -109,15 +109,22 @@ function init(github, owner, repo, config, core) {
`(expected one of: prefix, suffix, exact, pattern)`
);
}
return { label: rule.label, test };
return { label: rule.label, test, skipExcluded: !!rule.skipExcludedFiles };
});
}
function matchFileLabels(files, fileRules) {
const rules = fileRules || buildFileRules();
const excluded = new Set(excludedFiles);
const labels = new Set();
for (const rule of rules) {
if (files.some(f => rule.test(f.filename ?? ''))) {
// skipExcluded: ignore files whose basename is in the top-level
// "excludedFiles" list (e.g. uv.lock) so lockfile-only changes
// don't trigger package labels.
const candidates = rule.skipExcluded
? files.filter(f => !excluded.has((f.filename ?? '').split('/').pop()))
: files;
if (candidates.some(f => rule.test(f.filename ?? ''))) {
labels.add(rule.label);
}
}