quiver-cli 0.8.0 → 1.0.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 +110 -45
- package/bin/quiver-cli.mjs +3 -1
- package/dist/cli.js +546 -458
- package/package.json +2 -2
- package/template/.agents/AGENTS.md +4 -3
- package/template/.agents/config.json +7 -0
- package/template/.agents/plugins/opencode/rtk.ts +34 -0
- package/template/.agents/skills/agent-browser/SKILL.md +1 -0
- package/template/.agents/skills/apps/skybridge/SKILL.md +4 -0
- package/template/.agents/skills/design/impeccable/SKILL.md +36 -118
- package/template/.agents/skills/design/impeccable/reference/adapt.md +1 -0
- package/template/.agents/skills/design/impeccable/reference/adapt.native.md +58 -0
- package/template/.agents/skills/design/impeccable/reference/android.md +40 -0
- package/template/.agents/skills/design/impeccable/reference/animate.md +73 -188
- package/template/.agents/skills/design/impeccable/reference/audit.md +12 -9
- package/template/.agents/skills/design/impeccable/reference/audit.native.md +139 -0
- package/template/.agents/skills/design/impeccable/reference/bolder.md +19 -101
- package/template/.agents/skills/design/impeccable/reference/clarify.md +59 -253
- package/template/.agents/skills/design/impeccable/reference/colorize.md +51 -222
- package/template/.agents/skills/design/impeccable/reference/craft-floor.md +42 -0
- package/template/.agents/skills/design/impeccable/reference/craft.md +3 -121
- package/template/.agents/skills/design/impeccable/reference/critique.md +44 -23
- package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +90 -0
- package/template/.agents/skills/design/impeccable/reference/degraded/documenter.md +24 -0
- package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +37 -0
- package/template/.agents/skills/design/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/template/.agents/skills/design/impeccable/reference/delight.md +47 -279
- package/template/.agents/skills/design/impeccable/reference/distill.md +2 -2
- package/template/.agents/skills/design/impeccable/reference/doctor.md +53 -0
- package/template/.agents/skills/design/impeccable/reference/document.md +60 -73
- package/template/.agents/skills/design/impeccable/reference/harden.md +1 -12
- package/template/.agents/skills/design/impeccable/reference/hooks.md +20 -5
- package/template/.agents/skills/design/impeccable/reference/init.md +72 -119
- package/template/.agents/skills/design/impeccable/reference/ios.md +45 -0
- package/template/.agents/skills/design/impeccable/reference/layout.md +54 -131
- package/template/.agents/skills/design/impeccable/reference/live-setup.md +102 -0
- package/template/.agents/skills/design/impeccable/reference/live.md +116 -511
- package/template/.agents/skills/design/impeccable/reference/new-work.md +105 -0
- package/template/.agents/skills/design/impeccable/reference/{product.md → operate.md} +6 -5
- package/template/.agents/skills/design/impeccable/reference/optimize.md +4 -4
- package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -4
- package/template/.agents/skills/design/impeccable/reference/polish.md +68 -212
- package/template/.agents/skills/design/impeccable/reference/quieter.md +3 -3
- package/template/.agents/skills/design/impeccable/reference/routing.md +18 -0
- package/template/.agents/skills/design/impeccable/reference/shape.md +38 -144
- package/template/.agents/skills/design/impeccable/reference/typeset.md +51 -250
- package/template/.agents/skills/design/impeccable/reference/visualize.md +47 -0
- package/template/.agents/skills/design/impeccable/scripts/command-metadata.json +1 -1
- package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +558 -0
- package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +119 -10
- package/template/.agents/skills/design/impeccable/scripts/context.mjs +534 -45
- package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +18 -47
- package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +96 -10
- package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +174 -26
- package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +233 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +3348 -203
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +102 -7
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +297 -97
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +187 -16
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +41 -11
- package/template/.agents/skills/design/impeccable/scripts/detector/findings.mjs +7 -1
- package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +16 -2
- package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +207 -38
- package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +3082 -173
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +11 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/fonts.mjs +30 -0
- package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +336 -0
- package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +133 -0
- package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +240 -0
- package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +98 -18
- package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +46 -6
- package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +518 -50
- package/template/.agents/skills/design/impeccable/scripts/hook.mjs +25 -8
- package/template/.agents/skills/design/impeccable/scripts/lib/artifact-schema.mjs +93 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/composition-catalog.mjs +200 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +357 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +27 -7
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +17 -8
- package/template/.agents/skills/design/impeccable/scripts/lib/provider.mjs +5 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +362 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +457 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness-notice.mjs +169 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +457 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +151 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/target-slug.mjs +33 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/template-extensions.mjs +146 -0
- package/template/.agents/skills/design/impeccable/scripts/live/accept-css.mjs +617 -0
- package/template/.agents/skills/design/impeccable/scripts/live/accept-verify.mjs +60 -0
- package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +7 -1
- package/template/.agents/skills/design/impeccable/scripts/live/completion.mjs +10 -1
- package/template/.agents/skills/design/impeccable/scripts/live/event-validation.mjs +67 -5
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/astro.mjs +47 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/detect-utils.mjs +73 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/index.mjs +143 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/journal.mjs +197 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nextjs.mjs +49 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nuxt.mjs +161 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/script-src.mjs +17 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/static-html.mjs +26 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/sveltekit.mjs +71 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tag-strategy.mjs +247 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tanstack-start.mjs +70 -0
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/vite-generic.mjs +42 -0
- package/template/.agents/skills/design/impeccable/scripts/live/generation-preflight.mjs +149 -0
- package/template/.agents/skills/design/impeccable/scripts/live/instructions.mjs +142 -0
- package/template/.agents/skills/design/impeccable/scripts/live/poll-lanes.mjs +14 -0
- package/template/.agents/skills/design/impeccable/scripts/live/roots.mjs +508 -0
- package/template/.agents/skills/design/impeccable/scripts/live/session-store.mjs +324 -50
- package/template/.agents/skills/design/impeccable/scripts/live/source-lock.mjs +105 -0
- package/template/.agents/skills/design/impeccable/scripts/live/source-search.mjs +105 -0
- package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +961 -0
- package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +588 -72
- package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +59 -17
- package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +280 -0
- package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +135 -0
- package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +210 -68
- package/template/.agents/skills/design/impeccable/scripts/live-browser.js +1562 -223
- package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +3 -0
- package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +33 -1
- package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +175 -255
- package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +26 -6
- package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +6 -1
- package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +61 -16
- package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +39 -10
- package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +573 -47
- package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +17 -7
- package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +124 -91
- package/template/.agents/skills/design/impeccable/scripts/live.mjs +88 -26
- package/template/.agents/skills/design/impeccable/scripts/palette.mjs +76 -81
- package/template/.agents/skills/design/impeccable/scripts/pin.mjs +18 -11
- package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +932 -0
- package/template/.agents/skills/design/impeccable/scripts/surface-brief.mjs +74 -0
- package/template/.agents/skills/design/shadcn/SKILL.md +46 -11
- package/template/.agents/skills/design/shadcn/cli.md +49 -16
- package/template/.agents/skills/design/shadcn/customization.md +14 -7
- package/template/.agents/skills/design/shadcn/evals/evals.json +30 -0
- package/template/.agents/skills/design/shadcn/mcp.md +27 -16
- package/template/.agents/skills/design/shadcn/registry.md +277 -0
- package/template/.agents/skills/design/shadcn/rules/chat.md +224 -0
- package/template/.agents/skills/design/shadcn/rules/composition.md +20 -2
- package/template/.agents/skills/design/shadcn/rules/styling.md +23 -0
- package/template/.agents/skills/find-skills/SKILL.md +2 -3
- package/template/.agents/skills/hono/SKILL.md +579 -0
- package/template/.agents/skills/integrations/langfuse/SKILL.md +10 -6
- package/template/.agents/skills/integrations/langfuse/references/ci-cd.md +41 -0
- package/template/.agents/skills/integrations/langfuse/references/cli.md +8 -0
- package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +15 -27
- package/template/.agents/skills/integrations/langfuse/references/instrumentation.md +39 -52
- package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +5 -2
- package/template/.agents/skills/integrations/langfuse/references/prompt-engineering.md +35 -0
- package/template/.agents/skills/integrations/langfuse/references/prompt-migration.md +41 -196
- package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +3 -0
- package/template/.agents/skills/integrations/langfuse/references/trace-evaluator-upgrade.md +76 -0
- package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +4 -0
- package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +73 -0
- package/template/.agents/skills/supabase/CHANGELOG.md +71 -0
- package/template/.agents/skills/supabase/SKILL.md +145 -0
- package/template/.agents/skills/supabase/assets/feedback-issue-template.md +17 -0
- package/template/.agents/skills/supabase/references/skill-feedback.md +17 -0
- package/template/.agents/skills/supabase-postgres-best-practices/CHANGELOG.md +73 -0
- package/template/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/_template.md +34 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -0
- package/template/.agents/upstreams.json +34 -12
- package/template/.agents/skills/design/impeccable/reference/brand.md +0 -108
- package/template/.agents/skills/design/impeccable/reference/codex.md +0 -105
- package/template/.agents/skills/design/impeccable/reference/interaction-design.md +0 -189
- package/template/.agents/skills/integrations/langfuse/references/sdk-upgrade.md +0 -175
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
import { finding } from './findings.mjs';
|
|
@@ -7,8 +8,15 @@ import { parseAnyColor, resolveLengthPx } from './rules/checks.mjs';
|
|
|
7
8
|
|
|
8
9
|
const DESIGN_NAMES = ['DESIGN.md', 'Design.md', 'design.md'];
|
|
9
10
|
const FALLBACK_DIRS = ['.agents/context', 'docs'];
|
|
11
|
+
// Files/dirs whose presence marks a directory as a project root. Mirrors the
|
|
12
|
+
// walk-up semantics of skill/scripts/context.mjs (`resolveProject`), which the
|
|
13
|
+
// CLI can't import (separate tree). `.git` and `package.json` are the common
|
|
14
|
+
// boundaries; `.impeccable` is our own project marker.
|
|
15
|
+
const PROJECT_ROOT_MARKERS = ['.git', 'package.json', '.impeccable'];
|
|
10
16
|
const COLOR_CHANNEL_TOLERANCE = 6;
|
|
11
17
|
const RADIUS_TOLERANCE_PX = 0.5;
|
|
18
|
+
const FONT_SIZE_TOLERANCE_PX = 0.5;
|
|
19
|
+
const FONT_SIZE_LITERAL_RE = /^-?[\d.]+(?:px|rem)$/;
|
|
12
20
|
|
|
13
21
|
const CSS_COLOR_RE = /#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|oklch\([^)]+\)|hsla?\([^)]+\)/gi;
|
|
14
22
|
const FONT_DECL_RE = /font-family\s*:\s*([^;}\n]+)/gi;
|
|
@@ -16,6 +24,9 @@ const FONT_JS_RE = /fontFamily\s*[:=]\s*["'`]([^"'`]+)["'`]/g;
|
|
|
16
24
|
const GOOGLE_FONT_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
|
|
17
25
|
const BORDER_RADIUS_RE = /border-radius\s*:\s*([^;}\n]+)/gi;
|
|
18
26
|
const BORDER_RADIUS_JS_RE = /borderRadius\s*[:=]\s*["'`]([^"'`]+)["'`]/g;
|
|
27
|
+
const FONT_SIZE_DECL_RE = /font-size\s*:\s*([^;}\n]+)/gi;
|
|
28
|
+
const FONT_SIZE_JS_RE = /fontSize\s*[:=]\s*["'`]([^"'`]+)["'`]/g;
|
|
29
|
+
const TAILWIND_FONT_SIZE_RE = /\btext-\[(-?[\d.]+(?:px|rem))\]/g;
|
|
19
30
|
const STATIC_DESIGN_SKIP_TAGS = new Set(['head', 'title', 'meta', 'link', 'style', 'script', 'noscript', 'template', 'source']);
|
|
20
31
|
|
|
21
32
|
function firstExisting(dir, names) {
|
|
@@ -283,6 +294,77 @@ function addTypographyFonts(out, typography) {
|
|
|
283
294
|
}
|
|
284
295
|
}
|
|
285
296
|
|
|
297
|
+
function addFontSizeStep(out, raw, { fluid = false } = {}) {
|
|
298
|
+
const text = String(raw ?? '').trim().toLowerCase();
|
|
299
|
+
if (!FONT_SIZE_LITERAL_RE.test(text)) return;
|
|
300
|
+
const px = resolveLengthPx(text, 16);
|
|
301
|
+
if (px == null || !Number.isFinite(px) || px <= 0) return;
|
|
302
|
+
out.allowedFontSizes.push({ value: text, px, fluid });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Split a fluid value into its three terms, or null when it is not a
|
|
306
|
+
// well-formed clamp(). Used both to read DESIGN.md's fluid roles and to
|
|
307
|
+
// validate fluid values in source, so the two stay symmetric.
|
|
308
|
+
function parseClampArgs(raw) {
|
|
309
|
+
const match = /^clamp\(\s*([\s\S]+)\s*\)$/i.exec(String(raw ?? '').trim());
|
|
310
|
+
if (!match) return null;
|
|
311
|
+
const args = splitTopLevelArgs(match[1]);
|
|
312
|
+
return args.length === 3 ? args : null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// A fluid role declares its two fixed endpoints and interpolates between them
|
|
316
|
+
// with a viewport unit. Both endpoints are documented sizes, so they belong in
|
|
317
|
+
// the allowlist; the middle term is viewport-relative and never a fixed step.
|
|
318
|
+
// Endpoints are marked `fluid` because they do not *enumerate* a ramp: see
|
|
319
|
+
// `hasFontSizes` below for why that distinction has to survive.
|
|
320
|
+
function addClampEndpoints(out, raw) {
|
|
321
|
+
const args = parseClampArgs(raw);
|
|
322
|
+
if (!args) return false;
|
|
323
|
+
addFontSizeStep(out, args[0], { fluid: true });
|
|
324
|
+
addFontSizeStep(out, args[2], { fluid: true });
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function splitTopLevelArgs(s) {
|
|
329
|
+
const args = [];
|
|
330
|
+
let depth = 0;
|
|
331
|
+
let current = '';
|
|
332
|
+
for (const ch of String(s)) {
|
|
333
|
+
if (ch === '(') depth++;
|
|
334
|
+
else if (ch === ')') depth--;
|
|
335
|
+
if (ch === ',' && depth === 0) {
|
|
336
|
+
args.push(current.trim());
|
|
337
|
+
current = '';
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
current += ch;
|
|
341
|
+
}
|
|
342
|
+
if (current.trim()) args.push(current.trim());
|
|
343
|
+
return args;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function addTypographySizes(out, typography) {
|
|
347
|
+
if (!typography || typeof typography !== 'object') return;
|
|
348
|
+
|
|
349
|
+
// `scale` is the enumerated ramp: a name -> size map, since the frontmatter
|
|
350
|
+
// parser has no list support. It sits alongside the named roles.
|
|
351
|
+
const scale = typography.scale;
|
|
352
|
+
if (scale && typeof scale === 'object') {
|
|
353
|
+
for (const value of Object.values(scale)) {
|
|
354
|
+
if (typeof value !== 'string' && typeof value !== 'number') continue;
|
|
355
|
+
addFontSizeStep(out, value);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
for (const [name, role] of Object.entries(typography)) {
|
|
360
|
+
if (name === 'scale') continue;
|
|
361
|
+
if (!role || typeof role !== 'object') continue;
|
|
362
|
+
const raw = String(role.fontSize ?? '').trim().toLowerCase();
|
|
363
|
+
if (addClampEndpoints(out, raw)) continue;
|
|
364
|
+
addFontSizeStep(out, raw);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
286
368
|
function addRoundedScale(out, rounded) {
|
|
287
369
|
if (!rounded || typeof rounded !== 'object') return;
|
|
288
370
|
for (const [rawName, value] of Object.entries(rounded)) {
|
|
@@ -340,10 +422,12 @@ function normalizeDesignSystem(input = {}) {
|
|
|
340
422
|
allowedFonts: new Set(),
|
|
341
423
|
allowedColorKeys: new Map(),
|
|
342
424
|
allowedRadii: [],
|
|
425
|
+
allowedFontSizes: [],
|
|
343
426
|
hasPillRadius: false,
|
|
344
427
|
};
|
|
345
428
|
|
|
346
429
|
addTypographyFonts(out, frontmatter.typography);
|
|
430
|
+
addTypographySizes(out, frontmatter.typography);
|
|
347
431
|
addColorObject(out, frontmatter.colors);
|
|
348
432
|
addSidecarColors(out, sidecar);
|
|
349
433
|
addRoundedScale(out, frontmatter.rounded);
|
|
@@ -352,6 +436,10 @@ function normalizeDesignSystem(input = {}) {
|
|
|
352
436
|
out.hasFonts = out.allowedFonts.size > 0;
|
|
353
437
|
out.hasColors = out.allowedColorKeys.size > 0;
|
|
354
438
|
out.hasRadii = out.allowedRadii.length > 0;
|
|
439
|
+
// Gate on *enumerated* steps only. A fully fluid system declares clamp
|
|
440
|
+
// endpoints but no discrete ramp, so treating those endpoints as the whole
|
|
441
|
+
// allowlist would flag every intermediate size. Abstain instead.
|
|
442
|
+
out.hasFontSizes = out.allowedFontSizes.some(entry => !entry.fluid);
|
|
355
443
|
return out;
|
|
356
444
|
}
|
|
357
445
|
|
|
@@ -387,6 +475,62 @@ function loadDesignSystemForCwd(cwd = process.cwd()) {
|
|
|
387
475
|
});
|
|
388
476
|
}
|
|
389
477
|
|
|
478
|
+
// Directory to begin the project-root walk from, given a scan target that may
|
|
479
|
+
// be a file or a directory (and may not exist yet).
|
|
480
|
+
function designSystemStartDir(targetPath, cwd = process.cwd()) {
|
|
481
|
+
const abs = path.isAbsolute(targetPath) ? targetPath : path.resolve(cwd, targetPath);
|
|
482
|
+
try {
|
|
483
|
+
return fs.statSync(abs).isDirectory() ? abs : path.dirname(abs);
|
|
484
|
+
} catch {
|
|
485
|
+
// Nonexistent path: treat an extension-bearing leaf as a file.
|
|
486
|
+
return path.extname(abs) ? path.dirname(abs) : abs;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Walk up from `startDir` to the directory that governs the target's design
|
|
491
|
+
// system, mirroring skill/scripts/context.mjs's project-boundary semantics:
|
|
492
|
+
//
|
|
493
|
+
// - A directory carrying a DESIGN.md (directly or in a fallback dir) IS the
|
|
494
|
+
// design root — that's where the rules live.
|
|
495
|
+
// - A directory carrying a project marker (.git / package.json / .impeccable)
|
|
496
|
+
// but no DESIGN.md is a project BOUNDARY: the walk stops with no design
|
|
497
|
+
// system, so a sibling project never inherits a parent's or cwd's rules.
|
|
498
|
+
// - Reaching the home directory / filesystem root with neither means no
|
|
499
|
+
// design system at all — never process.cwd()'s.
|
|
500
|
+
//
|
|
501
|
+
// Returns { dir, hasDesign } for the stopping directory, or null when the walk
|
|
502
|
+
// runs out. This is the fix for cross-project contamination.
|
|
503
|
+
export function findDesignRoot(startDir) {
|
|
504
|
+
let dir = path.resolve(startDir);
|
|
505
|
+
const homeDir = path.resolve(os.homedir());
|
|
506
|
+
while (true) {
|
|
507
|
+
if (resolveDesignMdPath(dir)) return { dir, hasDesign: true };
|
|
508
|
+
if (PROJECT_ROOT_MARKERS.some((marker) => fs.existsSync(path.join(dir, marker)))) {
|
|
509
|
+
return { dir, hasDesign: false };
|
|
510
|
+
}
|
|
511
|
+
if (dir === homeDir) return null;
|
|
512
|
+
const parent = path.dirname(dir);
|
|
513
|
+
if (parent === dir) return null;
|
|
514
|
+
dir = parent;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Resolve the design system that governs a specific scan target, by walking up
|
|
519
|
+
// from the target's own location — never process.cwd(). Scanning project B's
|
|
520
|
+
// files from inside project A applies B's DESIGN.md (or none), not A's.
|
|
521
|
+
//
|
|
522
|
+
// Pass a `cache` Map to memoize by resolved design root across a multi-file
|
|
523
|
+
// scan; a target with no design root above it resolves to null.
|
|
524
|
+
export function loadDesignSystemForTarget(targetPath, { cache, cwd = process.cwd() } = {}) {
|
|
525
|
+
const startDir = designSystemStartDir(targetPath, cwd);
|
|
526
|
+
const found = findDesignRoot(startDir);
|
|
527
|
+
const key = found ? `root:${found.dir}` : '\0none';
|
|
528
|
+
if (cache && cache.has(key)) return cache.get(key);
|
|
529
|
+
const loaded = found?.hasDesign ? loadDesignSystemForCwd(found.dir) : null;
|
|
530
|
+
if (cache) cache.set(key, loaded);
|
|
531
|
+
return loaded;
|
|
532
|
+
}
|
|
533
|
+
|
|
390
534
|
function isAllowedFont(font, designSystem) {
|
|
391
535
|
if (!font || GENERIC_FONTS.has(font)) return true;
|
|
392
536
|
if (!designSystem?.hasFonts) return true;
|
|
@@ -418,6 +562,43 @@ function isAllowedRadiusRaw(raw, designSystem) {
|
|
|
418
562
|
return designSystem.allowedRadii.some(entry => Math.abs(entry.px - px) <= RADIUS_TOLERANCE_PX);
|
|
419
563
|
}
|
|
420
564
|
|
|
565
|
+
// One term of a font-size value. `unjudgeable` covers var(), calc(), percentages
|
|
566
|
+
// and units the ramp cannot resolve (em is parent-relative, not root-relative);
|
|
567
|
+
// those abstain rather than guess.
|
|
568
|
+
function fontSizeStepStatus(raw, designSystem) {
|
|
569
|
+
const text = String(raw || '').trim().toLowerCase();
|
|
570
|
+
if (!FONT_SIZE_LITERAL_RE.test(text)) return 'unjudgeable';
|
|
571
|
+
const px = resolveLengthPx(text, 16);
|
|
572
|
+
if (px == null || !Number.isFinite(px) || px <= 0) return 'unjudgeable';
|
|
573
|
+
return designSystem.allowedFontSizes.some(
|
|
574
|
+
entry => Math.abs(entry.px - px) <= FONT_SIZE_TOLERANCE_PX,
|
|
575
|
+
) ? 'on-ramp' : 'off-ramp';
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// The off-ramp endpoints of a fluid value, or null when `raw` is not a fluid
|
|
579
|
+
// value at all. Only the min and max are judged: the viewport term interpolates
|
|
580
|
+
// between them and is never a fixed step.
|
|
581
|
+
//
|
|
582
|
+
// Reading clamp endpoints as documented steps without also checking them in
|
|
583
|
+
// usage would let `clamp(99rem, 1vw, 200rem)` through, which is how a fluid
|
|
584
|
+
// declaration stayed invisible until someone measured computed styles.
|
|
585
|
+
export function offRampClampEndpoints(raw, designSystem) {
|
|
586
|
+
if (!designSystem?.hasFontSizes) return null;
|
|
587
|
+
const args = parseClampArgs(String(raw || '').trim().replace(/\s*!important\s*$/i, ''));
|
|
588
|
+
if (!args) return null;
|
|
589
|
+
return [args[0], args[2]].filter(
|
|
590
|
+
endpoint => fontSizeStepStatus(endpoint, designSystem) === 'off-ramp',
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function isAllowedFontSizeRaw(raw, designSystem) {
|
|
595
|
+
if (!designSystem?.hasFontSizes) return true;
|
|
596
|
+
const text = String(raw || '').trim().toLowerCase().replace(/\s*!important\s*$/, '');
|
|
597
|
+
const offRampEndpoints = offRampClampEndpoints(text, designSystem);
|
|
598
|
+
if (offRampEndpoints) return offRampEndpoints.length === 0;
|
|
599
|
+
return fontSizeStepStatus(text, designSystem) !== 'off-ramp';
|
|
600
|
+
}
|
|
601
|
+
|
|
421
602
|
function lineLooksCommented(line) {
|
|
422
603
|
const trimmed = String(line || '').trim();
|
|
423
604
|
return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*') || trimmed.startsWith('<!--');
|
|
@@ -509,6 +690,37 @@ function checkRadiusValue(value, filePath, line, designSystem, context) {
|
|
|
509
690
|
return findings;
|
|
510
691
|
}
|
|
511
692
|
|
|
693
|
+
function checkFontSizeValue(value, filePath, line, designSystem, context) {
|
|
694
|
+
const token = String(value || '').trim();
|
|
695
|
+
if (isAllowedFontSizeRaw(token, designSystem)) return [];
|
|
696
|
+
|
|
697
|
+
// Name the offending endpoint on a fluid value; the whole clamp() string is
|
|
698
|
+
// not actionable on its own, and it makes a poor ignore-value.
|
|
699
|
+
const offRampEndpoints = offRampClampEndpoints(token, designSystem) || [];
|
|
700
|
+
if (offRampEndpoints.length > 0) {
|
|
701
|
+
const plural = offRampEndpoints.length > 1 ? 's' : '';
|
|
702
|
+
return [makeDesignFinding(
|
|
703
|
+
'design-system-font-size',
|
|
704
|
+
filePath,
|
|
705
|
+
`${context}: ${token} has fluid endpoint${plural} ${offRampEndpoints.join(' and ')} off the DESIGN.md type ramp`,
|
|
706
|
+
line,
|
|
707
|
+
{ ignoreValue: offRampEndpoints[0] },
|
|
708
|
+
)];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// The snippet shows the declaration as authored, but the ignoreValue has to
|
|
712
|
+
// be what a `hooks ignore-value` waiver can match, so the priority marker is
|
|
713
|
+
// stripped. Otherwise the same size needs two different waivers depending on
|
|
714
|
+
// whether it carries !important. font-family already behaves this way.
|
|
715
|
+
return [makeDesignFinding(
|
|
716
|
+
'design-system-font-size',
|
|
717
|
+
filePath,
|
|
718
|
+
`${context}: ${token} is off the DESIGN.md type ramp`,
|
|
719
|
+
line,
|
|
720
|
+
{ ignoreValue: token.replace(/\s*!important\s*$/i, '').trim() },
|
|
721
|
+
)];
|
|
722
|
+
}
|
|
723
|
+
|
|
512
724
|
function checkSourceDesignSystem(content, filePath, options = {}) {
|
|
513
725
|
const designSystem = options.designSystem;
|
|
514
726
|
if (!designSystem?.present) return [];
|
|
@@ -567,6 +779,18 @@ function checkSourceDesignSystem(content, filePath, options = {}) {
|
|
|
567
779
|
findings.push(...checkRadiusValue(match[1], filePath, lineNum, designSystem, 'borderRadius'));
|
|
568
780
|
}
|
|
569
781
|
}
|
|
782
|
+
|
|
783
|
+
if (designSystem.hasFontSizes) {
|
|
784
|
+
for (const match of line.matchAll(FONT_SIZE_DECL_RE)) {
|
|
785
|
+
findings.push(...checkFontSizeValue(match[1], filePath, lineNum, designSystem, 'font-size'));
|
|
786
|
+
}
|
|
787
|
+
for (const match of line.matchAll(FONT_SIZE_JS_RE)) {
|
|
788
|
+
findings.push(...checkFontSizeValue(match[1], filePath, lineNum, designSystem, 'fontSize'));
|
|
789
|
+
}
|
|
790
|
+
for (const match of line.matchAll(TAILWIND_FONT_SIZE_RE)) {
|
|
791
|
+
findings.push(...checkFontSizeValue(match[1], filePath, lineNum, designSystem, 'text-[…] class'));
|
|
792
|
+
}
|
|
793
|
+
}
|
|
570
794
|
}
|
|
571
795
|
|
|
572
796
|
return dedupeDesignFindings(findings);
|
|
@@ -581,6 +805,8 @@ function sampleText(el) {
|
|
|
581
805
|
return text ? ` "${text.slice(0, 40)}"` : '';
|
|
582
806
|
}
|
|
583
807
|
|
|
808
|
+
// Font-size design-system checks are source-scan-only (see checkSourceDesignSystem).
|
|
809
|
+
// Computed font-size cascades and clamp() ramps resolve to off-ramp px in the browser.
|
|
584
810
|
function collectStaticDesignSystemFindings(document, window, filePath, designSystem) {
|
|
585
811
|
if (!designSystem?.present) return [];
|
|
586
812
|
const findings = [];
|
|
@@ -698,6 +924,12 @@ function canonicalDesignFindingKey(item) {
|
|
|
698
924
|
const label = String(value || '').trim().toLowerCase();
|
|
699
925
|
return label ? `${item.antipattern}:radius:${label}` : null;
|
|
700
926
|
}
|
|
927
|
+
if (item.antipattern === 'design-system-font-size') {
|
|
928
|
+
const px = resolveLengthPx(String(value || '').trim(), 16);
|
|
929
|
+
if (px != null && Number.isFinite(px)) return `${item.antipattern}:font-size:${Math.round(px * 100) / 100}`;
|
|
930
|
+
const label = String(value || '').trim().toLowerCase();
|
|
931
|
+
return label ? `${item.antipattern}:font-size:${label}` : null;
|
|
932
|
+
}
|
|
701
933
|
return null;
|
|
702
934
|
}
|
|
703
935
|
|
|
@@ -744,6 +976,7 @@ export {
|
|
|
744
976
|
isAllowedFont,
|
|
745
977
|
isAllowedColorRaw,
|
|
746
978
|
isAllowedRadiusRaw,
|
|
979
|
+
isAllowedFontSizeRaw,
|
|
747
980
|
checkSourceDesignSystem,
|
|
748
981
|
collectStaticDesignSystemFindings,
|
|
749
982
|
mergeDesignSystemFindings,
|