Skip to content

CI: classify PR files as core by component ownership - #15444

Merged
patmmccann merged 3 commits into
prebid:masterfrom
dgirardi:agent/core-file-component-heuristic
Aug 5, 2026
Merged

CI: classify PR files as core by component ownership#15444
patmmccann merged 3 commits into
prebid:masterfrom
dgirardi:agent/core-file-component-heuristic

Conversation

@dgirardi

@dgirardi dgirardi commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Type of change

  • CI related changes
  • Refactoring (no functional changes, no api changes)

Description of change

PR-assignment.yml labels a PR core and requires a second Prebid reviewer when any changed file looks like a core file. The rule that decided this lived inline in getPRProperties.js: everything counts as core except test/example/top-level/workflow files, modules matching an adapter naming convention, and libraries referenced by a single vendor.

That produces a lot of false positives. It flags the autogenerated per-module metadata under metadata/modules/, the vendor-specific libraries/*Utils shared between an adapter and its aliases, and the registries (metadata/modules.json, modules/.submodules.json) that every new module has to touch.

This PR moves the logic into .github/workflows/scripts/coreFiles.js and replaces the heuristic with one based on component ownership:

  • a module is core when its metadata declares no component, or only prebid components. Modules with no metadata at all — a newly added module, since metadata is generated separately from the module it describes — fall back to the naming conventions: <vendor>BidAdapter, AnalyticsAdapter, RtdProvider, IdSystem, VideoProvider, or a name that begins with a component name already registered in metadata/modules.json (adlooxAdServerVideoadloox). The prefix has to end on a camelCase boundary, so a component named currency would not make modules/currency.js look vendor-owned.
  • a library is core when a core module pulls it in, according to the chunk graph in dependencies.jsonprebid-core falls out of this naturally, since its metadata declares only prebid components. Failing that, a library with a single consumer is that consumer's own code; otherwise it is core unless it belongs to a vendor. Libraries default to core: most of them are shared code that happens to have only vendor modules as consumers (ortbConverter, dnt, sizeUtils), and a library extracted tomorrow should be reviewed until someone says otherwise. Vendor libraries are recognized by name, the same way metadata-less modules are.
  • metadata/modules.json, metadata/disclosures/modules/, and modules/.submodules.json are excluded: a change there is about the module being registered, not about the file itself.

Two constants hold what no convention can pick out. VENDOR_MODULES has one entry, seenthisBrandStories: vendor-owned, but with no metadata, no conventional name, and no component registered under its vendor's name. VENDOR_LIBRARIES has eleven, mostly white labels whose own brand is not a component (teqblazeUtils, vizionikUtils) or whose components are registered under a longer name than the library (intentIqUtilsintentIqId, advangUtilsadvangelists). Both lists shrink as the modules involved gain metadata.

coreFiles.js also runs as a CLI, so the classification can be checked against any set of files:

gh pr diff --name-only 1234 | node .github/workflows/scripts/coreFiles.js
node .github/workflows/scripts/coreFiles.js -c $(git diff --name-only master)

Effect on recent PRs

Measured against the 112 PRs opened or updated in the two weeks to 2026-08-04, using each PR's file list from the GitHub API and a locally built dependencies.json.

current this PR
PRs flagged core 41 25
distinct files flagged core (of 492) 150 79

No PR is newly flagged as core. 16 stop being flagged:

PR title flagged by
#15441 Microsoft Bid Adapter: move Project Agora alias from AppNexus libraries/appnexusUtils/anUtils.js
#15427 Copper6SSP Adapter Update: temporary override until GVL 1356 deviceStorageDisclosureUrl is updated metadata/modules.json, metadata/modules/copper6sspBidAdapter.json
#15400 Revert "Vidazoo utils: add event callbacks" libraries/vidazooUtils/bidderUtils.js
#15392 Blue Bid Adapter: remove GVL ID libraries/blueUtils/bidderUtils.js
#15388 Nexx360 Analytics Adapter: add analytics adapter libraries/nexx360Utils/index.ts (+3)
#15379 Unicorn RTD Provider: add slot position & viewability module modules/.submodules.json
#15374 New Bid Adapter: Adbix metadata/modules/adbixBidAdapter.json
#15360 Gopl adapter metadata/modules.json, metadata/modules/goplBidAdapter.json (+1)
#15335 Vidazoo utils: add event callbacks libraries/vidazooUtils/bidderUtils.js
#15319 Stackup RTD Module: disclose sessionStorage cache keys and fix ortb2 data block merge collapse metadata/disclosures/modules/stackupRtdProvider.json
#15315 Mantis RTD: New RTD Module Added modules/.submodules.json
#15297 precisoBidAdapter-updated libraries/precisoUtils/bidUtils.js, libraries/precisoUtils/bidUtilsCommon.js
#15296 AdSmartx Adapter: Rebranded to Agenticx & Added support for audio ads libraries/agenticxUtils/bidderUtils.js
#15082 New Bid Adapter: BidFabrik metadata/modules.json, metadata/modules/bidfabrikBidAdapter.json
#15006 MGID Bid Adapter: populate bid request with MGID session props libraries/mgidUtils/mgidSessionStorage.ts
#14991 Copper6 adapter: change utility suite libraries/vidazooUtils/bidderUtils.js, libraries/vidazooUtils/vidazooTypes.ts

The 25 that remain core are the src/ changes, the build/metadata tooling (metadata/compileMetadata.mjs, customize/), the prebid-owned modules (userId, rtdModule, consentManagement*, topicsFpdModule, adChoices, devtoolsMcp, gamAdServerVideo) and the libraries they use.

What is flagged core repo-wide

314 of 3772 tracked files, down from 1076. Modules go from 44 to 38; libraries from 76 to 52, but not the same 52 — the ones dropped are vendor-owned, and shared code that no core module happens to use is now kept.

Modules (38 of 753)

Declaring only prebid components (4):

debugging, topicsFpdModule, userId, validationFpdModule

With no metadata and no vendor name (34)

_moduleMetadata, adChoices, allowActivities, bidResponseFilter, bidViewability, bidViewabilityIO, consentManagementGpp, consentManagementTcf, consentManagementUsp, currency, dataControllerModule, dchain, dsaControl, enrichmentLiftMeasurement, fpdModule, gamAdServerVideo, gppControl_usnat, gppControl_usstates, gptPreAuction, idImportLibrary, instreamTracking, multibid, nativeRendering, previousAuctionInfo, priceFloors, rtdModule, rules, s2sTesting, schain, sizeMapping, sizeMappingV2, storageControl, tcfControl, videoModule

Libraries (52 of 103)

Consumer counts in parentheses. 21 of these are core because a core module pulls them in; the other 31 because they have several consumers and nothing marks them as a vendor's.

analyticsAdapter (70), autoplayDetection (4), bidViewabilityPixels (2), bidderTimeoutUtils (2), boundingClientRect (37), chunk (21), cmp (3), connectionInfo (18), consentManagement (4), cookieSync (2), currencyUtils (8), dealUtils (2), devicePixelRatio (10), dfpUtils (1), dnt (47), domainOverrideToRootDomain (4), fingerprinting (26), fpdUtils (7), gamUtils (0), getOrigin (3), gptUtils (29), greedy (0), keywords (14), metadata (714), mspa (2), navigatorData (4), objectGuard (1), ortb2.5StrictTranslator (0), ortb2.5Translator (5), ortb2Utils (31), ortbConverter (135), pbsExtensions (13), percentInView (17), placementPositionInfo (4), processResponse (2), purposeDeclarations (1), schainSerializer (2), sizeUtils (33), storageDisclosure (1), timeToFirstBytesUtils (2), timeoutQueue (5), timezone (14), transformParamsUtils (2), uid1Eids (2), urlUtils (20), userAgentUtils (9), userSyncUtils (7), video (4), viewport (1), weakStore (0), webdriver (3), xmlUtils (2)

The 51 that are not core, all owned by a single vendor

adagioUtils, adkernelUtils, adrelevantisUtils, adtelligentUtils, advangUtils, agenticxUtils, alliance_gravityUtils, appnexusUtils, asteriobidUtils, audUtils, biddoInvamiaUtils, blueUtils, braveUtils, browsiUtils, cryptoUtils, deepintentUtils, dspxUtils, dxUtils, encypherUtils, equativUtils, ferioUtils, htmlEscape, hybridVoxUtils, hypelabUtils, intentIqConstants, intentIqUtils, interpretResponseUtils, liveIntentId, magniteUtils, mediaImpactUtils, medianetUtils, mgidUtils, nexverseUtils, nexx360Utils, omsUtils, pageInfosUtils, permutiveUtils, precisoUtils, pubmaticUtils, riseUtils, smartyadsUtils, targetVideoUtils, teqblazeUtils, uid2Eids, uid2IdSystemShared, uniquestUtils, utiqUtils, vastTrackers, vidazooUtils, vizionikUtils, xeUtils

Beyond the single-consumer case, the consumer count is deliberately not part of the rule. The previous heuristic counted consuming vendors, which flagged as core every white-label library shared between a vendor's brands; teqblazeUtils has 55 consumers, more than pbsExtensions (13) or devicePixelRatio (10), so no threshold separates shared infrastructure from a white label.

Everything under src/, creative/, plugins/, customize/ and the build tooling stays core, as before — the rule only changes how modules/ and libraries/ are judged.

Known limitations

  • Four libraries have no recorded user at all — gamUtils, greedy, weakStore and ortb2.5StrictTranslator — so they fall into the "unknown owner" branch and stay core by assumption rather than by lookup.

    webpack.conf.js gives every directory under libraries/ its own splitChunks cacheGroup, so a library gets a <lib>.js chunk — and therefore an entry in dependencies.json — whenever its code survives into a build. No chunk means the code is not in the bundle, and each of the four is absent for its own reason:

    library why it has no chunk is core right?
    gamUtils one line, re-exporting four symbols from dfpUtils; webpack elides the intermediate module, so gamAdServerVideo records a dependency on dfpUtils instead yes — gamAdServerVideo is core, so correct attribution would reach the same answer
    greedy imported by src/utils/promise.ts behind FEATURES.GREEDY, which gulpHelpers.js disables by default; tree-shaken out of chunk-core.js yes — src/ is core
    weakStore no importer outside test/ no — nothing ships it, so nothing owns it
    ortb2.5StrictTranslator no importer outside test/ no — same

    All four end up core anyway, since none of them is vendor-named, so the blind spot costs nothing today. It would matter for a vendor library that fell out of the chunk graph the way gamUtils does. gamUtils is worth removing on its own merits regardless — it is a one-line re-export with a single consumer, which could import dfpUtils directly.

  • PR-assignment.yml checks out master, so metadata lookups always reflect master while dependencies.json comes from the PR build. A module added in the PR is therefore judged by name, which is what the naming fallback is for.

  • Library classification leans on names, and names collide. timeoutQueue begins with timeout, which is a registered rtd component, so the name test calls it vendor-owned; it stays core only because currency, priceFloors and rules use it. That ordering — usage first, name second — is deliberate, but a shared library with a colliding name and no core consumer would be missed. Conversely uid1Eids is core while uid2Eids is not, purely because uid2 is a registered component name and uid1 is not, though the two are the same kind of file.

  • A library extracted with only one consumer is treated as that consumer's, so genuinely shared code starts out non-core until a second module adopts it. This is what keeps a new adapter's own library from being flagged: on master the vendor is not registered yet, so no name test can recognize it. Four existing libraries are classified this way — cryptoUtils (sevioBidAdapter), htmlEscape (adgenerationBidAdapter), interpretResponseUtils (craftBidAdapter) and vastTrackers (medianetAnalyticsAdapter) — each a single vendor's helper with a generic name.

  • VENDOR_LIBRARIES is maintained by hand and will drift as white labels are added. It is the smaller of the two possible lists — white-label platforms appear rarely, while shared libraries are extracted constantly — and the failure mode of forgetting an entry is a PR flagged core that need not have been, not a core change slipping through.

  • The general fix for both the VENDOR_MODULES exception and the metadata-less modules is for those modules to declare a component, the way metadata/core.json already does for debugging, topicsFpdModule, userId and validationFpdModule. That would turn a 34-module guess into a lookup.

Testing

.github/ is excluded from eslint and there is no unit test harness for the workflow scripts, so this was verified by running the classifier over the repository and over the recent PR set, as reported above. The extraction step was checked separately: before the heuristic was changed, the extracted code agreed with the previous inline implementation on all 3771 tracked files.

Other information

No documentation PR is needed; this does not change anything user-facing.


Issue template

Type of issue — CI / tooling improvement. No associated issue.

Description — the core label and the two-reviewer requirement it triggers are applied to PRs that only touch vendor-owned files, because the file classification treats per-module metadata, single-vendor libraries and module registries as core.

Steps to reproduce — open a PR that changes only libraries/vidazooUtils/bidderUtils.js (e.g. #15335) or only adds a new adapter and its metadata (e.g. #15374); the core label is applied and two Prebid reviewers are required.

Expected results — a PR touching only files owned by one vendor is not flagged as core.

Actual results — it is flagged as core. Of the 112 PRs from the last two weeks, 41 are flagged; 16 of those touch nothing but vendor-owned files.

Platform details — GitHub Actions, actions/github-script@v9, Node 20+.


This PR was generated by Claude Code.

dgirardi and others added 2 commits August 4, 2026 07:01
Extract the "is this file core?" logic out of getPRProperties.js into
coreFiles.js, and replace the heuristic it used.

The old rule treated everything as core except test/example files, modules
matching an adapter naming convention, and libraries referenced by a single
vendor. That flagged as core the autogenerated per-module metadata, the
vendor-specific libraries shared by an adapter and its aliases, and the
registries every new module has to touch.

Core is now what no outside component owns: a module is core when its
metadata declares no component or only `prebid` ones, and a library is core
when a core module pulls it in (prebid-core included). Modules with no
metadata yet - newly added ones - fall back to the naming conventions, and
to whether their name starts with a registered component name.

Over the 112 PRs opened or updated in the last two weeks this flags 25 as
core, down from 38, with none newly flagged. Repo-wide it flags 260 files
and 38 modules, down from 1063 files.

coreFiles.js also runs as a CLI, to classify an arbitrary list of files:

  gh pr diff --name-only 1234 | node .github/workflows/scripts/coreFiles.js

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Attributing a library through the chunk graph alone misses prebid-maintained
shared code whose consumers all happen to be vendor modules: ortbConverter
(135 consumers), dnt (47), boundingClientRect (37), sizeUtils (33) and about
thirty others were not flagged.

Counting consumers, as the previous heuristic did, does not separate those
from white-label libraries shared between one vendor's brands - teqblazeUtils
has 55 consumers, more than pbsExtensions or devicePixelRatio. Neither does
counting distinct component owners or gvl ids, nor the mix of component types
among the consumers.

So libraries now default to core, and are excluded when they belong to a
vendor: when their name begins with a registered component name, or when they
are listed in VENDOR_LIBRARIES - white labels whose brand is not itself a
component, or whose components are registered under a longer name than the
library. A library that a core module pulls in stays core whatever its name
suggests, which is what keeps timeoutQueue - it reads as an extension of the
`timeout` rtd component - on the right side.

This keeps all 21 libraries that were core before and adds 35, while dropping
the 47 vendor-owned ones. Repo-wide the classification covers 318 files; the
112 PRs from the last two weeks are unaffected, still 25 flagged core.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a34542eaf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .github/workflows/scripts/coreFiles.js
@barecheck

barecheck Bot commented Aug 4, 2026

Copy link
Copy Markdown

Barecheck - Code coverage report

Total: 96.67%

Your code coverage diff: 0.00% ▴

✅ All code changes are covered

A library added together with the adapter that uses it was flagged core:
PR-assignment.yml runs from master, where the new vendor is not yet in
metadata/modules.json, so no name test can recognize the library as theirs.
Of the new-adapter-with-its-own-library PRs merged since the component
registry exists, five would have been mislabelled this way - prebid#13731, prebid#13815,
prebid#14267, prebid#14807, prebid#14896 - and four of them are not flagged core today.

A library with exactly one consumer now belongs to that consumer, whatever
its name suggests; it becomes core as soon as a second module picks it up.
Libraries a core module uses are still core, so this only applies to code
owned outright by one vendor module.

Counting consumers beyond that is still avoided - a threshold cannot separate
shared infrastructure from a white label, since teqblazeUtils has more
consumers than pbsExtensions - so ortbConverter, dnt and the other shared
libraries stay core.

Four existing libraries move with this: cryptoUtils, htmlEscape,
interpretResponseUtils and vastTrackers, each a single vendor's helper with a
generic name. Core libraries go from 56 to 52, repo-wide from 318 files to
314; the last two weeks of PRs are unchanged at 25 flagged core.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@patmmccann
patmmccann merged commit a0b5131 into prebid:master Aug 5, 2026
99 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants