Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,9 @@ reviews:
CI workflows. This project uses pnpm as package manager and Node.js 24.
Ensure workflows use pnpm/action-setup@v4 and --frozen-lockfile.

# Pre-merge check overrides
# TypeScript strict types serve as documentation; 80% docstring threshold is excessive
pre_merge_checks:
docstring_coverage:
threshold: 40
enabled: false

chat:
auto_reply: true
2 changes: 1 addition & 1 deletion src/agents/analysis-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function createMockIssue(overrides: {
definition: {
id: overrides.ruleId,
name: `Rule ${overrides.ruleId}`,
category: "layout",
category: "structure",
why: "test reason",
impact: "test impact",
fix: "test fix",
Expand Down
1 change: 1 addition & 0 deletions src/agents/contracts/calibration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const CalibrationConfigSchema = z.object({
});

export type CalibrationConfig = z.infer<typeof CalibrationConfigSchema>;
export type CalibrationConfigInput = z.input<typeof CalibrationConfigSchema>;

export interface CalibrationRun {
config: CalibrationConfig;
Expand Down
64 changes: 32 additions & 32 deletions src/agents/evidence-collector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,25 +192,25 @@ describe("evidence-collector", () => {
const file = {
schemaVersion: DISCOVERY_EVIDENCE_SCHEMA_VERSION,
entries: [
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
],
};
writeFileSync(disPath, JSON.stringify(file), "utf-8");

const result = loadDiscoveryEvidence(disPath);
expect(result).toHaveLength(1);
expect(result[0]!.category).toBe("layout");
expect(result[0]!.category).toBe("structure");
});

it("loads entries from legacy plain-array format (v0 fallback)", () => {
const entries: DiscoveryEvidenceEntry[] = [
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
];
writeFileSync(disPath, JSON.stringify(entries), "utf-8");

const result = loadDiscoveryEvidence(disPath);
expect(result).toHaveLength(1);
expect(result[0]!.category).toBe("layout");
expect(result[0]!.category).toBe("structure");
});

it("handles malformed JSON gracefully", () => {
Expand All @@ -221,7 +221,7 @@ describe("evidence-collector", () => {

it("skips invalid entries in legacy array", () => {
writeFileSync(disPath, JSON.stringify([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ bad: "entry" },
]), "utf-8");

Expand All @@ -233,7 +233,7 @@ describe("evidence-collector", () => {
const file = {
schemaVersion: DISCOVERY_EVIDENCE_SCHEMA_VERSION,
entries: [
{ description: "good", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "good", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ bad: "entry" },
{ description: "also good", category: "color", impact: "easy", fixture: "fx2", timestamp: "t2", source: "gap-analysis" },
],
Expand All @@ -250,7 +250,7 @@ describe("evidence-collector", () => {
const file = {
schemaVersion: 999,
entries: [
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
],
};
writeFileSync(disPath, JSON.stringify(file), "utf-8");
Expand All @@ -262,7 +262,7 @@ describe("evidence-collector", () => {
describe("appendDiscoveryEvidence", () => {
it("creates file in versioned format", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "gap-analysis" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "gap-analysis" },
], disPath);

const raw = JSON.parse(readFileSync(disPath, "utf-8")) as { schemaVersion: number; entries: DiscoveryEvidenceEntry[] };
Expand All @@ -273,7 +273,7 @@ describe("evidence-collector", () => {

it("appends to existing entries (different keys)", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
], disPath);

appendDiscoveryEvidence([
Expand All @@ -286,12 +286,12 @@ describe("evidence-collector", () => {

it("deduplicates by (category + description + fixture), last-write-wins", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
], disPath);

// Same category+description+fixture, different impact/timestamp → replaces
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "moderate", fixture: "fx1", timestamp: "t2", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "moderate", fixture: "fx1", timestamp: "t2", source: "evaluation" },
], disPath);

const raw = JSON.parse(readFileSync(disPath, "utf-8")) as { entries: DiscoveryEvidenceEntry[] };
Expand All @@ -302,11 +302,11 @@ describe("evidence-collector", () => {

it("dedupe is case-insensitive for category and description", () => {
appendDiscoveryEvidence([
{ description: "Gap One", category: "Layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "Gap One", category: "Structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
], disPath);

appendDiscoveryEvidence([
{ description: "gap one", category: "layout", impact: "easy", fixture: "fx1", timestamp: "t2", source: "evaluation" },
{ description: "gap one", category: "structure", impact: "easy", fixture: "fx1", timestamp: "t2", source: "evaluation" },
], disPath);

const raw = JSON.parse(readFileSync(disPath, "utf-8")) as { entries: DiscoveryEvidenceEntry[] };
Expand All @@ -316,11 +316,11 @@ describe("evidence-collector", () => {

it("dedupe is case-insensitive for fixture", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "FX1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "FX1", timestamp: "t1", source: "evaluation" },
], disPath);

appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "easy", fixture: "fx1", timestamp: "t2", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "easy", fixture: "fx1", timestamp: "t2", source: "evaluation" },
], disPath);

const raw = JSON.parse(readFileSync(disPath, "utf-8")) as { entries: DiscoveryEvidenceEntry[] };
Expand All @@ -330,8 +330,8 @@ describe("evidence-collector", () => {

it("dedupes within a single append call (last row wins)", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "layout", impact: "easy", fixture: "fx1", timestamp: "t2", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "easy", fixture: "fx1", timestamp: "t2", source: "evaluation" },
], disPath);

const raw = JSON.parse(readFileSync(disPath, "utf-8")) as { entries: DiscoveryEvidenceEntry[] };
Expand All @@ -341,8 +341,8 @@ describe("evidence-collector", () => {

it("same description different fixture → kept as separate entries", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx2", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx2", timestamp: "t1", source: "evaluation" },
], disPath);

const raw = JSON.parse(readFileSync(disPath, "utf-8")) as { entries: DiscoveryEvidenceEntry[] };
Expand All @@ -352,7 +352,7 @@ describe("evidence-collector", () => {
it("migrates legacy array to versioned format on append", () => {
// Write legacy format
writeFileSync(disPath, JSON.stringify([
{ description: "old", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t0", source: "evaluation" },
{ description: "old", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t0", source: "evaluation" },
]), "utf-8");

appendDiscoveryEvidence([
Expand All @@ -366,7 +366,7 @@ describe("evidence-collector", () => {

it("does nothing for empty entries", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
], disPath);
const before = readFileSync(disPath, "utf-8");

Expand All @@ -382,7 +382,7 @@ describe("evidence-collector", () => {
const before = readFileSync(disPath, "utf-8");

expect(() => appendDiscoveryEvidence([
{ description: "new", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "new", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
], disPath)).toThrow(/Unsupported discovery-evidence schemaVersion/);

// File must not be overwritten
Expand All @@ -393,12 +393,12 @@ describe("evidence-collector", () => {
describe("pruneDiscoveryEvidence", () => {
it("removes entries for specified categories (case-insensitive)", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "Layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap2", category: "layout", impact: "hard", fixture: "fx2", timestamp: "t2", source: "gap-analysis" },
{ description: "gap1", category: "Structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap2", category: "structure", impact: "hard", fixture: "fx2", timestamp: "t2", source: "gap-analysis" },
{ description: "gap3", category: "color", impact: "moderate", fixture: "fx1", timestamp: "t1", source: "evaluation" },
], disPath);

pruneDiscoveryEvidence(["layout"], disPath);
pruneDiscoveryEvidence(["structure"], disPath);

const raw = JSON.parse(readFileSync(disPath, "utf-8")) as { entries: DiscoveryEvidenceEntry[] };
expect(raw.entries).toHaveLength(1);
Expand All @@ -407,10 +407,10 @@ describe("evidence-collector", () => {

it("writes versioned format after prune", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
], disPath);

pruneDiscoveryEvidence(["layout"], disPath);
pruneDiscoveryEvidence(["structure"], disPath);

const raw = JSON.parse(readFileSync(disPath, "utf-8")) as { schemaVersion: number; entries: DiscoveryEvidenceEntry[] };
expect(raw.schemaVersion).toBe(DISCOVERY_EVIDENCE_SCHEMA_VERSION);
Expand All @@ -419,7 +419,7 @@ describe("evidence-collector", () => {

it("does nothing for empty categories", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
], disPath);

pruneDiscoveryEvidence([], disPath);
Expand All @@ -430,23 +430,23 @@ describe("evidence-collector", () => {

it("trims categories when matching", () => {
appendDiscoveryEvidence([
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
], disPath);

pruneDiscoveryEvidence([" layout "], disPath);
pruneDiscoveryEvidence([" structure "], disPath);

const raw = JSON.parse(readFileSync(disPath, "utf-8")) as { entries: DiscoveryEvidenceEntry[] };
expect(raw.entries).toHaveLength(0);
});

it("throws when file has unsupported schemaVersion", () => {
const file = { schemaVersion: 999, entries: [
{ description: "gap1", category: "layout", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
{ description: "gap1", category: "structure", impact: "hard", fixture: "fx1", timestamp: "t1", source: "evaluation" },
]};
writeFileSync(disPath, JSON.stringify(file), "utf-8");
const before = readFileSync(disPath, "utf-8");

expect(() => pruneDiscoveryEvidence(["layout"], disPath)).toThrow(/Unsupported discovery-evidence schemaVersion/);
expect(() => pruneDiscoveryEvidence(["structure"], disPath)).toThrow(/Unsupported discovery-evidence schemaVersion/);

expect(readFileSync(disPath, "utf-8")).toBe(before);
});
Expand Down
6 changes: 3 additions & 3 deletions src/agents/gap-rule-report.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe("generateGapRuleReport", () => {
fileKey: "fx-a",
gaps: [
{
category: "layout",
category: "structure",
area: "Title",
description: "Alignment mismatch",
coveredByExistingRule: false,
Expand All @@ -44,7 +44,7 @@ describe("generateGapRuleReport", () => {
fileKey: "fx-b",
gaps: [
{
category: "layout",
category: "structure",
area: "Title",
description: "Alignment mismatch",
coveredByExistingRule: false,
Expand All @@ -63,7 +63,7 @@ describe("generateGapRuleReport", () => {

expect(gapRunCount).toBe(2);
expect(runCount).toBe(0); // No analysis.json + conversion.json in these dirs
expect(markdown).toContain("layout");
expect(markdown).toContain("structure");
expect(markdown).toContain("text-alignment-mismatch");
expect(markdown).toContain("Repeating patterns");
});
Expand Down
20 changes: 4 additions & 16 deletions src/agents/orchestrator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ describe("runCalibrationEvaluate", () => {
scoreReport: {
overall: { score: 75, maxScore: 100, percentage: 75, grade: "B" as const },
byCategory: {
layout: {
category: "layout" as const,
structure: {
category: "structure" as const,
score: 70,
maxScore: 100,
percentage: 70,
Expand Down Expand Up @@ -133,20 +133,8 @@ describe("runCalibrationEvaluate", () => {
diversityScore: 100,
bySeverity: { blocking: 0, risk: 0, "missing-info": 0, suggestion: 0 },
},
"ai-readability": {
category: "ai-readability" 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 },
},
"handoff-risk": {
category: "handoff-risk" as const,
behavior: {
category: "behavior" as const,
score: 100,
maxScore: 100,
percentage: 100,
Expand Down
4 changes: 2 additions & 2 deletions src/agents/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { AnalysisFile, AnalysisNode, AnalysisNodeType } from "../core/contr
import { analyzeFile } from "../core/engine/rule-engine.js";
import { RULE_CONFIGS } from "../core/rules/rule-config.js";

import type { CalibrationConfig } from "./contracts/calibration.js";
import type { CalibrationConfigInput } from "./contracts/calibration.js";
import { CalibrationConfigSchema } from "./contracts/calibration.js";
import type { NodeIssueSummary } from "./contracts/analysis-agent.js";
import type { ScoreReport } from "../core/engine/scoring.js";
Expand Down Expand Up @@ -172,7 +172,7 @@ function buildRuleScoresMap(): Record<string, { score: number; severity: string
* Run Step 1 only: analysis + save JSON output
*/
export async function runCalibrationAnalyze(
config: CalibrationConfig
config: CalibrationConfigInput
): Promise<{
analysisOutput: ReturnType<typeof runAnalysisAgent> extends infer T ? T : never;
ruleScores: Record<string, { score: number; severity: string }>;
Expand Down
9 changes: 4 additions & 5 deletions src/agents/report-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import {
} from "./report-generator.js";

const ALL_CATEGORIES: Category[] = [
"layout",
"structure",
"token",
"component",
"naming",
"ai-readability",
"handoff-risk",
"behavior",
];

function buildCategoryScore(
Expand Down Expand Up @@ -222,7 +221,7 @@ describe("generateCalibrationReport", () => {
it("renders new rule proposals when they exist", () => {
const proposal: NewRuleProposal = {
suggestedId: "shadow-complexity",
category: "layout",
category: "structure",
description: "Detects complex shadow configurations",
suggestedSeverity: "risk",
suggestedScore: -4,
Expand All @@ -234,7 +233,7 @@ describe("generateCalibrationReport", () => {
const report = generateCalibrationReport(data);

expect(report).toContain("### shadow-complexity");
expect(report).toContain("layout");
expect(report).toContain("structure");
expect(report).toContain("Detects complex shadow configurations");
expect(report).toContain("risk");
expect(report).toContain("-4");
Expand Down
1 change: 0 additions & 1 deletion src/cli/commands/internal/calibrate-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export function registerCalibrateRun(cli: CAC): void {
input,
maxConversionNodes: options.maxNodes ?? 5,
samplingStrategy: (options.sampling as "all" | "top-issues" | "random") ?? "top-issues",
outputPath: "unused",
...(figmaToken && { token: figmaToken }),
});

Expand Down
Loading
Loading