diff --git a/.claude/agents/calibration/converter.md b/.claude/agents/calibration/converter.md index e54d53a8..6924cbce 100644 --- a/.claude/agents/calibration/converter.md +++ b/.claude/agents/calibration/converter.md @@ -188,7 +188,7 @@ Write results to `$RUN_DIR/conversion.json`. "uncoveredStruggles": [ { "description": "A difficulty not covered by any flagged rule", - "suggestedCategory": "pixel-critical | responsive-critical | code-quality | token-management | interaction | minor", + "suggestedCategory": "pixel-critical | responsive-critical | code-quality | token-management | interaction | semantic", "estimatedImpact": "easy | moderate | hard | failed" } ] diff --git a/.claude/agents/rule-discovery/designer.md b/.claude/agents/rule-discovery/designer.md index d34a54f0..c6b693e9 100644 --- a/.claude/agents/rule-discovery/designer.md +++ b/.claude/agents/rule-discovery/designer.md @@ -21,7 +21,7 @@ You will receive: - Read `src/core/rules/rule-config.ts` for score/severity conventions 3. Design the rule: - **Rule ID**: kebab-case, descriptive (e.g., `raw-value`) - - **Category**: existing (`pixel-critical | responsive-critical | code-quality | token-management | interaction | minor`) or propose a new category if none fits. New categories require justification. + - **Category**: existing (`pixel-critical | responsive-critical | code-quality | token-management | interaction | semantic`) or propose a new category if none fits. New categories require justification. - **Severity**: `blocking | risk | missing-info | suggestion` - **Initial score**: based on estimated impact on implementation difficulty - **Check logic**: what condition triggers the violation diff --git a/.claude/skills/canicode/SKILL.md b/.claude/skills/canicode/SKILL.md index aa22b563..3b177aec 100644 --- a/.claude/skills/canicode/SKILL.md +++ b/.claude/skills/canicode/SKILL.md @@ -58,7 +58,7 @@ npx canicode analyze --json ## What It Reports -16 rules across 6 categories: Pixel Critical, Responsive Critical, Code Quality, Token Management, Interaction, Minor. +16 rules across 6 categories: Pixel Critical, Responsive Critical, Code Quality, Token Management, Interaction, Semantic. Each issue includes: - Rule ID and severity (blocking / risk / missing-info / suggestion) diff --git a/CLAUDE.md b/CLAUDE.md index b7259b7c..81e55a59 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -268,7 +268,7 @@ npm publishing is handled by GitHub CI — **do not run `npm publish` manually** ### Language -- All code, comments, and documentation must be written in English +- All code, comments, documentation, and **GitHub Wiki** must be written in English - This is a global project targeting international users ### Code Style diff --git a/README.md b/README.md index 27a666e1..4c0c9f8f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ CanICode solves this: 2. **Generates a design-tree** — a curated, CSS-ready representation that AI implements more accurately and efficiently than raw Figma data 3. **Scores** responsive readiness, so you fix the design before generating code -- **16 rules** across 6 categories: Pixel Critical, Responsive Critical, Code Quality, Token Management, Interaction, Minor +- **16 rules** across 6 categories: Pixel Critical, Responsive Critical, Code Quality, Token Management, Interaction, Semantic - **Deterministic** — no AI tokens consumed per analysis, runs in milliseconds - **Validated** — [ablation experiments](https://github.com/let-sunny/canicode/wiki) confirmed design-tree achieves 94% pixel accuracy with 5× fewer tokens than raw JSON @@ -87,7 +87,7 @@ claude mcp add canicode -- npx -y -p canicode canicode-mcp | **Code Quality** | 4 | Is the design efficient for AI context? (components, variants, nesting) | | **Token Management** | 2 | Can AI reproduce exact values? (raw values, spacing grid) | | **Interaction** | 2 | Can AI know what happens? (state variants, prototypes) | -| **Minor** | 3 | Can AI infer meaning? (semantic names, conventions) | +| **Semantic** | 3 | Can AI infer meaning? (semantic names, conventions) | Each issue is classified: **Blocking** > **Risk** > **Missing Info** > **Suggestion**. diff --git a/docs/CUSTOMIZATION.md b/docs/CUSTOMIZATION.md index 0acd54ab..65941c2b 100644 --- a/docs/CUSTOMIZATION.md +++ b/docs/CUSTOMIZATION.md @@ -113,20 +113,20 @@ Override score, severity, or enable/disable individual rules: | `deep-nesting` | -3 | risk | | `missing-component` | -7 | risk | | `detached-instance` | -4 | risk | -| `variant-structure-mismatch` | -4 | risk | +| `variant-structure-mismatch` | -6 | risk | **Token Management (2 rules)** | Rule ID | Default Score | Default Severity | |---------|--------------|-----------------| | `raw-value` | -3 | missing-info | -| `irregular-spacing` | -2 | missing-info | +| `irregular-spacing` | -5 | risk | -**Minor (3 rules)** +**Semantic (3 rules)** | Rule ID | Default Score | Default Severity | |---------|--------------|-----------------| -| `non-semantic-name` | -1 | suggestion | +| `non-semantic-name` | -4 | risk | | `inconsistent-naming-convention` | -1 | suggestion | | `non-standard-naming` | -3 | suggestion | @@ -134,7 +134,7 @@ Override score, severity, or enable/disable individual rules: | Rule ID | Default Score | Default Severity | |---------|--------------|-----------------| -| `missing-interaction-state` | -3 | missing-info | +| `missing-interaction-state` | -5 | risk | | `missing-prototype` *(disabled)* | -3 | missing-info | @@ -146,7 +146,7 @@ Override score, severity, or enable/disable individual rules: "rules": { "no-auto-layout": { "score": -15 }, "raw-value": { "score": -5, "severity": "risk" }, - "non-semantic-name": { "score": -3, "severity": "risk" } + "non-semantic-name": { "score": -6, "severity": "blocking" } } } ``` diff --git a/src/agents/orchestrator.test.ts b/src/agents/orchestrator.test.ts index 65909291..dc602120 100644 --- a/src/agents/orchestrator.test.ts +++ b/src/agents/orchestrator.test.ts @@ -121,8 +121,8 @@ describe("runCalibrationEvaluate", () => { diversityScore: 100, bySeverity: { blocking: 0, risk: 0, "missing-info": 0, suggestion: 0 }, }, - minor: { - category: "minor" as const, + semantic: { + category: "semantic" as const, score: 100, maxScore: 100, percentage: 100, diff --git a/src/cli/commands/internal/rule-discovery.test.ts b/src/cli/commands/internal/rule-discovery.test.ts index 5bb9d811..a77fcbae 100644 --- a/src/cli/commands/internal/rule-discovery.test.ts +++ b/src/cli/commands/internal/rule-discovery.test.ts @@ -70,7 +70,7 @@ describe("readDecision", () => { writeFileSync(join(runDir, "decision.json"), JSON.stringify({ decision: "ADJUST", ruleId: "my-rule", - category: "minor", + category: "semantic", reason: "Score too high", })); diff --git a/src/core/contracts/category.ts b/src/core/contracts/category.ts index 62121fcc..72d7d0f5 100644 --- a/src/core/contracts/category.ts +++ b/src/core/contracts/category.ts @@ -5,7 +5,7 @@ export const CategorySchema = z.enum([ "responsive-critical", "code-quality", "token-management", - "minor", + "semantic", "interaction", ]); @@ -18,6 +18,6 @@ export const CATEGORY_LABELS: Record = { "responsive-critical": "Responsive Critical", "code-quality": "Code Quality", "token-management": "Token Management", - "minor": "Minor", + "semantic": "Semantic", "interaction": "Interaction", }; diff --git a/src/core/contracts/rule.ts b/src/core/contracts/rule.ts index 0a135a5e..0876cb98 100644 --- a/src/core/contracts/rule.ts +++ b/src/core/contracts/rule.ts @@ -113,7 +113,7 @@ export type RuleId = // Interaction — missing state variants and prototype links for interactive components | "missing-interaction-state" | "missing-prototype" - // Minor — naming issues with negligible impact (ΔV < 2%) + // Semantic — naming issues with negligible pixel impact (ΔV < 2%) | "non-standard-naming" | "non-semantic-name" | "inconsistent-naming-convention"; diff --git a/src/core/engine/integration.test.ts b/src/core/engine/integration.test.ts index 79574085..63398ec9 100644 --- a/src/core/engine/integration.test.ts +++ b/src/core/engine/integration.test.ts @@ -74,7 +74,7 @@ describe("Integration: fixture → analyze → score", () => { // Exactly 6 categories present const categories = Object.keys(scores.byCategory).sort(); expect(categories).toEqual( - ["code-quality", "interaction", "minor", "pixel-critical", "responsive-critical", "token-management"], + ["code-quality", "interaction", "pixel-critical", "responsive-critical", "semantic", "token-management"], ); // Each category has valid percentages diff --git a/src/core/engine/scoring.test.ts b/src/core/engine/scoring.test.ts index 0b2538cc..6a6b8087 100644 --- a/src/core/engine/scoring.test.ts +++ b/src/core/engine/scoring.test.ts @@ -75,7 +75,7 @@ describe("calculateScores", () => { makeIssue({ ruleId: "no-auto-layout", category: "pixel-critical", severity: "blocking" }), makeIssue({ ruleId: "non-layout-container", category: "pixel-critical", severity: "risk" }), makeIssue({ ruleId: "raw-value", category: "token-management", severity: "missing-info" }), - makeIssue({ ruleId: "non-semantic-name", category: "minor", severity: "suggestion" }), + makeIssue({ ruleId: "non-semantic-name", category: "semantic", severity: "suggestion" }), ]; const scores = calculateScores(makeResult(issues)); @@ -90,14 +90,14 @@ describe("calculateScores", () => { const heavyIssue = makeIssue({ ruleId: "no-auto-layout", category: "pixel-critical", severity: "blocking", score: -10 }); heavyIssue.calculatedScore = -15; // Simulate depthWeight effect - const lightIssue = makeIssue({ ruleId: "non-semantic-name", category: "minor", severity: "suggestion", score: -1 }); + const lightIssue = makeIssue({ ruleId: "non-semantic-name", category: "semantic", severity: "suggestion", score: -1 }); lightIssue.calculatedScore = -1; const heavy = calculateScores(makeResult([heavyIssue], 100)); const light = calculateScores(makeResult([lightIssue], 100)); expect(heavy.byCategory["pixel-critical"].weightedIssueCount).toBe(15); - expect(light.byCategory["minor"].weightedIssueCount).toBe(1); + expect(light.byCategory["semantic"].weightedIssueCount).toBe(1); }); it("differentiates rules within the same severity by score", () => { @@ -159,24 +159,24 @@ describe("calculateScores", () => { ], 100)); const lightRule = calculateScores(makeResult([ - makeIssue({ ruleId: "non-semantic-name", category: "minor", severity: "suggestion", score: -1 }), + makeIssue({ ruleId: "non-semantic-name", category: "semantic", severity: "suggestion", score: -1 }), ], 100)); expect(heavyRule.byCategory["pixel-critical"].diversityScore).toBeLessThan( - lightRule.byCategory["minor"].diversityScore + lightRule.byCategory["semantic"].diversityScore ); }); it("low-severity rules have minimal diversity impact (intentional)", () => { const lowSeverity = calculateScores(makeResult([ - makeIssue({ ruleId: "non-semantic-name", category: "minor", severity: "suggestion", score: -1 }), + makeIssue({ ruleId: "non-semantic-name", category: "semantic", severity: "suggestion", score: -1 }), ], 100)); const highSeverity = calculateScores(makeResult([ makeIssue({ ruleId: "no-auto-layout", category: "pixel-critical", severity: "blocking", score: -10 }), ], 100)); - expect(lowSeverity.byCategory["minor"].diversityScore).toBeGreaterThan(50); + expect(lowSeverity.byCategory["semantic"].diversityScore).toBeGreaterThan(50); expect(highSeverity.byCategory["pixel-critical"].diversityScore).toBeLessThan(80); }); @@ -217,7 +217,7 @@ describe("calculateScores", () => { expect(scores.byCategory["token-management"].percentage).toBe(100); expect(scores.byCategory["code-quality"].percentage).toBe(100); expect(scores.byCategory["interaction"].percentage).toBe(100); - expect(scores.byCategory["minor"].percentage).toBe(100); + expect(scores.byCategory["semantic"].percentage).toBe(100); expect(scores.byCategory["responsive-critical"].percentage).toBe(100); }); @@ -292,14 +292,14 @@ describe("calculateGrade (via calculateScores)", () => { it("score < 50% -> F", () => { const issues: AnalysisIssue[] = []; - const categories: Category[] = ["pixel-critical", "responsive-critical", "code-quality", "token-management", "interaction", "minor"]; + const categories: Category[] = ["pixel-critical", "responsive-critical", "code-quality", "token-management", "interaction", "semantic"]; const rulesPerCat: Record = { "pixel-critical": ["no-auto-layout", "non-layout-container", "absolute-position-in-auto-layout"], "responsive-critical": ["fixed-size-in-auto-layout", "missing-size-constraint"], "code-quality": ["missing-component", "detached-instance", "variant-structure-mismatch", "deep-nesting"], "token-management": ["raw-value", "irregular-spacing"], "interaction": ["missing-interaction-state", "missing-prototype"], - "minor": ["non-standard-naming", "non-semantic-name", "inconsistent-naming-convention"], + "semantic": ["non-standard-naming", "non-semantic-name", "inconsistent-naming-convention"], }; for (const cat of categories) { @@ -336,7 +336,7 @@ describe("formatScoreSummary", () => { expect(summary).toContain("Overall: S (100%)"); }); - it("includes all 5 categories", () => { + it("includes all categories", () => { const scores = calculateScores(makeResult([])); const summary = formatScoreSummary(scores); @@ -344,7 +344,8 @@ describe("formatScoreSummary", () => { expect(summary).toContain("responsive-critical:"); expect(summary).toContain("code-quality:"); expect(summary).toContain("token-management:"); - expect(summary).toContain("minor:"); + expect(summary).toContain("interaction:"); + expect(summary).toContain("semantic:"); }); it("includes severity breakdown", () => { @@ -366,7 +367,7 @@ describe("getCategoryLabel", () => { expect(getCategoryLabel("responsive-critical")).toBe("Responsive Critical"); expect(getCategoryLabel("code-quality")).toBe("Code Quality"); expect(getCategoryLabel("token-management")).toBe("Token Management"); - expect(getCategoryLabel("minor")).toBe("Minor"); + expect(getCategoryLabel("semantic")).toBe("Semantic"); }); }); diff --git a/src/core/engine/scoring.ts b/src/core/engine/scoring.ts index 2db184e9..42c9d335 100644 --- a/src/core/engine/scoring.ts +++ b/src/core/engine/scoring.ts @@ -57,12 +57,12 @@ export type Grade = "S" | "A+" | "A" | "B+" | "B" | "C+" | "C" | "D" | "F"; * the per-rule scores in rule-config.ts effectively unused. * * Now: `no-auto-layout` (score: -10, depthWeight: 1.5) at root contributes 15 - * to density, while `non-semantic-name` (score: -1, no depthWeight) contributes 1. + * to density, while `non-semantic-name` (score: -4, no depthWeight) contributes 4. * This makes calibration loop score adjustments flow through to user-facing scores. * * Category weights removed (#196) — overall score is simple average of categories. * Category importance is already encoded in rule scores (pixel-critical -10 - * vs minor -1), so per-category weighting is unnecessary. + * vs semantic -4), so per-category weighting is unnecessary. */ /** diff --git a/src/core/report-html/render.test.ts b/src/core/report-html/render.test.ts index b16325ba..92cba625 100644 --- a/src/core/report-html/render.test.ts +++ b/src/core/report-html/render.test.ts @@ -334,7 +334,7 @@ describe("renderIssueRow", () => { }); it("omits guide when absent", () => { - const noGuide = makeIssue({ ruleId: "test", category: "minor", severity: "suggestion" }); + const noGuide = makeIssue({ ruleId: "test", category: "semantic", severity: "suggestion" }); const html = renderIssueRow(noGuide, "fk"); expect(html).not.toContain("rpt-issue-guide"); }); @@ -361,7 +361,7 @@ describe("renderIssueRow", () => { }); it("escapes HTML in user-facing strings", () => { - const xss = makeIssue({ ruleId: "test", category: "minor", severity: "suggestion" }); + const xss = makeIssue({ ruleId: "test", category: "semantic", severity: "suggestion" }); xss.violation.message = ''; xss.violation.suggestion = ''; const html = renderIssueRow(xss, "fk"); @@ -384,10 +384,10 @@ describe("platform-neutral wording", () => { // ---- Category order ---- describe("category order", () => { - it("renders Minor before Interaction in tabs", () => { + it("renders Semantic before Interaction in tabs", () => { const html = renderReportBody(makeReportData()); - const minorPos = html.indexOf('data-tab="minor"'); + const semanticPos = html.indexOf('data-tab="semantic"'); const interactionPos = html.indexOf('data-tab="interaction"'); - expect(minorPos).toBeLessThan(interactionPos); + expect(semanticPos).toBeLessThan(interactionPos); }); }); diff --git a/src/core/rules/naming/inconsistent-naming-convention.test.ts b/src/core/rules/naming/inconsistent-naming-convention.test.ts index 223ee007..f6a7478d 100644 --- a/src/core/rules/naming/inconsistent-naming-convention.test.ts +++ b/src/core/rules/naming/inconsistent-naming-convention.test.ts @@ -4,7 +4,7 @@ import { inconsistentNamingConvention } from "./index.js"; describe("inconsistent-naming-convention", () => { it("has correct rule definition metadata", () => { expect(inconsistentNamingConvention.definition.id).toBe("inconsistent-naming-convention"); - expect(inconsistentNamingConvention.definition.category).toBe("minor"); + expect(inconsistentNamingConvention.definition.category).toBe("semantic"); }); it("flags node with different convention from dominant siblings", () => { diff --git a/src/core/rules/naming/index.ts b/src/core/rules/naming/index.ts index 75384181..015ebfa6 100644 --- a/src/core/rules/naming/index.ts +++ b/src/core/rules/naming/index.ts @@ -63,7 +63,7 @@ function convertName(name: string, target: string): string { const nonSemanticNameDef: RuleDefinition = { id: "non-semantic-name", name: "Non-Semantic Name", - category: "minor", + category: "semantic", why: "Default or shape names give AI no semantic context — it cannot choose appropriate HTML tags or class names", impact: "AI generates generic
wrappers instead of semantic elements like
,