From 1de970a9ae5bf3638e8e7a922c6f1ea1a00bdef0 Mon Sep 17 00:00:00 2001 From: jaso0n0818 Date: Mon, 22 Jun 2026 05:34:46 +0000 Subject: [PATCH] feat(scoring): sync dropped upstream constants and cap non-code token contribution (#809) The knownOnly constants parser dropped MAX_OPEN_PR_REVIEW_COLLATERAL_MULTIPLIER, MAX_LINES_SCORED_FOR_NON_CODE_EXT, and DEFAULT_ISSUE_DISCOVERY_SHARE (absent from SCORING_CONSTANT_NAMES), so they never synced from upstream and surfaced as unmodeled drift. Add all three to DEFAULT_SCORING_CONSTANTS so they sync. Model the non-code line cap in preview.ts: a PR's non-code token contribution is now capped at MAX_LINES_SCORED_FOR_NON_CODE_EXT before it feeds the component total, so a large generated/non-code file cannot inflate the contribution-bonus ramp (an explicit caller total is still honoured as-is). DEFAULT_ISSUE_DISCOVERY_SHARE is already applied as the upstream default in registry/normalize.ts; the review-collateral multiplier now syncs and can be modeled in a follow-up. Tests: the three constants are modeled + no longer flagged as drift, and the non-code cap clamps the component total (500 and 300 yield the same contribution bonus; 100 is lower). --- src/scoring/model.ts | 7 +++++++ src/scoring/preview.ts | 6 +++++- test/unit/scoring.test.ts | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/scoring/model.ts b/src/scoring/model.ts index c088a2abcd..58bab72096 100644 --- a/src/scoring/model.ts +++ b/src/scoring/model.ts @@ -47,6 +47,13 @@ export const DEFAULT_SCORING_CONSTANTS: Record = { TIME_DECAY_SIGMOID_MIDPOINT: 10, TIME_DECAY_SIGMOID_STEEPNESS_SCALAR: 0.4, TIME_DECAY_MIN_MULTIPLIER: 0.05, + // Upstream scoring constants the knownOnly parser previously dropped (absent from SCORING_CONSTANT_NAMES), + // so they never synced from upstream and surfaced as unmodeled drift (#809). DEFAULT_ISSUE_DISCOVERY_SHARE is + // the upstream default for a repo's issue-discovery share when its registry config does not set one (applied + // in preview.ts); the other two sync for parity with upstream review-collateral and non-code line handling. + MAX_OPEN_PR_REVIEW_COLLATERAL_MULTIPLIER: 2.0, + MAX_LINES_SCORED_FOR_NON_CODE_EXT: 300, + DEFAULT_ISSUE_DISCOVERY_SHARE: 0.5, }; export const DEFAULT_GITTENSOR_UPSTREAM_REPO = "entrius/gittensor"; diff --git a/src/scoring/preview.ts b/src/scoring/preview.ts index e5dd4ebfc3..6b6e08829f 100644 --- a/src/scoring/preview.ts +++ b/src/scoring/preview.ts @@ -298,7 +298,11 @@ function computeScoreCore( // TEST_FILE_CONTRIBUTION_WEIGHT (#808): upstream weights test-file tokens at 0.05× relative to source tokens. // Applied only when totalTokenScore is not explicitly provided — an explicit caller total is honoured as-is. const testFileWeight = constant(constants, "TEST_FILE_CONTRIBUTION_WEIGHT", 0.05); - const totalTokenScore = nonNegative(input.totalTokenScore ?? sourceTokenScore + testFileWeight * nonNegative(input.testTokenScore) + nonNegative(input.nonCodeTokenScore)); + // Non-code files (docs, data, config) contribute capped token score: upstream limits the lines scored for + // a non-code extension to MAX_LINES_SCORED_FOR_NON_CODE_EXT so a huge generated/non-code file cannot inflate + // the component total (#809). Honour an explicit caller total as-is. + const cappedNonCodeTokenScore = Math.min(nonNegative(input.nonCodeTokenScore), constant(constants, "MAX_LINES_SCORED_FOR_NON_CODE_EXT", 300)); + const totalTokenScore = nonNegative(input.totalTokenScore ?? sourceTokenScore + testFileWeight * nonNegative(input.testTokenScore) + cappedNonCodeTokenScore); const sourceLines = Math.max(1, nonNegative(input.sourceLines ?? sourceTokenScore)); const fixedBaseScore = input.fixedBaseScore ?? config?.fixedBaseScore ?? undefined; const rawDensity = sourceTokenScore / sourceLines; diff --git a/test/unit/scoring.test.ts b/test/unit/scoring.test.ts index 1445bc85c4..df0591631e 100644 --- a/test/unit/scoring.test.ts +++ b/test/unit/scoring.test.ts @@ -191,6 +191,26 @@ MAX_CODE_DENSITY_MULTIPLIER = 1.15 expect(unmodeled).not.toContain("TIME_DECAY_GRACE_PERIOD_HOURS"); // modeled as of #703 }); + it("models the previously-dropped upstream scoring constants so they sync and stop surfacing as drift (#809)", () => { + expect(DEFAULT_SCORING_CONSTANTS.MAX_OPEN_PR_REVIEW_COLLATERAL_MULTIPLIER).toBe(2); + expect(DEFAULT_SCORING_CONSTANTS.MAX_LINES_SCORED_FOR_NON_CODE_EXT).toBe(300); + expect(DEFAULT_SCORING_CONSTANTS.DEFAULT_ISSUE_DISCOVERY_SHARE).toBe(0.5); + expect( + findUnmodeledUpstreamConstants("MAX_OPEN_PR_REVIEW_COLLATERAL_MULTIPLIER = 2.0\nMAX_LINES_SCORED_FOR_NON_CODE_EXT = 300\nDEFAULT_ISSUE_DISCOVERY_SHARE = 0.5\n"), + ).toEqual([]); + }); + + it("caps a PR's non-code token contribution at MAX_LINES_SCORED_FOR_NON_CODE_EXT (#809)", () => { + const base = { repoFullName: repo.fullName, sourceTokenScore: 40, sourceLines: 40, credibility: 1 }; + // nonCodeTokenScore 500 is capped at 300, so the two previews (500 vs exactly 300) yield the same total. + const capped = buildScorePreview({ repo, snapshot, input: { ...base, nonCodeTokenScore: 500 } }); + const atCap = buildScorePreview({ repo, snapshot, input: { ...base, nonCodeTokenScore: 300 } }); + const underCap = buildScorePreview({ repo, snapshot, input: { ...base, nonCodeTokenScore: 100 } }); + // The cap caps the component total, which feeds the contribution-bonus ramp. + expect(capped.scoreEstimate.contributionBonus).toBe(atCap.scoreEstimate.contributionBonus); + expect(underCap.scoreEstimate.contributionBonus).toBeLessThan(atCap.scoreEstimate.contributionBonus); + }); + it("warns on the snapshot when upstream defines an unmodeled scoring dimension", async () => { const env = createTestEnv({ GITTENSOR_UPSTREAM_REPO: "custom/upstream",