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.
Files changed (200) hide show
  1. package/README.md +110 -45
  2. package/bin/quiver-cli.mjs +3 -1
  3. package/dist/cli.js +546 -458
  4. package/package.json +2 -2
  5. package/template/.agents/AGENTS.md +4 -3
  6. package/template/.agents/config.json +7 -0
  7. package/template/.agents/plugins/opencode/rtk.ts +34 -0
  8. package/template/.agents/skills/agent-browser/SKILL.md +1 -0
  9. package/template/.agents/skills/apps/skybridge/SKILL.md +4 -0
  10. package/template/.agents/skills/design/impeccable/SKILL.md +36 -118
  11. package/template/.agents/skills/design/impeccable/reference/adapt.md +1 -0
  12. package/template/.agents/skills/design/impeccable/reference/adapt.native.md +58 -0
  13. package/template/.agents/skills/design/impeccable/reference/android.md +40 -0
  14. package/template/.agents/skills/design/impeccable/reference/animate.md +73 -188
  15. package/template/.agents/skills/design/impeccable/reference/audit.md +12 -9
  16. package/template/.agents/skills/design/impeccable/reference/audit.native.md +139 -0
  17. package/template/.agents/skills/design/impeccable/reference/bolder.md +19 -101
  18. package/template/.agents/skills/design/impeccable/reference/clarify.md +59 -253
  19. package/template/.agents/skills/design/impeccable/reference/colorize.md +51 -222
  20. package/template/.agents/skills/design/impeccable/reference/craft-floor.md +42 -0
  21. package/template/.agents/skills/design/impeccable/reference/craft.md +3 -121
  22. package/template/.agents/skills/design/impeccable/reference/critique.md +44 -23
  23. package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +90 -0
  24. package/template/.agents/skills/design/impeccable/reference/degraded/documenter.md +24 -0
  25. package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +37 -0
  26. package/template/.agents/skills/design/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  27. package/template/.agents/skills/design/impeccable/reference/delight.md +47 -279
  28. package/template/.agents/skills/design/impeccable/reference/distill.md +2 -2
  29. package/template/.agents/skills/design/impeccable/reference/doctor.md +53 -0
  30. package/template/.agents/skills/design/impeccable/reference/document.md +60 -73
  31. package/template/.agents/skills/design/impeccable/reference/harden.md +1 -12
  32. package/template/.agents/skills/design/impeccable/reference/hooks.md +20 -5
  33. package/template/.agents/skills/design/impeccable/reference/init.md +72 -119
  34. package/template/.agents/skills/design/impeccable/reference/ios.md +45 -0
  35. package/template/.agents/skills/design/impeccable/reference/layout.md +54 -131
  36. package/template/.agents/skills/design/impeccable/reference/live-setup.md +102 -0
  37. package/template/.agents/skills/design/impeccable/reference/live.md +116 -511
  38. package/template/.agents/skills/design/impeccable/reference/new-work.md +105 -0
  39. package/template/.agents/skills/design/impeccable/reference/{product.md → operate.md} +6 -5
  40. package/template/.agents/skills/design/impeccable/reference/optimize.md +4 -4
  41. package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -4
  42. package/template/.agents/skills/design/impeccable/reference/polish.md +68 -212
  43. package/template/.agents/skills/design/impeccable/reference/quieter.md +3 -3
  44. package/template/.agents/skills/design/impeccable/reference/routing.md +18 -0
  45. package/template/.agents/skills/design/impeccable/reference/shape.md +38 -144
  46. package/template/.agents/skills/design/impeccable/reference/typeset.md +51 -250
  47. package/template/.agents/skills/design/impeccable/reference/visualize.md +47 -0
  48. package/template/.agents/skills/design/impeccable/scripts/command-metadata.json +1 -1
  49. package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +558 -0
  50. package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +119 -10
  51. package/template/.agents/skills/design/impeccable/scripts/context.mjs +534 -45
  52. package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +18 -47
  53. package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +96 -10
  54. package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +174 -26
  55. package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +233 -0
  56. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +3348 -203
  57. package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +102 -7
  58. package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +297 -97
  59. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +187 -16
  60. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +41 -11
  61. package/template/.agents/skills/design/impeccable/scripts/detector/findings.mjs +7 -1
  62. package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +16 -2
  63. package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +207 -38
  64. package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +3082 -173
  65. package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +11 -0
  66. package/template/.agents/skills/design/impeccable/scripts/detector/shared/fonts.mjs +30 -0
  67. package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +336 -0
  68. package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +133 -0
  69. package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +240 -0
  70. package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +98 -18
  71. package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +46 -6
  72. package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +518 -50
  73. package/template/.agents/skills/design/impeccable/scripts/hook.mjs +25 -8
  74. package/template/.agents/skills/design/impeccable/scripts/lib/artifact-schema.mjs +93 -0
  75. package/template/.agents/skills/design/impeccable/scripts/lib/composition-catalog.mjs +200 -0
  76. package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +357 -0
  77. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +27 -7
  78. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +17 -8
  79. package/template/.agents/skills/design/impeccable/scripts/lib/provider.mjs +5 -0
  80. package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +362 -0
  81. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +457 -0
  82. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-notice.mjs +169 -0
  83. package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +457 -0
  84. package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +151 -0
  85. package/template/.agents/skills/design/impeccable/scripts/lib/target-slug.mjs +33 -0
  86. package/template/.agents/skills/design/impeccable/scripts/lib/template-extensions.mjs +146 -0
  87. package/template/.agents/skills/design/impeccable/scripts/live/accept-css.mjs +617 -0
  88. package/template/.agents/skills/design/impeccable/scripts/live/accept-verify.mjs +60 -0
  89. package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +7 -1
  90. package/template/.agents/skills/design/impeccable/scripts/live/completion.mjs +10 -1
  91. package/template/.agents/skills/design/impeccable/scripts/live/event-validation.mjs +67 -5
  92. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/astro.mjs +47 -0
  93. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/detect-utils.mjs +73 -0
  94. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/index.mjs +143 -0
  95. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/journal.mjs +197 -0
  96. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nextjs.mjs +49 -0
  97. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nuxt.mjs +161 -0
  98. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/script-src.mjs +17 -0
  99. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/static-html.mjs +26 -0
  100. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/sveltekit.mjs +71 -0
  101. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tag-strategy.mjs +247 -0
  102. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tanstack-start.mjs +70 -0
  103. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/vite-generic.mjs +42 -0
  104. package/template/.agents/skills/design/impeccable/scripts/live/generation-preflight.mjs +149 -0
  105. package/template/.agents/skills/design/impeccable/scripts/live/instructions.mjs +142 -0
  106. package/template/.agents/skills/design/impeccable/scripts/live/poll-lanes.mjs +14 -0
  107. package/template/.agents/skills/design/impeccable/scripts/live/roots.mjs +508 -0
  108. package/template/.agents/skills/design/impeccable/scripts/live/session-store.mjs +324 -50
  109. package/template/.agents/skills/design/impeccable/scripts/live/source-lock.mjs +105 -0
  110. package/template/.agents/skills/design/impeccable/scripts/live/source-search.mjs +105 -0
  111. package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +961 -0
  112. package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +588 -72
  113. package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +59 -17
  114. package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +280 -0
  115. package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +135 -0
  116. package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +210 -68
  117. package/template/.agents/skills/design/impeccable/scripts/live-browser.js +1562 -223
  118. package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +3 -0
  119. package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +33 -1
  120. package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +175 -255
  121. package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +26 -6
  122. package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +6 -1
  123. package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +61 -16
  124. package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +39 -10
  125. package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +573 -47
  126. package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +17 -7
  127. package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +124 -91
  128. package/template/.agents/skills/design/impeccable/scripts/live.mjs +88 -26
  129. package/template/.agents/skills/design/impeccable/scripts/palette.mjs +76 -81
  130. package/template/.agents/skills/design/impeccable/scripts/pin.mjs +18 -11
  131. package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +932 -0
  132. package/template/.agents/skills/design/impeccable/scripts/surface-brief.mjs +74 -0
  133. package/template/.agents/skills/design/shadcn/SKILL.md +46 -11
  134. package/template/.agents/skills/design/shadcn/cli.md +49 -16
  135. package/template/.agents/skills/design/shadcn/customization.md +14 -7
  136. package/template/.agents/skills/design/shadcn/evals/evals.json +30 -0
  137. package/template/.agents/skills/design/shadcn/mcp.md +27 -16
  138. package/template/.agents/skills/design/shadcn/registry.md +277 -0
  139. package/template/.agents/skills/design/shadcn/rules/chat.md +224 -0
  140. package/template/.agents/skills/design/shadcn/rules/composition.md +20 -2
  141. package/template/.agents/skills/design/shadcn/rules/styling.md +23 -0
  142. package/template/.agents/skills/find-skills/SKILL.md +2 -3
  143. package/template/.agents/skills/hono/SKILL.md +579 -0
  144. package/template/.agents/skills/integrations/langfuse/SKILL.md +10 -6
  145. package/template/.agents/skills/integrations/langfuse/references/ci-cd.md +41 -0
  146. package/template/.agents/skills/integrations/langfuse/references/cli.md +8 -0
  147. package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +15 -27
  148. package/template/.agents/skills/integrations/langfuse/references/instrumentation.md +39 -52
  149. package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +5 -2
  150. package/template/.agents/skills/integrations/langfuse/references/prompt-engineering.md +35 -0
  151. package/template/.agents/skills/integrations/langfuse/references/prompt-migration.md +41 -196
  152. package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +3 -0
  153. package/template/.agents/skills/integrations/langfuse/references/trace-evaluator-upgrade.md +76 -0
  154. package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +4 -0
  155. package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +73 -0
  156. package/template/.agents/skills/supabase/CHANGELOG.md +71 -0
  157. package/template/.agents/skills/supabase/SKILL.md +145 -0
  158. package/template/.agents/skills/supabase/assets/feedback-issue-template.md +17 -0
  159. package/template/.agents/skills/supabase/references/skill-feedback.md +17 -0
  160. package/template/.agents/skills/supabase-postgres-best-practices/CHANGELOG.md +73 -0
  161. package/template/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  162. package/template/.agents/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
  163. package/template/.agents/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
  164. package/template/.agents/skills/supabase-postgres-best-practices/references/_template.md +34 -0
  165. package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  166. package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  167. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  168. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  169. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  170. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  171. package/template/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  172. package/template/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  173. package/template/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  174. package/template/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  175. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  176. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  177. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  178. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  179. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  180. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  181. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  182. package/template/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  183. package/template/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  184. package/template/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
  185. package/template/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  186. package/template/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  187. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
  188. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  189. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  190. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  191. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  192. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  193. package/template/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  194. package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  195. package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -0
  196. package/template/.agents/upstreams.json +34 -12
  197. package/template/.agents/skills/design/impeccable/reference/brand.md +0 -108
  198. package/template/.agents/skills/design/impeccable/reference/codex.md +0 -105
  199. package/template/.agents/skills/design/impeccable/reference/interaction-design.md +0 -189
  200. 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,