@vibecodeqa/cli 0.42.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -165
- package/dist/check-meta.js +59 -6
- package/dist/cli.js +299 -762
- package/dist/commands/explain.d.ts +2 -0
- package/dist/commands/explain.js +33 -0
- package/dist/commands/fix.d.ts +6 -0
- package/dist/commands/fix.js +157 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +96 -0
- package/dist/commands/shared.d.ts +4 -0
- package/dist/commands/shared.js +80 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +12 -1
- package/dist/delta.d.ts +45 -0
- package/dist/delta.js +158 -0
- package/dist/detect.js +2 -2
- package/dist/pr-comment.d.ts +1 -1
- package/dist/pr-comment.js +23 -4
- package/dist/report/html.d.ts +1 -1
- package/dist/report/html.js +7 -2
- package/dist/report/pages.d.ts +2 -0
- package/dist/report/pages.js +167 -0
- package/dist/report/styles.d.ts +1 -1
- package/dist/report/styles.js +37 -0
- package/dist/runners/accessibility.js +4 -1
- package/dist/runners/best-practices.js +1 -1
- package/dist/runners/confusion.js +28 -17
- package/dist/runners/design-consistency.d.ts +12 -0
- package/dist/runners/design-consistency.js +125 -0
- package/dist/runners/error-handling.js +18 -2
- package/dist/runners/file-cohesion.d.ts +17 -0
- package/dist/runners/file-cohesion.js +177 -0
- package/dist/runners/frontend-health.d.ts +14 -0
- package/dist/runners/frontend-health.js +206 -0
- package/dist/runners/html-quality.d.ts +8 -0
- package/dist/runners/html-quality.js +203 -0
- package/dist/runners/lint.js +6 -1
- package/dist/runners/react.js +1 -0
- package/dist/runners/secrets.js +7 -2
- package/dist/runners/security.js +7 -1
- package/dist/runners/standards.d.ts +2 -2
- package/dist/runners/standards.js +45 -12
- package/dist/runners/structure.js +1 -1
- package/dist/runners/styling.d.ts +15 -0
- package/dist/runners/styling.js +280 -0
- package/dist/runners/testing.js +3 -1
- package/package.json +2 -2
package/dist/detect.js
CHANGED
|
@@ -89,7 +89,7 @@ export function detectStack(cwd, workspace) {
|
|
|
89
89
|
const linter = allDeps["@biomejs/biome"] ? "biome" : allDeps.eslint ? "eslint" : "none";
|
|
90
90
|
const packageManager = has("pnpm-lock.yaml")
|
|
91
91
|
? "pnpm"
|
|
92
|
-
: has("bun.lockb")
|
|
92
|
+
: has("bun.lockb") || has("bun.lock")
|
|
93
93
|
? "bun"
|
|
94
94
|
: has("yarn.lock")
|
|
95
95
|
? "yarn"
|
|
@@ -167,7 +167,7 @@ export function detectWorkspace(cwd) {
|
|
|
167
167
|
if (parsed.workspaces) {
|
|
168
168
|
const ws = Array.isArray(parsed.workspaces) ? parsed.workspaces : parsed.workspaces.packages || [];
|
|
169
169
|
if (ws.length > 0) {
|
|
170
|
-
tool = has("bun.lockb") ? "bun" : has("yarn.lock") ? "yarn" : "npm";
|
|
170
|
+
tool = has("bun.lockb") || has("bun.lock") ? "bun" : has("yarn.lock") ? "yarn" : "npm";
|
|
171
171
|
globs = ws;
|
|
172
172
|
}
|
|
173
173
|
}
|
package/dist/pr-comment.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Post scan results as a GitHub PR comment. Upserts to avoid duplicates. */
|
|
2
2
|
import type { TrendDelta } from "./trend.js";
|
|
3
3
|
import type { VibeReport } from "./types.js";
|
|
4
|
-
export declare function postPRComment(report: VibeReport, trend: TrendDelta | null, cwd: string): Promise<boolean>;
|
|
4
|
+
export declare function postPRComment(report: VibeReport, trend: TrendDelta | null, cwd: string, prevReport?: VibeReport): Promise<boolean>;
|
package/dist/pr-comment.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/** Post scan results as a GitHub PR comment. Upserts to avoid duplicates. */
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { computeDelta } from "./delta.js";
|
|
4
5
|
const MARKER = "<!-- vcqa-report -->";
|
|
5
|
-
export async function postPRComment(report, trend, cwd) {
|
|
6
|
+
export async function postPRComment(report, trend, cwd, prevReport) {
|
|
6
7
|
const pr = detectPR(cwd);
|
|
7
8
|
if (!pr)
|
|
8
9
|
return false;
|
|
9
10
|
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
10
11
|
if (!token)
|
|
11
12
|
return false;
|
|
12
|
-
const body = buildCommentBody(report, trend);
|
|
13
|
+
const body = buildCommentBody(report, trend, prevReport);
|
|
13
14
|
// Try to find existing vcqa comment to update
|
|
14
15
|
const existingId = await findExistingComment(pr, token);
|
|
15
16
|
if (existingId) {
|
|
@@ -60,12 +61,30 @@ function detectPR(cwd) {
|
|
|
60
61
|
}
|
|
61
62
|
return null;
|
|
62
63
|
}
|
|
63
|
-
function buildCommentBody(report, trend) {
|
|
64
|
+
function buildCommentBody(report, trend, prevReport) {
|
|
64
65
|
const grade = report.grade;
|
|
65
66
|
const score = report.score;
|
|
66
67
|
const gradeEmoji = grade === "A" ? "🟢" : grade === "B" ? "🟡" : grade === "C" ? "🟠" : "🔴";
|
|
67
68
|
let body = `${MARKER}\n## ${gradeEmoji} VibeCode QA: **${grade}** ${score}/100\n\n`;
|
|
68
|
-
if (
|
|
69
|
+
if (prevReport) {
|
|
70
|
+
const delta = computeDelta(prevReport, report);
|
|
71
|
+
const arrow = delta.scoreDelta > 0 ? "📈" : delta.scoreDelta < 0 ? "📉" : "➡️";
|
|
72
|
+
body += `${arrow} **${delta.scoreDelta > 0 ? "+" : ""}${delta.scoreDelta}** vs previous`;
|
|
73
|
+
if (delta.fixed.length > 0)
|
|
74
|
+
body += ` · **${delta.fixed.length} fixed**`;
|
|
75
|
+
if (delta.introduced.length > 0)
|
|
76
|
+
body += ` · ${delta.introduced.length} new`;
|
|
77
|
+
body += "\n\n";
|
|
78
|
+
const changed = delta.checks.filter((c) => c.delta !== 0).sort((a, b) => b.delta - a.delta);
|
|
79
|
+
if (changed.length > 0) {
|
|
80
|
+
for (const c of changed.slice(0, 6)) {
|
|
81
|
+
const a = c.delta > 0 ? "+" : "";
|
|
82
|
+
body += `- ${c.delta > 0 ? "✅" : "⚠️"} ${c.name}: ${c.before} → ${c.after} (${a}${c.delta})\n`;
|
|
83
|
+
}
|
|
84
|
+
body += "\n";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (trend) {
|
|
69
88
|
const arrow = trend.scoreDelta > 0 ? "📈" : trend.scoreDelta < 0 ? "📉" : "➡️";
|
|
70
89
|
body += `${arrow} **${trend.scoreDelta > 0 ? "+" : ""}${trend.scoreDelta}** vs previous`;
|
|
71
90
|
if (trend.fixedIssues > 0)
|
package/dist/report/html.d.ts
CHANGED
|
@@ -17,5 +17,5 @@ export declare const GROUPS: {
|
|
|
17
17
|
file: string;
|
|
18
18
|
checks: string[];
|
|
19
19
|
}[];
|
|
20
|
-
export declare function generatePages(report: VibeReport, historyDir?: string): Map<string, string>;
|
|
20
|
+
export declare function generatePages(report: VibeReport, historyDir?: string, prevReport?: VibeReport): Map<string, string>;
|
|
21
21
|
export declare function generateHTML(report: VibeReport, historyDir?: string): string;
|
package/dist/report/html.js
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* Mobile: Hamburger toggles both top nav dropdown and sidebar panel.
|
|
12
12
|
*/
|
|
13
13
|
import { getCheckMeta } from "../check-meta.js";
|
|
14
|
+
import { computeDelta } from "../delta.js";
|
|
14
15
|
import { det, e, fileLink, gc } from "./components.js";
|
|
15
16
|
import { FAVICON_SVG } from "./favicon.js";
|
|
16
|
-
import { categoryPage, featureMapPage, filesPage, issuesPage, overviewPage, trendsPage } from "./pages.js";
|
|
17
|
+
import { actionsPage, categoryPage, featureMapPage, filesPage, issuesPage, overviewPage, trendsPage } from "./pages.js";
|
|
17
18
|
import { CSS } from "./styles.js";
|
|
18
19
|
export const GROUPS = [
|
|
19
20
|
{ id: "foundations", label: "Foundations", file: "foundations.html", checks: ["structure", "lint", "types", "type-safety", "standards"] },
|
|
@@ -29,7 +30,7 @@ export const GROUPS = [
|
|
|
29
30
|
{ id: "llm", label: "AI Readiness", file: "ai-readiness.html", checks: ["confusion", "context"] },
|
|
30
31
|
{ id: "ai", label: "AI Analysis", file: "ai-analysis.html", checks: ["doc-coherence", "code-coherence", "comment-staleness", "dead-patterns", "test-audit"] },
|
|
31
32
|
];
|
|
32
|
-
export function generatePages(report, historyDir) {
|
|
33
|
+
export function generatePages(report, historyDir, prevReport) {
|
|
33
34
|
const pages = new Map();
|
|
34
35
|
const allChecks = report.checks;
|
|
35
36
|
const checkMap = new Map(allChecks.map((c) => [c.name, c]));
|
|
@@ -107,6 +108,9 @@ export function generatePages(report, historyDir) {
|
|
|
107
108
|
// Feature Map (Pro page — reads dead-patterns check details)
|
|
108
109
|
const deadPatternsCheck = checkMap.get("dead-patterns");
|
|
109
110
|
pages.set("feature-map.html", w("feature-map", featureMapPage(deadPatternsCheck, fl)));
|
|
111
|
+
// Compute delta from previous scan
|
|
112
|
+
const scanDelta = prevReport ? computeDelta(prevReport, report) : undefined;
|
|
113
|
+
pages.set("actions.html", w("actions", actionsPage(allChecks, fl, report.meta.stack.linter, scanDelta)));
|
|
110
114
|
pages.set("issues.html", w("issues", issuesPage(allChecks, totalIssues, fl)));
|
|
111
115
|
pages.set("files.html", w("files", filesPage(topFiles, fileIssues, fl)));
|
|
112
116
|
pages.set("trends.html", w("trends", trendsPage(historyDir)));
|
|
@@ -131,6 +135,7 @@ function wrap(proj, currentId, report, totalIssues, sidebar, content) {
|
|
|
131
135
|
{ id: "checks", label: "Checks", file: GROUPS[0].file, active: isCheckPage },
|
|
132
136
|
{ id: "feature-map", label: "Feature Map", file: "feature-map.html" },
|
|
133
137
|
{ id: "trends", label: "Trends", file: "trends.html" },
|
|
138
|
+
{ id: "actions", label: "Actions", file: "actions.html" },
|
|
134
139
|
{ id: "issues", label: `Issues (${totalIssues})`, file: "issues.html" },
|
|
135
140
|
{ id: "files", label: "Files", file: "files.html" },
|
|
136
141
|
];
|
package/dist/report/pages.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Page renderers for the HTML report. */
|
|
2
|
+
import type { ScanDelta } from "../delta.js";
|
|
2
3
|
import type { CheckResult, VibeReport } from "../types.js";
|
|
3
4
|
export interface CatScore {
|
|
4
5
|
id: string;
|
|
@@ -25,4 +26,5 @@ export declare function filesPage(topFiles: FileEntry[], fileIssues: Map<string,
|
|
|
25
26
|
}>, fl: FL): string;
|
|
26
27
|
export declare function trendsPage(historyDir: string | undefined): string;
|
|
27
28
|
export declare function featureMapPage(deadPatternsCheck: CheckResult | undefined, fl: FL): string;
|
|
29
|
+
export declare function actionsPage(allChecks: CheckResult[], fl: FL, linter: string, delta?: ScanDelta): string;
|
|
28
30
|
export {};
|
package/dist/report/pages.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { getCheckMeta } from "../check-meta.js";
|
|
5
|
+
import { suggestFix } from "../commands/shared.js";
|
|
5
6
|
import { buildCoverageMapInput, generateCoverageMap } from "../diagrams/coverage.js";
|
|
6
7
|
import { loadHistory } from "../history.js";
|
|
7
8
|
import { generateArchSVG, generateDSM, generateLayerDiagram, generatePackageDiagram, generateSequenceDiagram, } from "../runners/architecture.js";
|
|
@@ -479,3 +480,169 @@ export function featureMapPage(deadPatternsCheck, fl) {
|
|
|
479
480
|
|
|
480
481
|
<div class="fm-grid">${cards}</div>`;
|
|
481
482
|
}
|
|
483
|
+
export function actionsPage(allChecks, fl, linter, delta) {
|
|
484
|
+
// Classify issues into auto-fixable, AI-fixable, manual
|
|
485
|
+
const autoFixes = [];
|
|
486
|
+
const aiFixes = [];
|
|
487
|
+
const manualGroups = [];
|
|
488
|
+
// Auto-fixable: lint issues (biome/eslint can auto-fix), structure issues
|
|
489
|
+
const AUTO_RULES = new Set(["missing-file", "missing-lockfile", "ts-strict", "env-not-ignored", "no-ci"]);
|
|
490
|
+
// Group AI-fixable by their fix suggestion
|
|
491
|
+
const fixGroups = new Map();
|
|
492
|
+
for (const c of allChecks) {
|
|
493
|
+
if (det(c).skipped || det(c).comingSoon)
|
|
494
|
+
continue;
|
|
495
|
+
const meta = getCheckMeta(c.name);
|
|
496
|
+
const manualIssues = [];
|
|
497
|
+
for (const iss of c.issues) {
|
|
498
|
+
const file = typeof iss.file === "string" ? iss.file : undefined;
|
|
499
|
+
// Auto-fixable: lint issues or known auto-fix rules
|
|
500
|
+
if (c.name === "lint" || AUTO_RULES.has(iss.rule || "")) {
|
|
501
|
+
autoFixes.push({ check: c.name, file, line: iss.line, message: iss.message, rule: iss.rule });
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
// AI-fixable: has a suggestFix mapping
|
|
505
|
+
const fix = suggestFix(c.name, iss.rule || "", iss.message);
|
|
506
|
+
if (fix) {
|
|
507
|
+
const key = `${c.name}::${fix}`;
|
|
508
|
+
const group = fixGroups.get(key) || { check: c.name, meta, fix, items: [] };
|
|
509
|
+
group.items.push({ file, line: iss.line, message: iss.message, rule: iss.rule });
|
|
510
|
+
fixGroups.set(key, group);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
// Manual
|
|
514
|
+
manualIssues.push({ file, line: iss.line, message: iss.message });
|
|
515
|
+
}
|
|
516
|
+
if (manualIssues.length > 0 && c.score < 90) {
|
|
517
|
+
manualGroups.push({ meta, score: c.score, issues: manualIssues });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
for (const g of fixGroups.values())
|
|
521
|
+
aiFixes.push(g);
|
|
522
|
+
aiFixes.sort((a, b) => b.items.length - a.items.length);
|
|
523
|
+
manualGroups.sort((a, b) => a.score - b.score);
|
|
524
|
+
const totalAuto = autoFixes.length;
|
|
525
|
+
const totalAI = aiFixes.reduce((s, g) => s + g.items.length, 0);
|
|
526
|
+
const totalManual = manualGroups.reduce((s, g) => s + g.issues.length, 0);
|
|
527
|
+
// Build auto-fix section
|
|
528
|
+
let autoSection = "";
|
|
529
|
+
if (totalAuto > 0) {
|
|
530
|
+
const linterName = linter === "biome" ? "Biome" : linter === "eslint" ? "ESLint" : "linter";
|
|
531
|
+
autoSection = `
|
|
532
|
+
<div class="act-section">
|
|
533
|
+
<h3><span class="act-icon" style="background:#22c55e20;color:var(--pass)">⚡</span> Quick Fixes <span class="act-count">${totalAuto}</span></h3>
|
|
534
|
+
<p class="act-desc">Auto-fixable with one command. Run <code>npx @vibecodeqa/cli fix</code></p>
|
|
535
|
+
<div class="act-cmd"><code>npx @vibecodeqa/cli fix</code></div>
|
|
536
|
+
<details class="act-details"><summary>${totalAuto} issues (${linterName} formatting, missing files, config)</summary>
|
|
537
|
+
<table class="act-table"><tbody>${autoFixes
|
|
538
|
+
.slice(0, 50)
|
|
539
|
+
.map((i) => `<tr><td class="act-check">${e(i.check)}</td><td>${i.file ? fl(i.file.split(":")[0], i.line) : ""}</td><td>${e(i.message)}</td></tr>`)
|
|
540
|
+
.join("")}</tbody></table>
|
|
541
|
+
${totalAuto > 50 ? `<p class="muted">+${totalAuto - 50} more</p>` : ""}
|
|
542
|
+
</details>
|
|
543
|
+
</div>`;
|
|
544
|
+
}
|
|
545
|
+
// Build AI-fix section
|
|
546
|
+
let aiSection = "";
|
|
547
|
+
if (totalAI > 0) {
|
|
548
|
+
const groupRows = aiFixes
|
|
549
|
+
.slice(0, 20)
|
|
550
|
+
.map((g) => {
|
|
551
|
+
const topItems = g.items
|
|
552
|
+
.slice(0, 3)
|
|
553
|
+
.map((i) => `<div class="act-item">${i.file ? fl(i.file.split(":")[0], i.line) : ""} <span class="muted">${e(i.message)}</span></div>`)
|
|
554
|
+
.join("");
|
|
555
|
+
const more = g.items.length > 3 ? `<div class="act-item muted">+${g.items.length - 3} more</div>` : "";
|
|
556
|
+
return `<div class="act-card"><div class="act-card-head"><span class="act-check">${e(g.meta.label)}</span><span class="act-count">${g.items.length}</span></div><div class="act-fix">${e(g.fix)}</div>${topItems}${more}</div>`;
|
|
557
|
+
})
|
|
558
|
+
.join("");
|
|
559
|
+
aiSection = `
|
|
560
|
+
<div class="act-section">
|
|
561
|
+
<h3><span class="act-icon" style="background:#6366f120;color:var(--info)">✨</span> AI Fixes <span class="act-count">${totalAI}</span></h3>
|
|
562
|
+
<p class="act-desc">Fixable with AI assistance. Run <code>npx @vibecodeqa/cli fix --ai</code></p>
|
|
563
|
+
<div class="act-cmd"><code>npx @vibecodeqa/cli fix --ai</code></div>
|
|
564
|
+
<div class="act-grid">${groupRows}</div>
|
|
565
|
+
</div>`;
|
|
566
|
+
}
|
|
567
|
+
// Build manual section
|
|
568
|
+
let manualSection = "";
|
|
569
|
+
if (manualGroups.length > 0) {
|
|
570
|
+
const groupRows = manualGroups
|
|
571
|
+
.slice(0, 15)
|
|
572
|
+
.map((g) => {
|
|
573
|
+
const clr = gc(g.score >= 90 ? "A" : g.score >= 75 ? "B" : g.score >= 60 ? "C" : g.score >= 40 ? "D" : "F");
|
|
574
|
+
const topIssues = g.issues
|
|
575
|
+
.slice(0, 3)
|
|
576
|
+
.map((i) => `<div class="act-item">${i.file ? fl(i.file.split(":")[0], i.line) : ""} <span class="muted">${e(i.message)}</span></div>`)
|
|
577
|
+
.join("");
|
|
578
|
+
const more = g.issues.length > 3 ? `<div class="act-item muted">+${g.issues.length - 3} more</div>` : "";
|
|
579
|
+
return `<div class="act-card"><div class="act-card-head"><span class="act-check">${e(g.meta.label)}</span><span style="color:${clr}">${g.score}/100</span></div><div class="act-rec">${e(g.meta.recommendation)}</div>${topIssues}${more}</div>`;
|
|
580
|
+
})
|
|
581
|
+
.join("");
|
|
582
|
+
manualSection = `
|
|
583
|
+
<div class="act-section">
|
|
584
|
+
<h3><span class="act-icon" style="background:#eab30820;color:var(--warn)">🛠</span> Manual Actions <span class="act-count">${totalManual}</span></h3>
|
|
585
|
+
<p class="act-desc">Require code changes — grouped by check with specific recommendations.</p>
|
|
586
|
+
<div class="act-grid">${groupRows}</div>
|
|
587
|
+
</div>`;
|
|
588
|
+
}
|
|
589
|
+
const noActions = totalAuto === 0 && totalAI === 0 && totalManual === 0;
|
|
590
|
+
// Delta section — shows what changed since last scan
|
|
591
|
+
let deltaSection = "";
|
|
592
|
+
if (delta) {
|
|
593
|
+
const dColor = delta.scoreDelta > 0 ? "var(--pass)" : delta.scoreDelta < 0 ? "var(--fail)" : "var(--muted)";
|
|
594
|
+
const dArrow = delta.scoreDelta > 0 ? "▲" : delta.scoreDelta < 0 ? "▼" : "▬";
|
|
595
|
+
// Per-check changes
|
|
596
|
+
const changed = delta.checks.filter((c) => c.delta !== 0).sort((a, b) => b.delta - a.delta);
|
|
597
|
+
const checkDeltas = changed
|
|
598
|
+
.slice(0, 10)
|
|
599
|
+
.map((c) => {
|
|
600
|
+
const cc = c.delta > 0 ? "var(--pass)" : "var(--fail)";
|
|
601
|
+
return `<span class="delta-chip" style="color:${cc}">${c.name} ${c.delta > 0 ? "+" : ""}${c.delta}</span>`;
|
|
602
|
+
})
|
|
603
|
+
.join("");
|
|
604
|
+
// Fixed issues list
|
|
605
|
+
let fixedList = "";
|
|
606
|
+
if (delta.fixed.length > 0) {
|
|
607
|
+
const byCheck = new Map();
|
|
608
|
+
for (const f of delta.fixed)
|
|
609
|
+
byCheck.set(f.check, (byCheck.get(f.check) || 0) + 1);
|
|
610
|
+
fixedList = `<div class="delta-fixed"><strong style="color:var(--pass)">Fixed (${delta.fixed.length}):</strong> ${[...byCheck.entries()].sort((a, b) => b[1] - a[1]).map(([c, n]) => `${c} (${n})`).join(", ")}</div>`;
|
|
611
|
+
}
|
|
612
|
+
let newList = "";
|
|
613
|
+
if (delta.introduced.length > 0) {
|
|
614
|
+
const byCheck = new Map();
|
|
615
|
+
for (const f of delta.introduced)
|
|
616
|
+
byCheck.set(f.check, (byCheck.get(f.check) || 0) + 1);
|
|
617
|
+
newList = `<div class="delta-new"><strong style="color:var(--fail)">New (${delta.introduced.length}):</strong> ${[...byCheck.entries()].sort((a, b) => b[1] - a[1]).map(([c, n]) => `${c} (${n})`).join(", ")}</div>`;
|
|
618
|
+
}
|
|
619
|
+
deltaSection = `
|
|
620
|
+
<div class="delta-banner">
|
|
621
|
+
<div class="delta-head">
|
|
622
|
+
<span class="delta-title">What Changed</span>
|
|
623
|
+
<span class="delta-score">${delta.before.grade} ${delta.before.score} → ${delta.after.grade} ${delta.after.score}</span>
|
|
624
|
+
<span class="delta-arrow" style="color:${dColor}">${dArrow} ${delta.scoreDelta > 0 ? "+" : ""}${delta.scoreDelta}</span>
|
|
625
|
+
</div>
|
|
626
|
+
<div class="delta-stats">
|
|
627
|
+
<span style="color:var(--pass)">${delta.fixed.length} fixed</span>
|
|
628
|
+
<span style="color:var(--fail)">${delta.introduced.length} new</span>
|
|
629
|
+
<span style="color:var(--muted)">${delta.after.issueCount} remaining</span>
|
|
630
|
+
</div>
|
|
631
|
+
${checkDeltas ? `<div class="delta-checks">${checkDeltas}</div>` : ""}
|
|
632
|
+
${fixedList}${newList}
|
|
633
|
+
</div>`;
|
|
634
|
+
}
|
|
635
|
+
return `
|
|
636
|
+
<h2>Recommended Actions</h2>
|
|
637
|
+
<p class="muted" style="margin-bottom:1.5rem">${noActions ? "No actions needed — all checks passed." : `${totalAuto + totalAI + totalManual} issues across ${totalAuto > 0 ? "3" : totalAI > 0 ? "2" : "1"} fix categories.`}</p>
|
|
638
|
+
|
|
639
|
+
${deltaSection}
|
|
640
|
+
|
|
641
|
+
<div class="act-summary">
|
|
642
|
+
<div class="act-stat"><span class="act-stat-n" style="color:var(--pass)">${totalAuto}</span><span class="act-stat-l">Auto-fixable</span></div>
|
|
643
|
+
<div class="act-stat"><span class="act-stat-n" style="color:var(--info)">${totalAI}</span><span class="act-stat-l">AI-fixable</span></div>
|
|
644
|
+
<div class="act-stat"><span class="act-stat-n" style="color:var(--warn)">${totalManual}</span><span class="act-stat-l">Manual</span></div>
|
|
645
|
+
</div>
|
|
646
|
+
|
|
647
|
+
${autoSection}${aiSection}${manualSection}`;
|
|
648
|
+
}
|
package/dist/report/styles.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** All CSS for the HTML report, extracted for maintainability. */
|
|
2
|
-
export declare const CSS = "\n:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8;--side-w:200px;--top-h:42px;--nav-bg:#0c0c0fdd;--side-bg:#0c0c0f;--hover:#14141a;--dim:#555;--card-alt:#0d0d12}\n[data-theme=\"light\"]{--bg:#f5f5f7;--card:#ffffff;--border:#e2e4e9;--text:#1a1a2e;--muted:#64748b;--pass:#16a34a;--fail:#dc2626;--warn:#ca8a04;--info:#4f46e5;--accent:#4f46e5;--nav-bg:#ffffffee;--side-bg:#fafafa;--hover:#eef0f5;--dim:#94a3b8;--card-alt:#f0f0f5}\nhtml{font-size:17px}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:\"Inter\",system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.5}\ncode{font-family:\"SF Mono\",Menlo,monospace;font-size:0.85em}\n\n/* \u2500\u2500 Top nav \u2500\u2500 */\n.top{position:sticky;top:0;z-index:30;background:var(--nav-bg);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 1.5rem;display:flex;align-items:center;height:var(--top-h)}\n.logo{font-weight:800;font-size:1rem;margin-right:0.5rem;flex-shrink:0;text-decoration:none;color:var(--text)}\n.logo span{color:var(--accent)}\n.nav-project{font-size:0.72rem;color:var(--muted);font-weight:600;margin-right:1rem;padding:0.2rem 0.5rem;background:var(--card);border:1px solid var(--border);border-radius:4px;flex-shrink:0}\n.nav-scroll{display:flex;align-items:center;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex:1}\n.nav-scroll::-webkit-scrollbar{display:none}\n.tn{padding:0 0.7rem;font-size:0.78rem;color:var(--muted);text-decoration:none;border-bottom:2px solid transparent;transition:all 0.15s;white-space:nowrap;line-height:var(--top-h)}\n.tn:hover{color:var(--text)}\n.tn.active{color:var(--text);border-bottom-color:var(--accent)}\n.hamburger{display:none;background:none;border:none;color:var(--muted);font-size:1.3rem;cursor:pointer;padding:0 0.4rem;line-height:var(--top-h)}\n\n/* \u2500\u2500 Sidebar \u2500\u2500 */\n.side{position:fixed;top:var(--top-h);left:0;bottom:0;width:var(--side-w);background:var(--side-bg);border-right:1px solid var(--border);overflow-y:auto;padding:0.6rem 0;font-size:0.7rem;z-index:20}\n.side-section{padding:0.3rem 0;border-bottom:1px solid var(--border)}\n.side-section:last-child{border-bottom:none}\n.side-label{padding:0.2rem 0.8rem;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600}\n.side-score{font-size:1.4rem;font-weight:900;padding:0.2rem 0.8rem}\n.side-cat{display:block;padding:0.3rem 0.8rem;color:var(--muted);font-weight:600;cursor:pointer;text-decoration:none;font-size:0.72rem}\n.side-cat:hover{background:var(--hover);color:var(--text)}\n.side-cat-active{color:var(--text);font-weight:700;border-left:2px solid var(--accent);padding-left:calc(0.8rem - 2px)}\n.side-cat-title{padding:0.3rem 0.8rem;font-size:0.65rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--accent);font-weight:700}\n.side-check{display:block;padding:0.15rem 0.8rem 0.15rem 0.8rem;color:var(--muted);cursor:pointer;text-decoration:none;font-size:0.65rem}\n.side-check:hover{color:var(--text);background:#14141a}\n.side-check span{display:inline-block;min-width:2.5rem;font-weight:700;font-size:0.6rem}\n.side-stat{padding:0.15rem 0.8rem;font-size:0.7rem;color:var(--muted)}\n.side-stat span{font-weight:800;font-size:0.8rem}\n.side-views{padding-top:0.3rem}\n.side-views .side-check{padding-left:0.8rem}\n\n/* \u2500\u2500 Content \u2500\u2500 */\n.content{margin-left:var(--side-w);padding:1.5rem 2rem;max-width:960px}\n\n/* \u2500\u2500 Overview \u2500\u2500 */\n.dash{display:flex;gap:2rem;margin-bottom:2rem;align-items:center;flex-wrap:wrap}\n.hero{display:flex;align-items:center;gap:1rem}\n.hero svg{width:100px;height:100px}\n.hc{display:flex;flex-direction:column}\n.hg{font-size:2.5rem;font-weight:900;line-height:1}\n.hs{font-size:1rem;font-weight:600}\n.hd{font-size:0.68rem;color:var(--muted)}\n.radar{flex:1;display:flex;justify-content:center}\n.radar svg{max-width:240px;width:100%}\n.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0.6rem;margin-bottom:2rem}\n.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;transition:border-color 0.15s;text-decoration:none;color:var(--text);display:block}\n.cc:hover{border-color:var(--accent)}\n.cc-s{font-size:1.8rem;font-weight:900}\n.cc-l{font-size:0.75rem;color:var(--muted)}\n.cc-m{margin-top:0.3rem;display:flex;gap:0.25rem}\n.mc{font-size:0.65rem;font-weight:800}\nh3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:0.5rem}\n\n/* \u2500\u2500 Overview sections \u2500\u2500 */\n.ov-section{margin-bottom:1.5rem}\n.ov-issue{font-size:0.68rem;font-family:\"SF Mono\",monospace;padding:0.2rem 0;display:flex;gap:0.4rem;align-items:baseline;border-bottom:1px solid var(--border)}\n.ov-issue .is{flex-shrink:0}\n.ov-issue.error .is{color:var(--fail)}\n.ov-issue.warning .is{color:var(--warn)}\n.ov-check{color:var(--muted);width:70px;flex-shrink:0;font-size:0.62rem}\n.ov-loc{color:var(--accent);flex-shrink:0;font-size:0.62rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ov-msg{flex:1;word-break:break-word}\n.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);text-decoration:none}\n.ov-link:hover{text-decoration:underline}\n\n/* \u2500\u2500 Timeline \u2500\u2500 */\n.timeline{margin:0.5rem 0;overflow-x:auto}\n.timeline svg{max-width:100%}\n\n/* \u2500\u2500 Bar chart \u2500\u2500 */\n.bars{margin-bottom:1.5rem}\n.brow{display:flex;align-items:center;gap:0.4rem;margin-bottom:0.25rem;font-size:0.72rem}\n.bl{width:90px;text-align:right;color:var(--muted);flex-shrink:0}\n.bb{flex:1;height:14px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.bf{height:100%;border-radius:2px}\n.bv{width:36px;font-weight:700;font-size:0.68rem}\n.stack{display:flex;gap:0.35rem;flex-wrap:wrap;margin-top:1rem}\n.stack span{background:var(--card);border:1px solid var(--border);padding:0.1rem 0.45rem;border-radius:9999px;font-size:0.62rem;color:var(--muted)}\n\n/* \u2500\u2500 Workspace / Repo structure \u2500\u2500 */\n.ws-info{display:flex;gap:0.6rem;align-items:center;flex-wrap:wrap;margin-bottom:0.5rem;font-size:0.72rem;color:var(--muted)}\n.ws-badge{background:var(--accent);color:#fff;padding:0.15rem 0.5rem;border-radius:4px;font-size:0.65rem;font-weight:700}\n.ws-pkgs{display:flex;flex-direction:column;gap:0.15rem}\n.ws-pkg{display:flex;gap:0.6rem;align-items:center;font-size:0.68rem;padding:0.15rem 0.4rem;background:var(--card);border-radius:4px}\n.ws-path{font-family:monospace;color:var(--text);min-width:140px}\n.ws-name{color:var(--muted);flex:1}\n.ws-flags{color:var(--muted);font-size:0.6rem}\n.ws-more{font-size:0.62rem;color:var(--muted);padding:0.2rem 0.4rem}\n\n/* \u2500\u2500 Category pages \u2500\u2500 */\n.cat-head{margin-bottom:0.3rem}\n.bar2{height:4px;background:var(--card);border-radius:2px;margin-bottom:1.5rem;overflow:hidden}\n.bf2{height:100%;border-radius:2px}\n.check-section{margin-bottom:2.5rem;padding-top:0.5rem;border-top:1px solid var(--border)}\n.check-section:first-of-type{border-top:none}\n\n/* \u2500\u2500 Check detail \u2500\u2500 */\n.ch-head{display:flex;align-items:center;gap:0.7rem;margin-bottom:0.8rem}\n.ch-g{font-size:2rem;font-weight:900}\n.ch-s{display:block;font-size:0.7rem;color:var(--muted)}\n.pri{font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;padding:0.15rem 0.5rem;border-radius:9999px;border:1px solid currentColor;flex-shrink:0}\n.info-panel{background:var(--card-alt);border:1px solid var(--border);border-radius:0.5rem;padding:0.7rem 0.9rem;margin-bottom:1rem;font-size:0.72rem;line-height:1.6}\n.ip-row{margin-bottom:0.4rem;display:flex;gap:0.5rem}\n.ip-row:last-child{margin-bottom:0}\n.ip-label{color:var(--accent);font-weight:700;min-width:2.5rem;flex-shrink:0}\n.skip-r{color:var(--muted);font-style:italic;font-size:0.78rem}\n.kvs{display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:1rem}\n.kv{background:var(--card);border:1px solid var(--border);border-radius:0.4rem;padding:0.3rem 0.6rem;font-size:0.7rem}\n.k{color:var(--muted);margin-right:0.3rem}\n.v{font-weight:600}\n\n/* \u2500\u2500 Issue list grouped by file \u2500\u2500 */\n.iss-list{margin-top:1rem}\n.fg{margin-bottom:0.8rem}\n.fn{font-size:0.72rem;font-weight:600;font-family:\"SF Mono\",monospace;padding:0.3rem 0;border-bottom:1px solid var(--border);margin-bottom:0.2rem;display:flex;align-items:center;gap:0.5rem}\n.fc{background:var(--border);border-radius:9999px;padding:0 0.4rem;font-size:0.6rem;color:var(--muted)}\n.ir{font-size:0.65rem;font-family:\"SF Mono\",monospace;padding:0.12rem 0 0.12rem 0.5rem;display:flex;gap:0.4rem;align-items:baseline}\n.is{font-weight:800;font-size:0.55rem;width:0.9rem;text-align:center;border-radius:2px;flex-shrink:0}\n.ir.error .is{color:var(--fail);background:#ef444418}\n.ir.warning .is{color:var(--warn);background:#eab30818}\n.ir.info .is{color:var(--info);background:#6366f118}\n.il{color:var(--accent);min-width:2rem;flex-shrink:0}\n.im{flex:1;word-break:break-word}\n.iru{color:var(--dim);font-size:0.55rem}\n\n/* \u2500\u2500 Source code snippets \u2500\u2500 */\n.src-block{background:var(--card-alt);border:1px solid var(--border);border-radius:6px;margin:0.3rem 0 0.5rem 0.5rem;padding:0.3rem 0;font-family:\"SF Mono\",Menlo,monospace;font-size:0.62rem;line-height:1.6;overflow-x:auto}\n.src-ln{padding:0 0.5rem;white-space:pre}\n.src-hl{padding:0 0.5rem;white-space:pre;background:#eab30815;border-left:2px solid var(--warn)}\n.src-num{color:var(--dim);margin-right:0.5rem;user-select:none;display:inline-block;min-width:2.5rem;text-align:right}\n.src-prompt{padding:0.3rem 0.5rem;border-top:1px solid var(--border);display:flex;justify-content:flex-end}\n.src-fix-btn{background:var(--card);border:1px solid var(--border);color:var(--accent);font-size:0.62rem;padding:0.2rem 0.6rem;border-radius:4px;cursor:pointer;font-family:inherit}\n.src-fix-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)}\n\n/* \u2500\u2500 All issues table \u2500\u2500 */\n.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}\n.it{width:100%;border-collapse:collapse;font-size:0.68rem}\n.it th{text-align:left;padding:0.35rem 0.4rem;color:var(--muted);font-size:0.62rem;text-transform:uppercase;border-bottom:1px solid var(--border)}\n.it td{padding:0.25rem 0.4rem;border-bottom:1px solid var(--border);font-family:\"SF Mono\",monospace;font-size:0.62rem}\n.it tr.error .is2{color:var(--fail)}\n.it tr.warning .is2{color:var(--warn)}\n.is2{font-weight:800;width:1rem}\n.ic2{color:var(--muted);width:70px}\n.il2{color:var(--muted)}\n.iru2{color:var(--dim);font-size:0.58rem}\n\n/* \u2500\u2500 File health \u2500\u2500 */\n.fr{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem;font-size:0.7rem}\n.ff{width:200px;font-family:\"SF Mono\",monospace;font-size:0.65rem;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fb{flex:1;height:12px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.fbf{height:100%;border-radius:2px}\n.fv{width:50px;font-size:0.65rem;color:var(--muted);flex-shrink:0}\n.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}\n.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:\"SF Mono\",monospace;font-size:0.65rem}\n.hm-bar{height:14px;border-radius:3px;min-width:4px}\n.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0;min-width:50px}\n.hm-checks{font-size:0.58rem;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n/* \u2500\u2500 Premium cards \u2500\u2500 */\n.pro-card{background:linear-gradient(135deg,#0f0f1a 0%,#13131f 100%);border:1px solid #2a2a3d;border-radius:0.75rem;padding:1.5rem;position:relative;overflow:hidden}\n.pro-card::before{content:\"\";position:absolute;top:-50%;right:-50%;width:200%;height:200%;background:radial-gradient(circle,#6366f108 0%,transparent 70%);pointer-events:none}\n.pro-badge{display:inline-block;background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff;font-size:0.6rem;font-weight:800;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:0.06em;margin-bottom:0.6rem}\n.pro-desc{color:var(--muted);font-size:0.78rem;line-height:1.6;margin-bottom:0.8rem}\n.pro-cta{color:#6366f1;font-size:0.72rem;font-weight:600;margin-top:1rem}\n.sn-pro{opacity:0.7}\n\n/* \u2500\u2500 Trends page \u2500\u2500 */\n.trend-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem;margin-top:0.5rem}\n.trend-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem}\n.trend-header{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem}\n.trend-name{font-size:0.78rem;font-weight:700;flex:1}\n.trend-score{font-size:1.1rem;font-weight:900}\n.trend-chart{overflow:hidden}\n.trend-chart svg{width:100%;height:60px}\n.trend-table{margin-bottom:1.5rem}\n.trend-row{display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;border-bottom:1px solid var(--border);font-size:0.75rem}\n.trend-row-name{flex:1;font-weight:600}\n.trend-row-val{width:2rem;text-align:center;color:var(--muted)}\n.trend-row-arrow{color:var(--muted);font-size:0.6rem}\n.trend-row-delta{width:2.5rem;text-align:right;font-weight:700}\n\n.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}\n.footer a{color:var(--muted)}\n.muted{color:var(--muted)}\n.deeper-tools code{background:var(--border);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.62rem;color:var(--accent);margin-right:0.3rem}\n.flink{color:var(--accent);text-decoration:none;font-family:\"SF Mono\",monospace}.flink:hover{text-decoration:underline}\n.arch-svg{margin:1rem 0;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.arch-svg svg{border-radius:8px}\n.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}\n.ir:hover .cp-btn{opacity:0.6}\n\n/* \u2500\u2500 Mobile: hamburger collapses both navs \u2500\u2500 */\n@media(max-width:768px){\n.hamburger{display:block}\n.nav-scroll{display:none}\n.nav-scroll.open{display:flex;position:absolute;top:var(--top-h);left:0;right:0;background:var(--bg);border-bottom:1px solid var(--border);flex-wrap:wrap;padding:0.3rem 0.5rem;z-index:25}\n.side{display:none}\n.side.open{display:block;z-index:25}\n.top{padding:0 0.8rem}\n.logo{font-size:0.85rem;margin-right:0.5rem}\n.content{margin-left:0;padding:0.8rem}\n.cats{grid-template-columns:1fr 1fr}\n.dash{flex-direction:column;gap:1rem}\n.hero svg{width:80px;height:80px}\n.hg{font-size:2rem}\n.radar svg{max-width:180px}\n.bl{width:60px;font-size:0.62rem}\n.bv{width:30px;font-size:0.6rem}\n.it{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.ff{width:120px;font-size:0.58rem}\n.hm-name{width:120px;font-size:0.58rem}\n.hm-checks{display:none}\n.ov-check{width:50px}\n.ov-loc{max-width:120px}\n.ir{font-size:0.6rem}\n.ch-head{flex-wrap:wrap}\n.ch-g{font-size:1.5rem}\n.info-panel{font-size:0.68rem;padding:0.5rem 0.6rem}\n.ip-row{flex-direction:column;gap:0.1rem}\n.kvs{gap:0.4rem}\n.kv{font-size:0.62rem;padding:0.2rem 0.4rem}\n.arch-svg svg{min-width:400px}\n}\n@media(max-width:480px){\n.cats{grid-template-columns:1fr}\n.tn{padding:0 0.4rem;font-size:0.65rem}\n.ff{width:90px}\n.hm-name{width:90px}\n.ov-check{display:none}\n}\n\n/* \u2500\u2500 Feature Map (Pro) \u2500\u2500 */\n.fm-header{margin-bottom:1.5rem}\n.fm-header h2{display:flex;align-items:center;gap:0.6rem}\n.fm-stats{display:flex;gap:1.5rem;margin-bottom:2rem;padding:1rem 1.2rem;background:var(--card);border:1px solid var(--border);border-radius:12px}\n.fm-stat{display:flex;flex-direction:column;align-items:center}\n.fm-stat-n{font-size:1.6rem;font-weight:900;line-height:1.2}\n.fm-stat-l{font-size:0.65rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:600}\n.fm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}\n.fm-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.2rem;transition:border-color 0.15s}\n.fm-card:hover{border-color:#333}\n.fm-card-issue{border-color:#eab30830;background:linear-gradient(135deg,var(--card) 0%,#1a1a0f 100%)}\n.fm-card-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:0.4rem}\n.fm-card-label{font-weight:800;font-size:0.95rem}\n.fm-card-desc{font-size:0.72rem;color:var(--muted);margin-bottom:0.5rem;line-height:1.4}\n.fm-card-dir{font-size:0.65rem;color:var(--dim);font-family:\"SF Mono\",monospace;margin-bottom:0.6rem}\n.fm-card-badge{font-size:0.6rem;font-weight:700;padding:0.15rem 0.5rem;border-radius:9999px;white-space:nowrap}\n.fm-ok{background:#22c55e18;color:var(--pass)}\n.fm-warn{background:#eab30818;color:var(--warn)}\n.fm-info{background:#6366f118;color:var(--info)}\n.fm-card-files{display:flex;flex-direction:column;gap:0.15rem;margin-bottom:0.6rem}\n.fm-file{font-size:0.68rem;color:var(--muted);font-family:\"SF Mono\",monospace}\n.fm-file a{color:var(--accent);text-decoration:none}\n.fm-file a:hover{text-decoration:underline}\n.fm-more{color:var(--dim);font-style:italic}\n.fm-findings{margin-top:0.6rem;padding-top:0.6rem;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:0.3rem}\n.fm-finding{display:flex;align-items:baseline;gap:0.4rem;font-size:0.68rem;line-height:1.4}\n.fm-f-sev{font-weight:800;font-size:0.6rem;width:1rem;flex-shrink:0}\n.fm-f-warn .fm-f-sev{color:var(--warn)}\n.fm-f-info .fm-f-sev{color:var(--info)}\n.fm-f-loc{color:var(--dim);font-family:\"SF Mono\",monospace;flex-shrink:0}\n.fm-f-loc a{color:var(--accent);text-decoration:none}\n.fm-f-msg{color:var(--text)}\n.fm-f-rule{color:var(--dim);font-size:0.6rem;font-family:\"SF Mono\",monospace}\n\n/* Teaser (no Pro key) */\n.fm-teaser{margin-top:1.5rem}\n.fm-teaser-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1rem;margin-bottom:2rem}\n.fm-card-blur{filter:blur(3px);opacity:0.5;pointer-events:none;user-select:none}\n.fm-cta{text-align:center;padding:2rem;background:linear-gradient(135deg,#0f0f1a,#13131f);border:1px solid #2a2a3d;border-radius:12px}\n.fm-cta code{background:#1a1a2e;padding:0.2rem 0.5rem;border-radius:4px;font-size:0.8rem}\n@media(max-width:640px){\n.fm-grid,.fm-teaser-grid{grid-template-columns:1fr}\n.fm-stats{flex-wrap:wrap;gap:1rem}\n}\n\n/* \u2500\u2500 Preferences panel \u2500\u2500 */\n.prefs-btn{background:none;border:1px solid var(--border);color:var(--muted);font-size:0.72rem;cursor:pointer;padding:0.2rem 0.5rem;border-radius:6px;margin-left:auto;flex-shrink:0;font-family:inherit;line-height:1.4}\n.prefs-btn:hover{color:var(--text);border-color:var(--dim)}\n.prefs-panel{display:none;position:absolute;top:var(--top-h);right:1rem;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem 1rem;z-index:40;min-width:200px;box-shadow:0 8px 30px #0008}\n.prefs-panel.open{display:block}\n.prefs-label{font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600;margin-bottom:0.3rem}\n.prefs-label:not(:first-child){margin-top:0.7rem}\n.prefs-row{display:flex;gap:0.3rem}\n.prefs-opt{background:var(--card-alt);border:1px solid var(--border);color:var(--muted);font-size:0.68rem;padding:0.25rem 0.6rem;border-radius:6px;cursor:pointer;font-family:inherit;transition:all 0.1s}\n.prefs-opt:hover{color:var(--text);border-color:var(--dim)}\n.prefs-opt.active{background:var(--accent);color:#fff;border-color:var(--accent)}\n[data-theme=\"light\"] .prefs-panel{box-shadow:0 8px 30px #0002}\n";
|
|
2
|
+
export declare const CSS = "\n:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8;--side-w:200px;--top-h:42px;--nav-bg:#0c0c0fdd;--side-bg:#0c0c0f;--hover:#14141a;--dim:#555;--card-alt:#0d0d12}\n[data-theme=\"light\"]{--bg:#f5f5f7;--card:#ffffff;--border:#e2e4e9;--text:#1a1a2e;--muted:#64748b;--pass:#16a34a;--fail:#dc2626;--warn:#ca8a04;--info:#4f46e5;--accent:#4f46e5;--nav-bg:#ffffffee;--side-bg:#fafafa;--hover:#eef0f5;--dim:#94a3b8;--card-alt:#f0f0f5}\nhtml{font-size:17px}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:\"Inter\",system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.5}\ncode{font-family:\"SF Mono\",Menlo,monospace;font-size:0.85em}\n\n/* \u2500\u2500 Top nav \u2500\u2500 */\n.top{position:sticky;top:0;z-index:30;background:var(--nav-bg);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 1.5rem;display:flex;align-items:center;height:var(--top-h)}\n.logo{font-weight:800;font-size:1rem;margin-right:0.5rem;flex-shrink:0;text-decoration:none;color:var(--text)}\n.logo span{color:var(--accent)}\n.nav-project{font-size:0.72rem;color:var(--muted);font-weight:600;margin-right:1rem;padding:0.2rem 0.5rem;background:var(--card);border:1px solid var(--border);border-radius:4px;flex-shrink:0}\n.nav-scroll{display:flex;align-items:center;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex:1}\n.nav-scroll::-webkit-scrollbar{display:none}\n.tn{padding:0 0.7rem;font-size:0.78rem;color:var(--muted);text-decoration:none;border-bottom:2px solid transparent;transition:all 0.15s;white-space:nowrap;line-height:var(--top-h)}\n.tn:hover{color:var(--text)}\n.tn.active{color:var(--text);border-bottom-color:var(--accent)}\n.hamburger{display:none;background:none;border:none;color:var(--muted);font-size:1.3rem;cursor:pointer;padding:0 0.4rem;line-height:var(--top-h)}\n\n/* \u2500\u2500 Sidebar \u2500\u2500 */\n.side{position:fixed;top:var(--top-h);left:0;bottom:0;width:var(--side-w);background:var(--side-bg);border-right:1px solid var(--border);overflow-y:auto;padding:0.6rem 0;font-size:0.7rem;z-index:20}\n.side-section{padding:0.3rem 0;border-bottom:1px solid var(--border)}\n.side-section:last-child{border-bottom:none}\n.side-label{padding:0.2rem 0.8rem;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600}\n.side-score{font-size:1.4rem;font-weight:900;padding:0.2rem 0.8rem}\n.side-cat{display:block;padding:0.3rem 0.8rem;color:var(--muted);font-weight:600;cursor:pointer;text-decoration:none;font-size:0.72rem}\n.side-cat:hover{background:var(--hover);color:var(--text)}\n.side-cat-active{color:var(--text);font-weight:700;border-left:2px solid var(--accent);padding-left:calc(0.8rem - 2px)}\n.side-cat-title{padding:0.3rem 0.8rem;font-size:0.65rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--accent);font-weight:700}\n.side-check{display:block;padding:0.15rem 0.8rem 0.15rem 0.8rem;color:var(--muted);cursor:pointer;text-decoration:none;font-size:0.65rem}\n.side-check:hover{color:var(--text);background:#14141a}\n.side-check span{display:inline-block;min-width:2.5rem;font-weight:700;font-size:0.6rem}\n.side-stat{padding:0.15rem 0.8rem;font-size:0.7rem;color:var(--muted)}\n.side-stat span{font-weight:800;font-size:0.8rem}\n.side-views{padding-top:0.3rem}\n.side-views .side-check{padding-left:0.8rem}\n\n/* \u2500\u2500 Content \u2500\u2500 */\n.content{margin-left:var(--side-w);padding:1.5rem 2rem;max-width:960px}\n\n/* \u2500\u2500 Overview \u2500\u2500 */\n.dash{display:flex;gap:2rem;margin-bottom:2rem;align-items:center;flex-wrap:wrap}\n.hero{display:flex;align-items:center;gap:1rem}\n.hero svg{width:100px;height:100px}\n.hc{display:flex;flex-direction:column}\n.hg{font-size:2.5rem;font-weight:900;line-height:1}\n.hs{font-size:1rem;font-weight:600}\n.hd{font-size:0.68rem;color:var(--muted)}\n.radar{flex:1;display:flex;justify-content:center}\n.radar svg{max-width:240px;width:100%}\n.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0.6rem;margin-bottom:2rem}\n.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;transition:border-color 0.15s;text-decoration:none;color:var(--text);display:block}\n.cc:hover{border-color:var(--accent)}\n.cc-s{font-size:1.8rem;font-weight:900}\n.cc-l{font-size:0.75rem;color:var(--muted)}\n.cc-m{margin-top:0.3rem;display:flex;gap:0.25rem}\n.mc{font-size:0.65rem;font-weight:800}\nh3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:0.5rem}\n\n/* \u2500\u2500 Overview sections \u2500\u2500 */\n.ov-section{margin-bottom:1.5rem}\n.ov-issue{font-size:0.68rem;font-family:\"SF Mono\",monospace;padding:0.2rem 0;display:flex;gap:0.4rem;align-items:baseline;border-bottom:1px solid var(--border)}\n.ov-issue .is{flex-shrink:0}\n.ov-issue.error .is{color:var(--fail)}\n.ov-issue.warning .is{color:var(--warn)}\n.ov-check{color:var(--muted);width:70px;flex-shrink:0;font-size:0.62rem}\n.ov-loc{color:var(--accent);flex-shrink:0;font-size:0.62rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ov-msg{flex:1;word-break:break-word}\n.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);text-decoration:none}\n.ov-link:hover{text-decoration:underline}\n\n/* \u2500\u2500 Timeline \u2500\u2500 */\n.timeline{margin:0.5rem 0;overflow-x:auto}\n.timeline svg{max-width:100%}\n\n/* \u2500\u2500 Bar chart \u2500\u2500 */\n.bars{margin-bottom:1.5rem}\n.brow{display:flex;align-items:center;gap:0.4rem;margin-bottom:0.25rem;font-size:0.72rem}\n.bl{width:90px;text-align:right;color:var(--muted);flex-shrink:0}\n.bb{flex:1;height:14px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.bf{height:100%;border-radius:2px}\n.bv{width:36px;font-weight:700;font-size:0.68rem}\n.stack{display:flex;gap:0.35rem;flex-wrap:wrap;margin-top:1rem}\n.stack span{background:var(--card);border:1px solid var(--border);padding:0.1rem 0.45rem;border-radius:9999px;font-size:0.62rem;color:var(--muted)}\n\n/* \u2500\u2500 Workspace / Repo structure \u2500\u2500 */\n.ws-info{display:flex;gap:0.6rem;align-items:center;flex-wrap:wrap;margin-bottom:0.5rem;font-size:0.72rem;color:var(--muted)}\n.ws-badge{background:var(--accent);color:#fff;padding:0.15rem 0.5rem;border-radius:4px;font-size:0.65rem;font-weight:700}\n.ws-pkgs{display:flex;flex-direction:column;gap:0.15rem}\n.ws-pkg{display:flex;gap:0.6rem;align-items:center;font-size:0.68rem;padding:0.15rem 0.4rem;background:var(--card);border-radius:4px}\n.ws-path{font-family:monospace;color:var(--text);min-width:140px}\n.ws-name{color:var(--muted);flex:1}\n.ws-flags{color:var(--muted);font-size:0.6rem}\n.ws-more{font-size:0.62rem;color:var(--muted);padding:0.2rem 0.4rem}\n\n/* \u2500\u2500 Category pages \u2500\u2500 */\n.cat-head{margin-bottom:0.3rem}\n.bar2{height:4px;background:var(--card);border-radius:2px;margin-bottom:1.5rem;overflow:hidden}\n.bf2{height:100%;border-radius:2px}\n.check-section{margin-bottom:2.5rem;padding-top:0.5rem;border-top:1px solid var(--border)}\n.check-section:first-of-type{border-top:none}\n\n/* \u2500\u2500 Check detail \u2500\u2500 */\n.ch-head{display:flex;align-items:center;gap:0.7rem;margin-bottom:0.8rem}\n.ch-g{font-size:2rem;font-weight:900}\n.ch-s{display:block;font-size:0.7rem;color:var(--muted)}\n.pri{font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;padding:0.15rem 0.5rem;border-radius:9999px;border:1px solid currentColor;flex-shrink:0}\n.info-panel{background:var(--card-alt);border:1px solid var(--border);border-radius:0.5rem;padding:0.7rem 0.9rem;margin-bottom:1rem;font-size:0.72rem;line-height:1.6}\n.ip-row{margin-bottom:0.4rem;display:flex;gap:0.5rem}\n.ip-row:last-child{margin-bottom:0}\n.ip-label{color:var(--accent);font-weight:700;min-width:2.5rem;flex-shrink:0}\n.skip-r{color:var(--muted);font-style:italic;font-size:0.78rem}\n.kvs{display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:1rem}\n.kv{background:var(--card);border:1px solid var(--border);border-radius:0.4rem;padding:0.3rem 0.6rem;font-size:0.7rem}\n.k{color:var(--muted);margin-right:0.3rem}\n.v{font-weight:600}\n\n/* \u2500\u2500 Issue list grouped by file \u2500\u2500 */\n.iss-list{margin-top:1rem}\n.fg{margin-bottom:0.8rem}\n.fn{font-size:0.72rem;font-weight:600;font-family:\"SF Mono\",monospace;padding:0.3rem 0;border-bottom:1px solid var(--border);margin-bottom:0.2rem;display:flex;align-items:center;gap:0.5rem}\n.fc{background:var(--border);border-radius:9999px;padding:0 0.4rem;font-size:0.6rem;color:var(--muted)}\n.ir{font-size:0.65rem;font-family:\"SF Mono\",monospace;padding:0.12rem 0 0.12rem 0.5rem;display:flex;gap:0.4rem;align-items:baseline}\n.is{font-weight:800;font-size:0.55rem;width:0.9rem;text-align:center;border-radius:2px;flex-shrink:0}\n.ir.error .is{color:var(--fail);background:#ef444418}\n.ir.warning .is{color:var(--warn);background:#eab30818}\n.ir.info .is{color:var(--info);background:#6366f118}\n.il{color:var(--accent);min-width:2rem;flex-shrink:0}\n.im{flex:1;word-break:break-word}\n.iru{color:var(--dim);font-size:0.55rem}\n\n/* \u2500\u2500 Source code snippets \u2500\u2500 */\n.src-block{background:var(--card-alt);border:1px solid var(--border);border-radius:6px;margin:0.3rem 0 0.5rem 0.5rem;padding:0.3rem 0;font-family:\"SF Mono\",Menlo,monospace;font-size:0.62rem;line-height:1.6;overflow-x:auto}\n.src-ln{padding:0 0.5rem;white-space:pre}\n.src-hl{padding:0 0.5rem;white-space:pre;background:#eab30815;border-left:2px solid var(--warn)}\n.src-num{color:var(--dim);margin-right:0.5rem;user-select:none;display:inline-block;min-width:2.5rem;text-align:right}\n.src-prompt{padding:0.3rem 0.5rem;border-top:1px solid var(--border);display:flex;justify-content:flex-end}\n.src-fix-btn{background:var(--card);border:1px solid var(--border);color:var(--accent);font-size:0.62rem;padding:0.2rem 0.6rem;border-radius:4px;cursor:pointer;font-family:inherit}\n.src-fix-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)}\n\n/* \u2500\u2500 All issues table \u2500\u2500 */\n.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}\n.it{width:100%;border-collapse:collapse;font-size:0.68rem}\n.it th{text-align:left;padding:0.35rem 0.4rem;color:var(--muted);font-size:0.62rem;text-transform:uppercase;border-bottom:1px solid var(--border)}\n.it td{padding:0.25rem 0.4rem;border-bottom:1px solid var(--border);font-family:\"SF Mono\",monospace;font-size:0.62rem}\n.it tr.error .is2{color:var(--fail)}\n.it tr.warning .is2{color:var(--warn)}\n.is2{font-weight:800;width:1rem}\n.ic2{color:var(--muted);width:70px}\n.il2{color:var(--muted)}\n.iru2{color:var(--dim);font-size:0.58rem}\n\n/* \u2500\u2500 File health \u2500\u2500 */\n.fr{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem;font-size:0.7rem}\n.ff{width:200px;font-family:\"SF Mono\",monospace;font-size:0.65rem;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fb{flex:1;height:12px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.fbf{height:100%;border-radius:2px}\n.fv{width:50px;font-size:0.65rem;color:var(--muted);flex-shrink:0}\n.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}\n.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:\"SF Mono\",monospace;font-size:0.65rem}\n.hm-bar{height:14px;border-radius:3px;min-width:4px}\n.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0;min-width:50px}\n.hm-checks{font-size:0.58rem;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n/* \u2500\u2500 Premium cards \u2500\u2500 */\n.pro-card{background:linear-gradient(135deg,#0f0f1a 0%,#13131f 100%);border:1px solid #2a2a3d;border-radius:0.75rem;padding:1.5rem;position:relative;overflow:hidden}\n.pro-card::before{content:\"\";position:absolute;top:-50%;right:-50%;width:200%;height:200%;background:radial-gradient(circle,#6366f108 0%,transparent 70%);pointer-events:none}\n.pro-badge{display:inline-block;background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff;font-size:0.6rem;font-weight:800;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:0.06em;margin-bottom:0.6rem}\n.pro-desc{color:var(--muted);font-size:0.78rem;line-height:1.6;margin-bottom:0.8rem}\n.pro-cta{color:#6366f1;font-size:0.72rem;font-weight:600;margin-top:1rem}\n.sn-pro{opacity:0.7}\n\n/* \u2500\u2500 Trends page \u2500\u2500 */\n.trend-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem;margin-top:0.5rem}\n.trend-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem}\n.trend-header{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem}\n.trend-name{font-size:0.78rem;font-weight:700;flex:1}\n.trend-score{font-size:1.1rem;font-weight:900}\n.trend-chart{overflow:hidden}\n.trend-chart svg{width:100%;height:60px}\n.trend-table{margin-bottom:1.5rem}\n.trend-row{display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;border-bottom:1px solid var(--border);font-size:0.75rem}\n.trend-row-name{flex:1;font-weight:600}\n.trend-row-val{width:2rem;text-align:center;color:var(--muted)}\n.trend-row-arrow{color:var(--muted);font-size:0.6rem}\n.trend-row-delta{width:2.5rem;text-align:right;font-weight:700}\n\n.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}\n.footer a{color:var(--muted)}\n.muted{color:var(--muted)}\n.deeper-tools code{background:var(--border);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.62rem;color:var(--accent);margin-right:0.3rem}\n.flink{color:var(--accent);text-decoration:none;font-family:\"SF Mono\",monospace}.flink:hover{text-decoration:underline}\n.arch-svg{margin:1rem 0;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.arch-svg svg{border-radius:8px}\n.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}\n.ir:hover .cp-btn{opacity:0.6}\n\n/* \u2500\u2500 Mobile: hamburger collapses both navs \u2500\u2500 */\n@media(max-width:768px){\n.hamburger{display:block}\n.nav-scroll{display:none}\n.nav-scroll.open{display:flex;position:absolute;top:var(--top-h);left:0;right:0;background:var(--bg);border-bottom:1px solid var(--border);flex-wrap:wrap;padding:0.3rem 0.5rem;z-index:25}\n.side{display:none}\n.side.open{display:block;z-index:25}\n.top{padding:0 0.8rem}\n.logo{font-size:0.85rem;margin-right:0.5rem}\n.content{margin-left:0;padding:0.8rem}\n.cats{grid-template-columns:1fr 1fr}\n.dash{flex-direction:column;gap:1rem}\n.hero svg{width:80px;height:80px}\n.hg{font-size:2rem}\n.radar svg{max-width:180px}\n.bl{width:60px;font-size:0.62rem}\n.bv{width:30px;font-size:0.6rem}\n.it{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.ff{width:120px;font-size:0.58rem}\n.hm-name{width:120px;font-size:0.58rem}\n.hm-checks{display:none}\n.ov-check{width:50px}\n.ov-loc{max-width:120px}\n.ir{font-size:0.6rem}\n.ch-head{flex-wrap:wrap}\n.ch-g{font-size:1.5rem}\n.info-panel{font-size:0.68rem;padding:0.5rem 0.6rem}\n.ip-row{flex-direction:column;gap:0.1rem}\n.kvs{gap:0.4rem}\n.kv{font-size:0.62rem;padding:0.2rem 0.4rem}\n.arch-svg svg{min-width:400px}\n}\n@media(max-width:480px){\n.cats{grid-template-columns:1fr}\n.tn{padding:0 0.4rem;font-size:0.65rem}\n.ff{width:90px}\n.hm-name{width:90px}\n.ov-check{display:none}\n}\n\n/* \u2500\u2500 Feature Map (Pro) \u2500\u2500 */\n.fm-header{margin-bottom:1.5rem}\n.fm-header h2{display:flex;align-items:center;gap:0.6rem}\n.fm-stats{display:flex;gap:1.5rem;margin-bottom:2rem;padding:1rem 1.2rem;background:var(--card);border:1px solid var(--border);border-radius:12px}\n.fm-stat{display:flex;flex-direction:column;align-items:center}\n.fm-stat-n{font-size:1.6rem;font-weight:900;line-height:1.2}\n.fm-stat-l{font-size:0.65rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:600}\n.fm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}\n.fm-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.2rem;transition:border-color 0.15s}\n.fm-card:hover{border-color:#333}\n.fm-card-issue{border-color:#eab30830;background:linear-gradient(135deg,var(--card) 0%,#1a1a0f 100%)}\n.fm-card-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:0.4rem}\n.fm-card-label{font-weight:800;font-size:0.95rem}\n.fm-card-desc{font-size:0.72rem;color:var(--muted);margin-bottom:0.5rem;line-height:1.4}\n.fm-card-dir{font-size:0.65rem;color:var(--dim);font-family:\"SF Mono\",monospace;margin-bottom:0.6rem}\n.fm-card-badge{font-size:0.6rem;font-weight:700;padding:0.15rem 0.5rem;border-radius:9999px;white-space:nowrap}\n.fm-ok{background:#22c55e18;color:var(--pass)}\n.fm-warn{background:#eab30818;color:var(--warn)}\n.fm-info{background:#6366f118;color:var(--info)}\n.fm-card-files{display:flex;flex-direction:column;gap:0.15rem;margin-bottom:0.6rem}\n.fm-file{font-size:0.68rem;color:var(--muted);font-family:\"SF Mono\",monospace}\n.fm-file a{color:var(--accent);text-decoration:none}\n.fm-file a:hover{text-decoration:underline}\n.fm-more{color:var(--dim);font-style:italic}\n.fm-findings{margin-top:0.6rem;padding-top:0.6rem;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:0.3rem}\n.fm-finding{display:flex;align-items:baseline;gap:0.4rem;font-size:0.68rem;line-height:1.4}\n.fm-f-sev{font-weight:800;font-size:0.6rem;width:1rem;flex-shrink:0}\n.fm-f-warn .fm-f-sev{color:var(--warn)}\n.fm-f-info .fm-f-sev{color:var(--info)}\n.fm-f-loc{color:var(--dim);font-family:\"SF Mono\",monospace;flex-shrink:0}\n.fm-f-loc a{color:var(--accent);text-decoration:none}\n.fm-f-msg{color:var(--text)}\n.fm-f-rule{color:var(--dim);font-size:0.6rem;font-family:\"SF Mono\",monospace}\n\n/* Teaser (no Pro key) */\n.fm-teaser{margin-top:1.5rem}\n.fm-teaser-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1rem;margin-bottom:2rem}\n.fm-card-blur{filter:blur(3px);opacity:0.5;pointer-events:none;user-select:none}\n.fm-cta{text-align:center;padding:2rem;background:linear-gradient(135deg,#0f0f1a,#13131f);border:1px solid #2a2a3d;border-radius:12px}\n.fm-cta code{background:#1a1a2e;padding:0.2rem 0.5rem;border-radius:4px;font-size:0.8rem}\n@media(max-width:640px){\n.fm-grid,.fm-teaser-grid{grid-template-columns:1fr}\n.fm-stats{flex-wrap:wrap;gap:1rem}\n}\n\n/* \u2500\u2500 Preferences panel \u2500\u2500 */\n.prefs-btn{background:none;border:1px solid var(--border);color:var(--muted);font-size:0.72rem;cursor:pointer;padding:0.2rem 0.5rem;border-radius:6px;margin-left:auto;flex-shrink:0;font-family:inherit;line-height:1.4}\n.prefs-btn:hover{color:var(--text);border-color:var(--dim)}\n.prefs-panel{display:none;position:absolute;top:var(--top-h);right:1rem;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem 1rem;z-index:40;min-width:200px;box-shadow:0 8px 30px #0008}\n.prefs-panel.open{display:block}\n.prefs-label{font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600;margin-bottom:0.3rem}\n.prefs-label:not(:first-child){margin-top:0.7rem}\n.prefs-row{display:flex;gap:0.3rem}\n.prefs-opt{background:var(--card-alt);border:1px solid var(--border);color:var(--muted);font-size:0.68rem;padding:0.25rem 0.6rem;border-radius:6px;cursor:pointer;font-family:inherit;transition:all 0.1s}\n.prefs-opt:hover{color:var(--text);border-color:var(--dim)}\n.prefs-opt.active{background:var(--accent);color:#fff;border-color:var(--accent)}\n[data-theme=\"light\"] .prefs-panel{box-shadow:0 8px 30px #0002}\n\n/* \u2500\u2500 Actions page \u2500\u2500 */\n.act-summary{display:flex;gap:1.5rem;margin-bottom:2rem}\n.act-stat{text-align:center}\n.act-stat-n{display:block;font-size:1.8rem;font-weight:700;line-height:1.2}\n.act-stat-l{font-size:0.72rem;color:var(--muted)}\n.act-section{margin-bottom:2.5rem}\n.act-section h3{display:flex;align-items:center;gap:0.5rem;font-size:1rem;margin-bottom:0.3rem}\n.act-icon{display:inline-flex;align-items:center;justify-content:center;width:1.6rem;height:1.6rem;border-radius:8px;font-size:0.9rem}\n.act-count{font-size:0.72rem;color:var(--muted);font-weight:400}\n.act-desc{font-size:0.78rem;color:var(--muted);margin-bottom:0.8rem}\n.act-cmd{margin-bottom:1rem}\n.act-cmd code{display:inline-block;background:var(--card);border:1px solid var(--border);padding:0.3rem 0.8rem;border-radius:6px;font-size:0.78rem;color:var(--accent)}\n.act-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:0.8rem}\n.act-card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:0.8rem 1rem}\n.act-card-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:0.4rem}\n.act-check{font-size:0.78rem;font-weight:600}\n.act-fix{font-size:0.78rem;color:var(--pass);margin-bottom:0.5rem;padding:0.3rem 0.5rem;background:var(--pass)10;border-radius:4px}\n.act-rec{font-size:0.75rem;color:var(--muted);margin-bottom:0.5rem;line-height:1.4}\n.act-item{font-size:0.72rem;color:var(--dim);padding:0.15rem 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.act-details{margin-top:0.5rem}\n.act-details summary{font-size:0.75rem;color:var(--muted);cursor:pointer}\n.act-table{width:100%;font-size:0.72rem;margin-top:0.3rem}\n.act-table td{padding:0.2rem 0.4rem;border-bottom:1px solid var(--border)}\n.act-table .act-check{color:var(--muted);white-space:nowrap}\n@media(max-width:600px){.act-summary{flex-direction:column;gap:0.8rem;align-items:center}.act-grid{grid-template-columns:1fr}}\n\n/* \u2500\u2500 Delta banner \u2500\u2500 */\n.delta-banner{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:1.2rem 1.5rem;margin-bottom:2rem}\n.delta-head{display:flex;align-items:center;gap:1rem;margin-bottom:0.5rem}\n.delta-title{font-weight:700;font-size:0.9rem}\n.delta-score{font-size:0.85rem;color:var(--muted)}\n.delta-arrow{font-weight:800;font-size:1rem}\n.delta-stats{display:flex;gap:1.2rem;font-size:0.78rem;margin-bottom:0.5rem}\n.delta-checks{display:flex;flex-wrap:wrap;gap:0.4rem;margin-bottom:0.5rem}\n.delta-chip{font-size:0.7rem;padding:0.15rem 0.5rem;background:var(--card-alt);border:1px solid var(--border);border-radius:4px;white-space:nowrap}\n.delta-fixed,.delta-new{font-size:0.75rem;color:var(--muted);margin-top:0.3rem}\n";
|
package/dist/report/styles.js
CHANGED
|
@@ -294,4 +294,41 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
294
294
|
.prefs-opt:hover{color:var(--text);border-color:var(--dim)}
|
|
295
295
|
.prefs-opt.active{background:var(--accent);color:#fff;border-color:var(--accent)}
|
|
296
296
|
[data-theme="light"] .prefs-panel{box-shadow:0 8px 30px #0002}
|
|
297
|
+
|
|
298
|
+
/* ── Actions page ── */
|
|
299
|
+
.act-summary{display:flex;gap:1.5rem;margin-bottom:2rem}
|
|
300
|
+
.act-stat{text-align:center}
|
|
301
|
+
.act-stat-n{display:block;font-size:1.8rem;font-weight:700;line-height:1.2}
|
|
302
|
+
.act-stat-l{font-size:0.72rem;color:var(--muted)}
|
|
303
|
+
.act-section{margin-bottom:2.5rem}
|
|
304
|
+
.act-section h3{display:flex;align-items:center;gap:0.5rem;font-size:1rem;margin-bottom:0.3rem}
|
|
305
|
+
.act-icon{display:inline-flex;align-items:center;justify-content:center;width:1.6rem;height:1.6rem;border-radius:8px;font-size:0.9rem}
|
|
306
|
+
.act-count{font-size:0.72rem;color:var(--muted);font-weight:400}
|
|
307
|
+
.act-desc{font-size:0.78rem;color:var(--muted);margin-bottom:0.8rem}
|
|
308
|
+
.act-cmd{margin-bottom:1rem}
|
|
309
|
+
.act-cmd code{display:inline-block;background:var(--card);border:1px solid var(--border);padding:0.3rem 0.8rem;border-radius:6px;font-size:0.78rem;color:var(--accent)}
|
|
310
|
+
.act-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:0.8rem}
|
|
311
|
+
.act-card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:0.8rem 1rem}
|
|
312
|
+
.act-card-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:0.4rem}
|
|
313
|
+
.act-check{font-size:0.78rem;font-weight:600}
|
|
314
|
+
.act-fix{font-size:0.78rem;color:var(--pass);margin-bottom:0.5rem;padding:0.3rem 0.5rem;background:var(--pass)10;border-radius:4px}
|
|
315
|
+
.act-rec{font-size:0.75rem;color:var(--muted);margin-bottom:0.5rem;line-height:1.4}
|
|
316
|
+
.act-item{font-size:0.72rem;color:var(--dim);padding:0.15rem 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
317
|
+
.act-details{margin-top:0.5rem}
|
|
318
|
+
.act-details summary{font-size:0.75rem;color:var(--muted);cursor:pointer}
|
|
319
|
+
.act-table{width:100%;font-size:0.72rem;margin-top:0.3rem}
|
|
320
|
+
.act-table td{padding:0.2rem 0.4rem;border-bottom:1px solid var(--border)}
|
|
321
|
+
.act-table .act-check{color:var(--muted);white-space:nowrap}
|
|
322
|
+
@media(max-width:600px){.act-summary{flex-direction:column;gap:0.8rem;align-items:center}.act-grid{grid-template-columns:1fr}}
|
|
323
|
+
|
|
324
|
+
/* ── Delta banner ── */
|
|
325
|
+
.delta-banner{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:1.2rem 1.5rem;margin-bottom:2rem}
|
|
326
|
+
.delta-head{display:flex;align-items:center;gap:1rem;margin-bottom:0.5rem}
|
|
327
|
+
.delta-title{font-weight:700;font-size:0.9rem}
|
|
328
|
+
.delta-score{font-size:0.85rem;color:var(--muted)}
|
|
329
|
+
.delta-arrow{font-weight:800;font-size:1rem}
|
|
330
|
+
.delta-stats{display:flex;gap:1.2rem;font-size:0.78rem;margin-bottom:0.5rem}
|
|
331
|
+
.delta-checks{display:flex;flex-wrap:wrap;gap:0.4rem;margin-bottom:0.5rem}
|
|
332
|
+
.delta-chip{font-size:0.7rem;padding:0.15rem 0.5rem;background:var(--card-alt);border:1px solid var(--border);border-radius:4px;white-space:nowrap}
|
|
333
|
+
.delta-fixed,.delta-new{font-size:0.75rem;color:var(--muted);margin-top:0.3rem}
|
|
297
334
|
`;
|
|
@@ -183,7 +183,10 @@ export function runAccessibility(cwd) {
|
|
|
183
183
|
name: "accessibility",
|
|
184
184
|
score,
|
|
185
185
|
grade: gradeFromScore(score),
|
|
186
|
-
details: {
|
|
186
|
+
details: {
|
|
187
|
+
jsxFiles: files.length, missingAlt, clickDiv, missingLabel, missingLang, autofocus, positiveTabindex,
|
|
188
|
+
suggestion: !hasA11yPlugin ? "Install eslint-plugin-jsx-a11y for deeper accessibility analysis: pnpm add -D eslint-plugin-jsx-a11y" : undefined,
|
|
189
|
+
},
|
|
187
190
|
issues,
|
|
188
191
|
duration: Date.now() - start,
|
|
189
192
|
};
|
|
@@ -173,7 +173,7 @@ function checkSupplyChain(has, read) {
|
|
|
173
173
|
let followed = 0;
|
|
174
174
|
// Lockfile committed
|
|
175
175
|
practices++;
|
|
176
|
-
const hasLockfile = has("pnpm-lock.yaml") || has("package-lock.json") || has("yarn.lock") || has("bun.lockb") || has("pubspec.lock");
|
|
176
|
+
const hasLockfile = has("pnpm-lock.yaml") || has("package-lock.json") || has("yarn.lock") || has("bun.lockb") || has("bun.lock") || has("pubspec.lock");
|
|
177
177
|
if (hasLockfile) {
|
|
178
178
|
followed++;
|
|
179
179
|
}
|
|
@@ -117,12 +117,9 @@ export function runConfusion(cwd) {
|
|
|
117
117
|
const dirGroups = new Map();
|
|
118
118
|
for (const f of files) {
|
|
119
119
|
const dir = f.path.includes("/") ? f.path.replace(/\/[^/]+$/, "") : ".";
|
|
120
|
-
|
|
121
|
-
const pkg = f.path.match(/^(packages|apps|libs|modules|internal)\/[^/]+/)?.[0];
|
|
122
|
-
const groupKey = pkg || dir;
|
|
123
|
-
const group = dirGroups.get(groupKey) || [];
|
|
120
|
+
const group = dirGroups.get(dir) || [];
|
|
124
121
|
group.push(f);
|
|
125
|
-
dirGroups.set(
|
|
122
|
+
dirGroups.set(dir, group);
|
|
126
123
|
}
|
|
127
124
|
// Also do a global comparison but only for files in the same top-level group
|
|
128
125
|
for (const group of dirGroups.values()) {
|
|
@@ -130,11 +127,12 @@ export function runConfusion(cwd) {
|
|
|
130
127
|
for (let j = i + 1; j < group.length; j++) {
|
|
131
128
|
const a = group[i].base;
|
|
132
129
|
const b = group[j].base;
|
|
133
|
-
// Near-identical (Levenshtein ≤ 2, but
|
|
130
|
+
// Near-identical (Levenshtein ≤ 2, but require longer names for distance 2)
|
|
134
131
|
// Early exit: length diff > 2 means edit distance > 2
|
|
135
132
|
if (a !== b && a.length >= 3 && b.length >= 3 && Math.abs(a.length - b.length) <= 2) {
|
|
136
133
|
const dist = levenshtein(a, b);
|
|
137
|
-
|
|
134
|
+
const minLen = Math.min(a.length, b.length);
|
|
135
|
+
if (dist === 1 || (dist === 2 && minLen >= 5)) {
|
|
138
136
|
fileConfusability++;
|
|
139
137
|
issues.push({
|
|
140
138
|
severity: "warning",
|
|
@@ -179,8 +177,8 @@ export function runConfusion(cwd) {
|
|
|
179
177
|
rule: "generic-name",
|
|
180
178
|
});
|
|
181
179
|
}
|
|
182
|
-
// Match
|
|
183
|
-
const varMatch = line.match(/^
|
|
180
|
+
// Match exported variable assignments with generic names (local vars are fine)
|
|
181
|
+
const varMatch = line.match(/^export\s+(?:const|let)\s+(\w+)\s*=/);
|
|
184
182
|
if (varMatch && GENERIC_NAMES.has(varMatch[1].toLowerCase()) && varMatch[1].length <= 6) {
|
|
185
183
|
genericNames++;
|
|
186
184
|
issues.push({
|
|
@@ -193,7 +191,7 @@ export function runConfusion(cwd) {
|
|
|
193
191
|
}
|
|
194
192
|
}
|
|
195
193
|
}
|
|
196
|
-
// ── 3. Export name collisions ──
|
|
194
|
+
// ── 3. Export name collisions (within same package scope) ──
|
|
197
195
|
const exportMap = new Map();
|
|
198
196
|
for (const f of files) {
|
|
199
197
|
for (const exp of f.exports) {
|
|
@@ -204,13 +202,26 @@ export function runConfusion(cwd) {
|
|
|
204
202
|
}
|
|
205
203
|
for (const [name, paths] of exportMap) {
|
|
206
204
|
if (paths.length > 1) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
205
|
+
// In monorepos, only flag collisions within the same package
|
|
206
|
+
const pkgOf = (p) => p.match(/^(packages|apps|libs|modules|internal)\/[^/]+/)?.[0] || ".";
|
|
207
|
+
const pkgGroups = new Map();
|
|
208
|
+
for (const p of paths) {
|
|
209
|
+
const pkg = pkgOf(p);
|
|
210
|
+
const arr = pkgGroups.get(pkg) || [];
|
|
211
|
+
arr.push(p);
|
|
212
|
+
pkgGroups.set(pkg, arr);
|
|
213
|
+
}
|
|
214
|
+
for (const groupPaths of pkgGroups.values()) {
|
|
215
|
+
if (groupPaths.length > 1) {
|
|
216
|
+
exportCollisions++;
|
|
217
|
+
issues.push({
|
|
218
|
+
severity: "error",
|
|
219
|
+
message: `Export collision: "${name}" exported from ${groupPaths.length} files — LLMs may reference the wrong one`,
|
|
220
|
+
file: groupPaths.join(", "),
|
|
221
|
+
rule: "export-collision",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
214
225
|
}
|
|
215
226
|
}
|
|
216
227
|
// ── 4. Ambiguous abbreviations in filenames and exports ──
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Design Consistency — LLM-powered audit of visual consistency across components.
|
|
2
|
+
*
|
|
3
|
+
* Pro feature. Requires VCQA_PRO_KEY env var.
|
|
4
|
+
*
|
|
5
|
+
* Detects:
|
|
6
|
+
* - Components that define the same visual pattern differently (accidental design system)
|
|
7
|
+
* - Spacing/color/typography inconsistencies across files
|
|
8
|
+
* - Missing component extraction opportunities
|
|
9
|
+
* - Tailwind class patterns that should be @apply'd or componentized
|
|
10
|
+
*/
|
|
11
|
+
import type { CheckResult } from "../types.js";
|
|
12
|
+
export declare function runDesignConsistency(cwd: string): Promise<CheckResult>;
|