diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 0bc83763..0163b79d 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -133,7 +133,7 @@ Override score, severity, or enable/disable individual rules: | Rule ID | Default Score | Default Severity | |---------|--------------|-----------------| -| `non-standard-naming` | -1 | suggestion | +| `non-standard-naming` | -3 | suggestion | | `non-semantic-name` | -1 | suggestion | | `inconsistent-naming-convention` | -1 | suggestion | diff --git a/src/core/engine/scoring.test.ts b/src/core/engine/scoring.test.ts index 68e502ec..713c9321 100644 --- a/src/core/engine/scoring.test.ts +++ b/src/core/engine/scoring.test.ts @@ -1,4 +1,4 @@ -import { calculateScores, formatScoreSummary, gradeToClassName, getCategoryLabel, getSeverityLabel, buildResultJson } from "./scoring.js"; +import { calculateScores, formatScoreSummary, gradeToClassName, getCategoryLabel, getSeverityLabel, buildResultJson, CATEGORY_WEIGHT } from "./scoring.js"; import type { AnalysisIssue, AnalysisResult } from "./rule-engine.js"; import type { AnalysisFile, AnalysisNode } from "../contracts/figma-node.js"; import type { Rule, RuleConfig, RuleViolation } from "../contracts/rule.js"; @@ -226,17 +226,13 @@ describe("calculateScores", () => { makeIssue({ ruleId: "no-auto-layout", category: "pixel-critical", severity: "blocking" }), ], 100)); - const categoryPercentages = [ - scores.byCategory["pixel-critical"].percentage, - scores.byCategory["responsive-critical"].percentage, - scores.byCategory["code-quality"].percentage, - scores.byCategory["token-management"].percentage, - scores.byCategory["interaction"].percentage, - scores.byCategory["minor"].percentage, - ]; - const expectedOverall = Math.round( - categoryPercentages.reduce((a, b) => a + b, 0) / 6 - ); + let weightedSum = 0; + let totalWeight = 0; + for (const [cat, w] of Object.entries(CATEGORY_WEIGHT)) { + weightedSum += scores.byCategory[cat as Category].percentage * w; + totalWeight += w; + } + const expectedOverall = Math.round(weightedSum / totalWeight); expect(scores.overall.percentage).toBe(expectedOverall); }); diff --git a/src/core/engine/scoring.ts b/src/core/engine/scoring.ts index 0b0d2dd6..93ab8b15 100644 --- a/src/core/engine/scoring.ts +++ b/src/core/engine/scoring.ts @@ -85,19 +85,23 @@ function computeTotalScorePerCategory( } /** - * Category weights for overall score. - * All equal (1.0) by design — no category is inherently more important than another. - * This avoids subjective bias; individual rule scores within each category already - * encode relative importance. If calibration reveals certain categories correlate - * more strongly with visual-compare similarity, these weights can be adjusted. + * Category weights for overall score, based on ablation experiment data (PR #149, #150). + * + * Evidence: + * - responsive-critical: ΔV +15.9% at expanded viewport, mobile-shop +46% — highest impact + * - pixel-critical: ΔV +5.4% when layout info stripped — direct pixel accuracy impact + * - token-management: ΔV +3.5% (style-ref, Phase 2) — moderate + * - code-quality: ΔV ≈0%, CSS classes -8~15 — affects structure, not pixels + * - interaction: data incomplete (missing-prototype disabled, #139 fixture rebuild pending) + * - minor: ΔV <2%, negligible code difference */ -const CATEGORY_WEIGHT: Record = { - "pixel-critical": 1.0, - "responsive-critical": 1.0, +export const CATEGORY_WEIGHT: Record = { + "pixel-critical": 2.5, + "responsive-critical": 3.0, "code-quality": 1.0, "token-management": 1.0, - "interaction": 1.0, - "minor": 1.0, + "interaction": 0.5, + "minor": 0.3, }; /** diff --git a/src/core/rules/rule-config.ts b/src/core/rules/rule-config.ts index 36bfdcd0..956d8cdc 100644 --- a/src/core/rules/rule-config.ts +++ b/src/core/rules/rule-config.ts @@ -131,13 +131,13 @@ export const RULE_CONFIGS: Record = { "missing-prototype": { severity: "missing-info", score: -3, - enabled: true, + enabled: false, // disabled: interactionDestinations data missing from fixtures (#139) }, // ── Minor ── "non-standard-naming": { severity: "suggestion", - score: -1, + score: -3, // higher than other naming rules: non-standard state names break interaction detection pipeline enabled: true, }, "non-semantic-name": {