Skip to content

refactor: reorganize rule categories and scores based on ablation data#154

Merged
let-sunny merged 10 commits intomainfrom
refactor/rule-categories-v2
Mar 29, 2026
Merged

refactor: reorganize rule categories and scores based on ablation data#154
let-sunny merged 10 commits intomainfrom
refactor/rule-categories-v2

Conversation

@let-sunny
Copy link
Copy Markdown
Owner

@let-sunny let-sunny commented Mar 28, 2026

Summary

Ablation 실험 데이터(#149, #150)를 기반으로 카테고리/severity/score 체계를 직관 기반 → 실험 기반으로 전면 재구성.

  • 29개 룰 → 15개 룰 (10개 제거, 6개 → 2개 합침, spacing variable 감지 추가)
  • 5개 카테고리 전면 교체 (실험 영향 등급 기반)
  • Score/severity 실험 데이터 기반 재조정

Category 변경

실험 근거 기존 카테고리 새 카테고리
ΔV ≥ 5% (layout strip) structure pixel-critical
ΔV ≥ 15% (responsive viewport) structure responsive-critical
ΔV ≈ 0%, CSS classes -8~15 component code-quality
잘못 입력하면 바로 틀림 token token-management
ΔV < 2%, 코드 차이 미미 naming minor
behavior (삭제)

Rule 변경

제거 (10개)

Rule 제거 사유
unnecessary-node disabled 상태 + 오탐
missing-component-description 영향 없음, 대부분 미사용
prototype-link-in-design 방향 틀림 (감점→가점이어야), #151에서 대체
numeric-suffix-name 영향 미미
too-long-name 영향 미미
z-index-dependent-layout absolute-position 룰과 중복
multiple-fill-colors 그라데이션 등 오탐
text-truncation-unhandled 의도 vs 실수 구분 불가, #151에서 재정의
overflow-behavior-unknown #151에서 재정의
wrap-behavior-unknown #151에서 재정의

합침 (6 → 2)

기존 새 Rule 변경
raw-color, raw-font, raw-shadow, raw-opacity raw-value 동일 문제(토큰 미사용) 통합 + spacing variable 감지 추가
inconsistent-spacing, magic-number-spacing irregular-spacing grid base 검사 통합

Score 재조정

카테고리 Score 범위 주요 변경
pixel-critical -10 ~ -7 group-usage -5 → -8 (blocking 승격)
responsive-critical -6 ~ -5 fixed-size-in-auto-layout -3 → -6
code-quality -5 ~ -3 missing-component -7 → -5
token-management -3 ~ -2 raw-value -3 (신규)
minor -1 default-name -2 → -1

Related Issues

Test plan

  • pnpm lint — clean
  • pnpm test:run — 521/521 pass
  • Fixture 재분석 확인 (실제 Figma fixture로 canicode analyze 돌려보기)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Redesigned rule taxonomy: Pixel Critical, Responsive Critical, Code Quality, Token Management, Minor.
    • Centralized, human-friendly rule message library for clearer guidance.
  • Changes

    • Consolidated token checks into a single Raw Value rule and a unified Irregular Spacing rule.
    • Removed global colorTolerance setting; docs, examples, and templates updated.
    • Many rules renamed, recategorized, and had default scores/severities adjusted.

…eriment data

Categories changed from intuition-based (structure/token/component/naming/behavior)
to experiment-based (pixel-critical/responsive-critical/code-quality/token-management/minor).
Removed 10 low-impact rules, merged 6 rules into 2 (raw-value, irregular-spacing).
Scores recalibrated using Phase 1+2 ablation results (PR #149, #150).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the five-category taxonomy with a new five-category set; consolidates multiple token/spacing rules into raw-value and irregular-spacing; removes several behavior/naming/component rules and many associated tests; adds typed centralized rule message utilities and RuleViolation.subType; removes colorTolerance config and updates configs, docs, CLI, scoring, agents, and numerous test fixtures.

Changes

Cohort / File(s) Summary
Category & Contracts
src/core/contracts/category.ts, src/core/contracts/rule.ts
Replaced category enum with pixel-critical/responsive-critical/code-quality/token-management/minor; added RuleViolation.subType; updated RuleId union and DEPTH_WEIGHT_CATEGORIES.
Rule Messages Module
src/core/rules/rule-messages.ts
New centralized, typed message factories and subtype types used by many rules.
Token Rules Consolidation
src/core/rules/token/index.ts, removed src/core/rules/token/*.test.ts
Removed many fine-grained token rules/tests; added consolidated raw-value (subTypes: color/font/shadow/opacity/spacing) and irregular-spacing.
Structure Rules & Non-layout
src/core/rules/structure/index.ts, removed src/core/rules/structure/*.test.ts
Re-categorized structure rules, added non-layout-container, deleted several legacy rules/tests, replaced hardcoded messages with message factories and added subType to findings.
Behavior Rules Removal
src/core/rules/behavior/index.ts, removed src/core/rules/behavior/*.test.ts
Deleted behavior rules module and its associated tests (four behavior rules removed).
Naming & Component Changes
src/core/rules/naming/index.ts, src/core/rules/component/index.ts, removed naming/component tests
Moved naming rules to minor, removed numeric-suffix-name, too-long-name, and missing-component-description; component rules moved to code-quality; messages switched to factories; some findings include subType.
Rule Config & Presets
src/core/rules/rule-config.ts, src/core/rules/config-loader.ts, examples/config.json
Removed colorTolerance from schema/merge logic; added strict validation for unknown rule IDs; overhauled RULE_ID_CATEGORY and RULE_CONFIGS to match new taxonomy; examples updated to use raw-value/irregular-spacing.
Scoring & Engine
src/core/engine/scoring.ts, src/core/engine/*.test.ts
Switched to imported CATEGORY_LABELS, replaced CATEGORY_WEIGHT keys with new categories, updated calibration comments; buildResultJson now conditionally includes subType; many tests updated for new category keys.
Exports & Indexes
src/core/rules/index.ts
Stopped re-exporting behavior rules (behavior index removed from exports).
UI, Docs & CLI
docs/REFERENCE.md, .claude/agents/rule-discovery/designer.md, src/cli/docs.ts, src/core/ui-constants.ts
Updated docs, designer instructions, CLI docs, and category descriptions to reflect new categories and rule IDs; removed colorTolerance references.
Agents, Tests & Fixtures
src/agents/*, .github/ISSUE_TEMPLATE/bug.yml
Updated many agent tests and fixtures to use new categories/rule IDs; adjusted bug template placeholder text.
Removed Test Suites
src/core/rules/**/*.test.ts (multiple removed)
Deleted many unit test files across behavior, token, naming, and structure to reflect consolidated/removed rules.

Sequence Diagram(s)

(Skipped — changes are taxonomy and rule consolidation/refactors, not new multi-component sequential flows.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

"🐰
I hop through rules with tidy paws,
I bundle raw bits and mend the laws;
Messages gathered, categories anew,
Subtypes tucked in, tests hopped through — woohoo! 🥕"

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and clearly summarizes the main change: reorganizing rule categories and scores based on ablation data, which is the primary objective of this refactor.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/rule-categories-v2

Comment @coderabbitai help to get the list of available commands and usage tips.

@let-sunny let-sunny marked this pull request as draft March 28, 2026 14:08
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/agents/orchestrator.test.ts (1)

86-136: ⚠️ Potential issue | 🟡 Minor

Missing responsive-critical category in test fixture.

The byCategory object is missing the "responsive-critical" category. The ScoreReport.byCategory type is Record<Category, CategoryScoreResult>, which requires entries for all 5 categories defined in CategorySchema. The production code in src/core/engine/scoring.ts (lines 252-256) iterates over all CATEGORIES and accesses categoryScores[category], which would cause issues if a category is missing.

🔧 Proposed fix: Add the missing category
           minor: {
             category: "minor" as const,
             score: 100,
             maxScore: 100,
             percentage: 100,
             issueCount: 0,
             uniqueRuleCount: 0,
             weightedIssueCount: 0,
             densityScore: 100,
             diversityScore: 100,
             bySeverity: { blocking: 0, risk: 0, "missing-info": 0, suggestion: 0 },
           },
+          "responsive-critical": {
+            category: "responsive-critical" as const,
+            score: 100,
+            maxScore: 100,
+            percentage: 100,
+            issueCount: 0,
+            uniqueRuleCount: 0,
+            weightedIssueCount: 0,
+            densityScore: 100,
+            diversityScore: 100,
+            bySeverity: { blocking: 0, risk: 0, "missing-info": 0, suggestion: 0 },
+          },
         },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/orchestrator.test.ts` around lines 86 - 136, The test fixture for
ScoreReport is missing the "responsive-critical" entry in the byCategory object,
which violates the ScoreReport.byCategory (Record<Category,
CategoryScoreResult>) expectation and will break code that iterates CATEGORIES;
update the test data used in orchestrator.test.ts to add a "responsive-critical"
key with a valid CategoryScoreResult shape (same fields as the other category
entries) so that byCategory includes all categories from
CategorySchema/CATEGORIES referenced in src/core/engine/scoring.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/config.json`:
- Around line 10-12: The example config's "raw-value" object uses outdated
override values; update the "raw-value" entry (the keys "score" and "severity")
in examples/config.json to match the calibrated baseline defaults (use severity
"missing-info" and score -3) so the sample aligns with current experiment
defaults and won't mislead users.

In `@src/agents/report-generator.test.ts`:
- Around line 11-14: ALL_CATEGORIES in src/agents/report-generator.test.ts omits
the "responsive-critical" category, leaving the test fixture Record<Category,
CategoryScoreResult> incomplete; update the ALL_CATEGORIES array/constant to
include "responsive-critical" alongside "pixel-critical", "token-management",
"code-quality", and "minor" so the test generates a full mapping for all
Category keys used by the report formatting logic (look for the ALL_CATEGORIES
variable in this test file and add the missing category string).

In `@src/core/engine/scoring.ts`:
- Around line 359-368: The getCategoryLabel function is duplicating the label
map; replace the local labels Record<Category,string> with the shared
CATEGORY_LABELS constant: remove the inline labels object in getCategoryLabel
and return CATEGORY_LABELS[category] instead, and add an import for
CATEGORY_LABELS (and Category if needed) from the contracts module so
getCategoryLabel reuses the existing mapping.

In `@src/core/rules/rule-config.ts`:
- Around line 85-94: The rule "missing-component" in rule-config.ts has been
lowered to score -5 which violates the guideline to keep component-related rule
scores at or above the code-quality floor until full fixture rerun; update the
"missing-component" entry (key: "missing-component") to restore the approved
floor score (do not lower it based on small-fixture calibration), leaving
enabled: true and the options (minRepetitions, structureMinRepetitions,
maxFingerprintDepth) unchanged.

In `@src/core/rules/token/index.ts`:
- Around line 55-73: The current TEXT-node branch only flags raw fonts when both
fontFamily and fontSize are unbound; change the condition in the TEXT branch
(the hasStyleReference(node, "text") check and the hasBoundVariable calls) so
that any unbound font axis counts as raw-value debt — e.g., replace the &&
between hasBoundVariable(node,"fontFamily") and
hasBoundVariable(node,"fontSize") with || (so nodes with either fontFamily or
fontSize unbound are reported). Keep the rest of the logic that builds fontParts
and returns rawValueDef.id/nodeId/nodePath/message unchanged.

---

Outside diff comments:
In `@src/agents/orchestrator.test.ts`:
- Around line 86-136: The test fixture for ScoreReport is missing the
"responsive-critical" entry in the byCategory object, which violates the
ScoreReport.byCategory (Record<Category, CategoryScoreResult>) expectation and
will break code that iterates CATEGORIES; update the test data used in
orchestrator.test.ts to add a "responsive-critical" key with a valid
CategoryScoreResult shape (same fields as the other category entries) so that
byCategory includes all categories from CategorySchema/CATEGORIES referenced in
src/core/engine/scoring.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2af08228-e801-49a5-bdbe-68ea90d11559

📥 Commits

Reviewing files that changed from the base of the PR and between 750217f and 1df5a76.

📒 Files selected for processing (50)
  • .claude/agents/rule-discovery/designer.md
  • .github/ISSUE_TEMPLATE/bug.yml
  • docs/REFERENCE.md
  • examples/config.json
  • src/agents/analysis-agent.test.ts
  • src/agents/evidence-collector.test.ts
  • src/agents/gap-rule-report.test.ts
  • src/agents/orchestrator.test.ts
  • src/agents/report-generator.test.ts
  • src/agents/tuning-agent.test.ts
  • src/cli/docs.ts
  • src/core/contracts/category.ts
  • src/core/contracts/rule.ts
  • src/core/engine/integration.test.ts
  • src/core/engine/rule-engine.test.ts
  • src/core/engine/scoring.test.ts
  • src/core/engine/scoring.ts
  • src/core/rules/behavior/index.ts
  • src/core/rules/behavior/overflow-behavior-unknown.test.ts
  • src/core/rules/behavior/prototype-link-in-design.test.ts
  • src/core/rules/behavior/text-truncation-unhandled.test.ts
  • src/core/rules/behavior/wrap-behavior-unknown.test.ts
  • src/core/rules/component/detached-instance.test.ts
  • src/core/rules/component/index.test.ts
  • src/core/rules/component/index.ts
  • src/core/rules/component/missing-component.test.ts
  • src/core/rules/component/variant-structure-mismatch.test.ts
  • src/core/rules/config-loader.test.ts
  • src/core/rules/config-loader.ts
  • src/core/rules/index.ts
  • src/core/rules/naming/default-name.test.ts
  • src/core/rules/naming/inconsistent-naming-convention.test.ts
  • src/core/rules/naming/index.ts
  • src/core/rules/naming/non-semantic-name.test.ts
  • src/core/rules/naming/numeric-suffix-name.test.ts
  • src/core/rules/naming/too-long-name.test.ts
  • src/core/rules/rule-config.ts
  • src/core/rules/structure/index.ts
  • src/core/rules/structure/no-auto-layout.test.ts
  • src/core/rules/structure/unnecessary-node.test.ts
  • src/core/rules/structure/z-index-dependent-layout.test.ts
  • src/core/rules/token/inconsistent-spacing.test.ts
  • src/core/rules/token/index.ts
  • src/core/rules/token/magic-number-spacing.test.ts
  • src/core/rules/token/multiple-fill-colors.test.ts
  • src/core/rules/token/raw-color.test.ts
  • src/core/rules/token/raw-font.test.ts
  • src/core/rules/token/raw-opacity.test.ts
  • src/core/rules/token/raw-shadow.test.ts
  • src/core/ui-constants.ts
💤 Files with no reviewable changes (19)
  • src/core/rules/index.ts
  • src/core/rules/behavior/text-truncation-unhandled.test.ts
  • src/core/rules/token/magic-number-spacing.test.ts
  • src/core/rules/structure/z-index-dependent-layout.test.ts
  • src/core/rules/behavior/overflow-behavior-unknown.test.ts
  • src/core/rules/naming/too-long-name.test.ts
  • src/core/rules/naming/numeric-suffix-name.test.ts
  • src/core/rules/behavior/wrap-behavior-unknown.test.ts
  • src/core/rules/token/raw-opacity.test.ts
  • src/core/rules/behavior/prototype-link-in-design.test.ts
  • src/core/rules/token/multiple-fill-colors.test.ts
  • src/core/rules/token/raw-shadow.test.ts
  • src/core/rules/token/raw-font.test.ts
  • src/core/rules/behavior/index.ts
  • src/core/rules/component/index.test.ts
  • src/core/rules/token/raw-color.test.ts
  • src/core/rules/structure/unnecessary-node.test.ts
  • src/core/rules/token/inconsistent-spacing.test.ts
  • src/core/rules/config-loader.ts

Comment on lines +85 to 94
"missing-component": {
severity: "risk",
score: -5,
depthWeight: 1.2,
enabled: true,
},
"deep-nesting": {
severity: "risk",
score: -4,
enabled: true,
options: {
maxDepth: 5,
minRepetitions: 3,
structureMinRepetitions: 2,
maxFingerprintDepth: 3,
},
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep missing-component above the code-quality floor until the full fixture rerun finishes.

The PR notes that real-fixture re-analysis is still pending, so leaving this component rule at -5 weakens a signal the repo explicitly says not to lower on small-fixture calibration.

Suggested adjustment
   "missing-component": {
     severity: "risk",
-    score: -5,
+    score: -7,
     enabled: true,
     options: {
       minRepetitions: 3,

As per coding guidelines, "Component-related rule scores (missing-component, etc.) should NOT be lowered based on small fixture calibration".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/rules/rule-config.ts` around lines 85 - 94, The rule
"missing-component" in rule-config.ts has been lowered to score -5 which
violates the guideline to keep component-related rule scores at or above the
code-quality floor until full fixture rerun; update the "missing-component"
entry (key: "missing-component") to restore the approved floor score (do not
lower it based on small-fixture calibration), leaving enabled: true and the
options (minRepetitions, structureMinRepetitions, maxFingerprintDepth)
unchanged.

let-sunny and others added 5 commits March 28, 2026 23:14
- Add missing responsive-critical to orchestrator + report-generator tests
- Restore missing-component score to -7 (CLAUDE.md guideline)
- Fix raw-value font check: flag partial tokenization (fontFamily OR fontSize)
- Reuse CATEGORY_LABELS in getCategoryLabel (remove duplication)
- Align example config with baseline defaults

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add optional subType field to RuleViolation for programmatic grouping
  (e.g., raw-value has color/font/shadow/opacity/spacing sub-types)
- Create rule-messages.ts with all message template functions
- Replace inline message strings in all 15 rules with centralized constants
- Include subType in JSON output (buildResultJson)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…), add subType to default-name and irregular-spacing

- Remove missing-responsive-behavior rule (15→14 rules) — duplicates no-auto-layout
- Add subType to default-name: frame/group/vector/shape/text/image/component/instance
- Add subType to irregular-spacing: padding/gap
- Separate vector from shape in default-name subType for granular control

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

- Rename rule: group-usage → non-layout-container (broader scope)
- Add Section detection: Sections used as layout containers are flagged
- SubTypes: group (blocking, -8) and section (same score, flagged only with children)
- Remove missing-responsive-behavior (duplicate of no-auto-layout)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p/grid)

Messages pre-defined for all 4 sub-types. Currently only max-width is
detected — min-width, wrap, and grid conditions to be implemented in #152.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/engine/scoring.test.ts (1)

420-440: 🧹 Nitpick | 🔵 Trivial

Add a regression assertion for subType in the JSON output.

buildResultJson() now emits violation.subType when present, but these assertions still only cover ruleId, severity, nodeId, nodePath, and message. A single case with issue.violation.subType set would keep this new JSON contract from regressing unnoticed.

🧪 Possible test extension
   it("includes detailed issues list with severity and node info", () => {
+    const tokenIssue = makeIssue({
+      ruleId: "raw-value",
+      category: "token-management",
+      severity: "missing-info",
+    });
+    tokenIssue.violation.subType = "color";
+
     const result = makeResult([
       makeIssue({ ruleId: "no-auto-layout", category: "pixel-critical", severity: "blocking" }),
-      makeIssue({ ruleId: "raw-value", category: "token-management", severity: "missing-info" }),
+      tokenIssue,
     ]);
     const scores = calculateScores(result);
     const json = buildResultJson("TestFile", result, scores);
     const issues = json.issues as Array<{ ruleId: string; severity: string; nodeId: string; nodePath: string; message: string }>;
 
     expect(issues).toHaveLength(2);
     expect(issues[0]).toMatchObject({
       ruleId: "no-auto-layout",
       severity: "blocking",
       nodeId: expect.any(String),
       nodePath: expect.any(String),
       message: expect.any(String),
     });
     expect(issues[1]).toMatchObject({
       ruleId: "raw-value",
+      subType: "color",
       severity: "missing-info",
     });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/engine/scoring.test.ts` around lines 420 - 440, Add a regression
assertion to the test that verifies violation.subType is emitted by
buildResultJson: update the "includes detailed issues list..." test to create at
least one issue with issue.violation.subType set (via makeIssue), call
makeResult/calculateScores/buildResultJson as already done, and assert that the
corresponding entry in json.issues includes subType (e.g.,
expect(issues[0].subType).toEqual(<the subType string>) or use toMatchObject
including subType). This ensures buildResultJson continues to include
violation.subType when present.
♻️ Duplicate comments (1)
src/agents/report-generator.test.ts (1)

10-15: 🧹 Nitpick | 🔵 Trivial

Reuse the canonical category list here.

ALL_CATEGORIES already caused one taxonomy drift bug, and the local order is still different from src/core/contracts/category.ts (token-management comes before code-quality). Importing CATEGORIES for this fixture keeps the test exhaustive and ordered like production.

♻️ Possible refactor
-import type { Category } from "../core/contracts/category.js";
+import { CATEGORIES, type Category } from "../core/contracts/category.js";
 
-const ALL_CATEGORIES: Category[] = [
-  "pixel-critical",
-  "responsive-critical",
-  "token-management",
-  "code-quality",
-  "minor",
-];
+const ALL_CATEGORIES: Category[] = [...CATEGORIES];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/report-generator.test.ts` around lines 10 - 15, Replace the
locally defined ALL_CATEGORIES test fixture with the canonical CATEGORIES export
to avoid taxonomy drift: remove the ALL_CATEGORIES constant and import the
CATEGORIES symbol (the exported canonical category array) from the module that
defines it, then use CATEGORIES in the test (ensuring its type aligns with
Category[] if needed); this will preserve the production order (e.g.,
token-management before code-quality) and keep the test exhaustive and
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/config.json`:
- Around line 10-12: The config loader currently uses z.record(z.string(),
RuleOverrideSchema) in src/core/rules/config-loader.ts which allows
legacy/removed rule keys; change the schema to validate keys against the
canonical rule-id set (e.g., replace z.string() with z.enum(...) or validate
keys with .refine using the exported supported rule IDs constant such as
SUPPORTED_RULE_IDS or supportedRuleIds) so any unknown keys (including removed
names like raw-color, missing-responsive-behavior or top-level colorTolerance)
are rejected during parsing; update imports to pull the authoritative rule ID
list and ensure RuleOverrideSchema remains the value schema.

In `@src/core/rules/rule-config.ts`:
- Around line 13-33: The change to RULE_ID_CATEGORY removed/renamed several rule
IDs which will make older user configs silently ignored; update
src/core/rules/config-loader.ts to detect unknown or retired rule IDs and either
map them to new IDs via an alias/deprecation table or emit a clear
warning/error; implement a small ALIAS_MAP (deprecatedId -> currentId) lookup
used when loading configs so config-loader validates each incoming ruleId
against RULE_ID_CATEGORY and substitutes or logs a deprecation notice, and
ensure unknown ruleIds that have no alias cause a visible validation error (or
documented fallback) so users are not left with rules that silently fail to
apply.

In `@src/core/rules/structure/index.ts`:
- Around line 296-304: The SECTION branch in the non-layout-container check is
too broad; stop flagging SECTION nodes as blocking by removing or gating that
branch—specifically, in the block that checks node.type === "SECTION" (which
currently returns the object using nonLayoutContainerDef.id and
nonLayoutContainerMsg.section), change the logic to skip SECTION nodes (early
return null) until a stronger layout-container heuristic is implemented, or
replace the children.length check with a more specific predicate before emitting
the rule; update any related tests/assertions that expect SECTION to be
reported.

In `@src/core/rules/token/index.ts`:
- Around line 36-135: The rawValueCheck function currently returns on the first
matched token and therefore only emits a single violation per node; modify
rawValueCheck so it collects all matching subType findings (use an array of
findings within rawValueCheck) instead of returning immediately in each check
(replace each early return with pushing an object holding subType, message,
nodeId/nodePath, ruleId); after all checks, if findings.length > 0 either (A)
return the array of findings (if the engine/check contract supports multiple
results per rule) or (B) fold them into a single aggregated result (e.g.,
ruleId: rawValueDef.id, subTypes: findings.map(f=>f.subType), messages:
findings.map(f=>f.message) or a combined message) and update callers
accordingly; reference rawValueCheck, rawValueDef, rawValueMsg,
hasStyleReference, hasBoundVariable when making these changes.

---

Outside diff comments:
In `@src/core/engine/scoring.test.ts`:
- Around line 420-440: Add a regression assertion to the test that verifies
violation.subType is emitted by buildResultJson: update the "includes detailed
issues list..." test to create at least one issue with issue.violation.subType
set (via makeIssue), call makeResult/calculateScores/buildResultJson as already
done, and assert that the corresponding entry in json.issues includes subType
(e.g., expect(issues[0].subType).toEqual(<the subType string>) or use
toMatchObject including subType). This ensures buildResultJson continues to
include violation.subType when present.

---

Duplicate comments:
In `@src/agents/report-generator.test.ts`:
- Around line 10-15: Replace the locally defined ALL_CATEGORIES test fixture
with the canonical CATEGORIES export to avoid taxonomy drift: remove the
ALL_CATEGORIES constant and import the CATEGORIES symbol (the exported canonical
category array) from the module that defines it, then use CATEGORIES in the test
(ensuring its type aligns with Category[] if needed); this will preserve the
production order (e.g., token-management before code-quality) and keep the test
exhaustive and consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0f9bd16b-8c5e-49e0-be7c-f3c2fa842a13

📥 Commits

Reviewing files that changed from the base of the PR and between 1df5a76 and 0a9a7a8.

📒 Files selected for processing (14)
  • docs/REFERENCE.md
  • examples/config.json
  • src/agents/orchestrator.test.ts
  • src/agents/report-generator.test.ts
  • src/core/contracts/rule.ts
  • src/core/engine/rule-engine.test.ts
  • src/core/engine/scoring.test.ts
  • src/core/engine/scoring.ts
  • src/core/rules/component/index.ts
  • src/core/rules/naming/index.ts
  • src/core/rules/rule-config.ts
  • src/core/rules/rule-messages.ts
  • src/core/rules/structure/index.ts
  • src/core/rules/token/index.ts

Comment on lines +296 to +304
if (node.type === "SECTION") {
// Only flag Sections that have children (used as layout container)
if (!node.children || node.children.length === 0) return null;
return {
ruleId: missingResponsiveBehaviorDef.id,
ruleId: nonLayoutContainerDef.id,
subType: "section" as const,
nodeId: node.id,
nodePath: context.path.join(" > "),
message: `"${node.name}" has no responsive behavior configured — apply auto-layout or set constraints`,
message: nonLayoutContainerMsg.section(node.name),
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 29, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The SECTION branch is too broad for a blocking rule.

children.length > 0 does not prove a section is being used as a layout container; it only proves the section is non-empty. With the current predicate, any ordinary section wrapper in an analyzed subtree will raise a blocking non-layout-container issue. Please gate this on a stronger signal or skip SECTION until that heuristic exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/rules/structure/index.ts` around lines 296 - 304, The SECTION branch
in the non-layout-container check is too broad; stop flagging SECTION nodes as
blocking by removing or gating that branch—specifically, in the block that
checks node.type === "SECTION" (which currently returns the object using
nonLayoutContainerDef.id and nonLayoutContainerMsg.section), change the logic to
skip SECTION nodes (early return null) until a stronger layout-container
heuristic is implemented, or replace the children.length check with a more
specific predicate before emitting the rule; update any related tests/assertions
that expect SECTION to be reported.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Keeping as-is. Section is not a layout container by design — using it as one is a semantic misuse, which is exactly what this rule flags. children.length > 0 is sufficient because a non-empty Section in a UI layout tree means someone is using it structurally.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@let-sunny — Got it, I'm here and responsive! 👋 Let me know if you have any questions or feedback on the review comment about the SECTION branch in non-layout-container.


🧠 Learnings used
Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/**/rules/rule-config.ts : `no-auto-layout` is the single highest-impact rule with score -10 — empirically validated via ablation experiments

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/cli/**/*.ts : Full-file analysis is discouraged — use section or page level analysis with `node-id` required in URL

Learnt from: let-sunny
Repo: let-sunny/canicode PR: 93
File: src/core/rules/rule-config.ts:76-80
Timestamp: 2026-03-26T01:28:57.785Z
Learning: In the let-sunny/canicode repo, `src/core/rules/rule-config.ts` is automatically adjusted by a nightly calibration pipeline. Do NOT suggest adding inline comments to this file for calibration rationale — the change evidence is tracked in PR descriptions, commit messages, and `data/calibration-evidence.json` instead. Inline comments would create clutter as the file is frequently auto-modified.

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/**/rules/**/*.ts : All rule scores, severity, and thresholds are managed in `rules/rule-config.ts` — rule logic and score config must be intentionally separated

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/**/rules/rule-config.ts : Component-related rule scores (missing-component, etc.) should NOT be lowered based on small fixture calibration

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@let-sunny, understood — that's a clear and well-reasoned distinction. Since Figma's SECTION type is explicitly not a layout container, any non-empty SECTION in a layout tree represents a semantic misuse, making children.length > 0 the correct and sufficient gate. I'll defer to your judgment here.


✏️ Learnings added
Learnt from: let-sunny
Repo: let-sunny/canicode PR: 154
File: src/core/rules/structure/index.ts:296-304
Timestamp: 2026-03-29T00:24:13.455Z
Learning: In let-sunny/canicode, the `non-layout-container` rule in `src/core/rules/structure/index.ts` intentionally flags non-empty `SECTION` nodes (`children.length > 0`) because Figma `SECTION` is not a layout container by design — using it structurally with children is treated as semantic misuse. This predicate is considered sufficient and intentional; do not flag it as too broad.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/**/rules/rule-config.ts : `no-auto-layout` is the single highest-impact rule with score -10 — empirically validated via ablation experiments

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/**/rules/rule-config.ts : Component-related rule scores (missing-component, etc.) should NOT be lowered based on small fixture calibration

Learnt from: let-sunny
Repo: let-sunny/canicode PR: 93
File: src/core/rules/rule-config.ts:76-80
Timestamp: 2026-03-26T01:28:57.785Z
Learning: In the let-sunny/canicode repo, `src/core/rules/rule-config.ts` is automatically adjusted by a nightly calibration pipeline. Do NOT suggest adding inline comments to this file for calibration rationale — the change evidence is tracked in PR descriptions, commit messages, and `data/calibration-evidence.json` instead. Inline comments would create clutter as the file is frequently auto-modified.

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/**/rules/**/*.ts : All rule scores, severity, and thresholds are managed in `rules/rule-config.ts` — rule logic and score config must be intentionally separated

- Add subType regression test in buildResultJson
- Use CATEGORIES import in report-generator.test.ts (prevent taxonomy drift)
- Legacy config deprecation → #156 (separate issue)
- raw-value single-violation-per-node → by design (RuleCheckFn contract)
- SECTION detection condition kept as-is (semantic misuse = valid flag)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/engine/scoring.test.ts (1)

420-446: 🧹 Nitpick | 🔵 Trivial

Good coverage of subType conditional spreading.

The test correctly verifies both cases:

  1. Issues without subType have it omitted from JSON output
  2. Issues with subType="color" have it included

Consider adding an edge case test for empty string subType.

Per the relevant code snippet at scoring.ts:392-399, the guard issue.violation.subType && { subType: ... } treats falsy values (including empty string "") as absent. This is subtle behavior worth documenting with a test.

💡 Suggested edge case test
it("omits subType when it is an empty string", () => {
  const emptySubTypeIssue = makeIssue({ ruleId: "raw-value", category: "token-management", severity: "missing-info" });
  emptySubTypeIssue.violation.subType = "";

  const result = makeResult([emptySubTypeIssue]);
  const scores = calculateScores(result);
  const json = buildResultJson("TestFile", result, scores);
  const issues = json.issues as Array<{ ruleId: string; subType?: string }>;

  expect(issues[0]!["subType"]).toBeUndefined();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/engine/scoring.test.ts` around lines 420 - 446, Add a focused
edge-case test to scoring.test.ts that verifies an empty-string subType is
treated as absent: create an issue via makeIssue, set issue.violation.subType =
"" (empty string), run makeResult and calculateScores, call buildResultJson and
assert that the resulting json.issues[0]["subType"] is undefined; this mirrors
the guard in scoring.ts (the issue.violation.subType && { subType: ... } logic)
and ensures empty string is omitted from output.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/core/engine/scoring.test.ts`:
- Around line 420-446: Add a focused edge-case test to scoring.test.ts that
verifies an empty-string subType is treated as absent: create an issue via
makeIssue, set issue.violation.subType = "" (empty string), run makeResult and
calculateScores, call buildResultJson and assert that the resulting
json.issues[0]["subType"] is undefined; this mirrors the guard in scoring.ts
(the issue.violation.subType && { subType: ... } logic) and ensures empty string
is omitted from output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7d2ecb8d-297d-4ec0-a473-86b0cc6a6eea

📥 Commits

Reviewing files that changed from the base of the PR and between 0a9a7a8 and 923f843.

📒 Files selected for processing (2)
  • src/agents/report-generator.test.ts
  • src/core/engine/scoring.test.ts

Verifies that falsy subType (empty string) is omitted from JSON output,
matching the conditional spread guard in buildResultJson.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/engine/scoring.test.ts`:
- Around line 452-458: The test reads issues[0] without guaranteeing the array
has elements; update the test that uses makeResult, calculateScores and
buildResultJson to assert the issues array length before indexed access (e.g.,
expect(issues.length).toBeGreaterThan(0) or toEqual(1)) so the subsequent
expect(issues[0]!["subType"]).toBeUndefined() is guarded and satisfies
noUncheckedIndexedAccess.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f44a2e9-8349-4119-8f7a-904eb73dad6e

📥 Commits

Reviewing files that changed from the base of the PR and between 923f843 and ef09329.

📒 Files selected for processing (1)
  • src/core/engine/scoring.test.ts

@let-sunny let-sunny marked this pull request as draft March 29, 2026 00:33
Config files with invalid rule IDs now throw with a clear error message
listing the unknown IDs and all valid options. No legacy alias mapping
needed — no existing users to support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/rules/config-loader.test.ts`:
- Around line 88-98: The test for "throws for unknown rule ID" only asserts
/Unknown rule ID/; update it to assert the full error contract by checking that
the thrown error contains both the unknown ID mention and the list/phrase about
valid IDs. Modify the assertion around loadConfigFile(filePath) (in
src/core/rules/config-loader.test.ts, test "throws for unknown rule ID") to
either use a regex that matches both "Unknown rule ID" and "valid IDs" (e.g.
/Unknown rule ID[\s\S]*valid IDs/) or explicitly inspect error.message to assert
it includes both substrings, ensuring the error from loadConfigFile contains
both pieces.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 082a6e19-5756-44f0-9d21-07759e477846

📥 Commits

Reviewing files that changed from the base of the PR and between ef09329 and b117d58.

📒 Files selected for processing (2)
  • src/core/rules/config-loader.test.ts
  • src/core/rules/config-loader.ts

- Guard indexed array access with length assertion (noUncheckedIndexedAccess)
- Strengthen unknown rule ID test to assert both error and valid IDs list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@let-sunny let-sunny merged commit 269c46b into main Mar 29, 2026
3 checks passed
@let-sunny let-sunny deleted the refactor/rule-categories-v2 branch March 29, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant