onsystem 0.7.0 → 0.8.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.
@@ -1,4 +1,4 @@
1
- import { h as DesignSystemModel, i as ResolvedConfig } from "./config-1XSTYI44.js";
1
+ import { h as DesignSystemModel, i as ResolvedConfig } from "./config-kYGiD5oW.js";
2
2
  import { MessagePort } from "node:worker_threads";
3
3
  //#region src/eslint/worker.d.ts
4
4
  export interface LoadRequest {
@@ -1,5 +1,5 @@
1
- import { Ct as relativePath, K as DEFAULT_TEST_EXCLUDE, St as matchesGlob, i as componentFiles, q as DEFAULT_TEST_FOLDERS, r as buildModel } from "./design-system-C4bhqLLw.js";
2
- import { r as loadTarget } from "./workspace-Z0tZf_Dv.js";
1
+ import { Q as excludedBy, Tt as relativePath, i as componentFiles, r as buildModel } from "./design-system-Dmcv7eHR.js";
2
+ import { r as loadTarget } from "./workspace-CM1RyqZA.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { parentPort } from "node:worker_threads";
@@ -14,8 +14,6 @@ const warned = /* @__PURE__ */ new Set();
14
14
  /** How long a loaded design system is trusted before its files are looked at again. */
15
15
  const FRESH_MS = 2e3;
16
16
  const targets = /* @__PURE__ */ new Map();
17
- /** The default test and story patterns: the recommended config ignores those files itself. */
18
- const TEST_PATTERNS = /* @__PURE__ */ new Set([DEFAULT_TEST_EXCLUDE, DEFAULT_TEST_FOLDERS]);
19
17
  const projects = /* @__PURE__ */ new Map();
20
18
  function realPath(file) {
21
19
  try {
@@ -65,7 +63,7 @@ async function load(request) {
65
63
  projects.set(config.root, project);
66
64
  }
67
65
  const relative = relativePath(config.root, request.file);
68
- const excluded = config.exclude.find((pattern) => !TEST_PATTERNS.has(pattern) && matchesGlob(relative, pattern));
66
+ const excluded = excludedBy(config, relative, { includeTests: true });
69
67
  if (excluded) return { skip: `excluded by "${excluded}"` };
70
68
  if (!config.includeDesignSystem && project.own.has(realPath(request.file))) return { skip: "one of the design system’s own component files" };
71
69
  const { version } = project;
package/dist/eslint.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Ct as relativePath, G as DEFAULT_SEVERITY, K as DEFAULT_TEST_EXCLUDE, _ as NAME, k as RULES, q as DEFAULT_TEST_FOLDERS, t as DesignSystem, v as VERSION } from "./design-system-C4bhqLLw.js";
1
+ import { G as DEFAULT_SEVERITY, K as DEFAULT_TEST_EXCLUDE, Tt as relativePath, _ as NAME, k as RULES, q as DEFAULT_TEST_FOLDERS, t as DesignSystem, v as VERSION } from "./design-system-Dmcv7eHR.js";
2
2
  import path from "node:path";
3
3
  import { MessageChannel, Worker, receiveMessageOnPort } from "node:worker_threads";
4
4
  //#region src/eslint/bridge.ts
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as TOKEN_CATEGORIES, D as VariantInfo, E as TokenCategory, O as ImportMapping, S as SourceLocation, T as Token, _ as DocSection, a as ResolvedRule, b as PropInfo, c as configJsonSchema, d as CheckResult, f as ComponentDocs, g as Diagnostic, h as DesignSystemModel, i as ResolvedConfig, l as defineConfig, m as CompoundVariantInfo, n as LoadConfigOptions, o as RuleId, p as ComponentInfo, r as RULE_IDS, s as RuleOptions, t as Config, u as loadConfig, v as ExampleInfo, w as TextEdit, x as Severity, y as InheritedProps } from "./config-1XSTYI44.js";
1
+ import { C as TOKEN_CATEGORIES, D as VariantInfo, E as TokenCategory, O as ImportMapping, S as SourceLocation, T as Token, _ as DocSection, a as ResolvedRule, b as PropInfo, c as configJsonSchema, d as CheckResult, f as ComponentDocs, g as Diagnostic, h as DesignSystemModel, i as ResolvedConfig, l as defineConfig, m as CompoundVariantInfo, n as LoadConfigOptions, o as RuleId, p as ComponentInfo, r as RULE_IDS, s as RuleOptions, t as Config, u as loadConfig, v as ExampleInfo, w as TextEdit, x as Severity, y as InheritedProps } from "./config-kYGiD5oW.js";
2
2
  import ts from "typescript";
3
3
  import { Oklch } from "culori";
4
4
  import { McpServer, Transport } from "@modelcontextprotocol/server";
@@ -541,11 +541,24 @@ export declare class DesignSystem {
541
541
  private readonly searchIndex;
542
542
  /**
543
543
  * The components the tools offer: all of them, except a library's (MUI's) where the project
544
- * has its own of that name, a wrapper the team means to be used. The rules still check code
545
- * that imports the library's directly against the library's.
544
+ * has its own of that name, a wrapper the team means to be used, and the helpers in a
545
+ * registry folder's `lib/` (signoff-ui's `Img` and icons). The rules still check code that
546
+ * imports either directly against them.
546
547
  */
547
548
  private readonly offered;
549
+ /** The design systems the rules rank, with the registry folders a config lists. */
550
+ private readonly sources;
548
551
  constructor(model: DesignSystemModel, config: ResolvedConfig);
552
+ /**
553
+ * The shadcn registry a component was copied from (`@signoff-ui`), when its folder is one
554
+ * components.json names: a read-only design system, whose code is the registry's.
555
+ */
556
+ registryOf(component: ComponentInfo): {
557
+ registry: string;
558
+ folder: string;
559
+ } | undefined;
560
+ /** A component in a registry folder's `lib/`: an internal of its items, not one of them. */
561
+ private registryHelper;
549
562
  get root(): string;
550
563
  get components(): ComponentInfo[];
551
564
  get tokens(): Token[];
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { $ as loadConfig, A as TokenIndex, B as parseDoc, I as parseDtcgTokens, L as parseDesignMd, M as mergeTokens, N as toDesignMd, P as toDtcg, R as parseCssTokens, V as extractComponents, X as configJsonSchema, Y as RULE_IDS, Z as defineConfig, a as fingerprint, d as searchComponents, f as applyFixes, g as formatDiagnostics, j as loadTokens, k as RULES, n as DesignSystemHost, o as loadDesignSystem, p as checkSource, r as buildModel, rt as findVariantDefinitions, t as DesignSystem, v as VERSION, z as attachDocs } from "./design-system-C4bhqLLw.js";
2
- import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-BojAQ6JP.js";
1
+ import { A as TokenIndex, B as parseDoc, I as parseDtcgTokens, L as parseDesignMd, M as mergeTokens, N as toDesignMd, P as toDtcg, R as parseCssTokens, V as extractComponents, X as configJsonSchema, Y as RULE_IDS, Z as defineConfig, a as fingerprint, d as searchComponents, et as loadConfig, f as applyFixes, g as formatDiagnostics, it as findVariantDefinitions, j as loadTokens, k as RULES, n as DesignSystemHost, o as loadDesignSystem, p as checkSource, r as buildModel, t as DesignSystem, v as VERSION, z as attachDocs } from "./design-system-Dmcv7eHR.js";
2
+ import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-MTS6QAgo.js";
3
3
  export { DesignSystem, DesignSystemHost, INSTRUCTIONS, RULES, RULE_IDS, TokenIndex, VERSION, applyFixes, attachDocs, buildModel, checkSource, configJsonSchema, createServer, defineConfig, extractComponents, findVariantDefinitions, fingerprint, formatDiagnostics, loadConfig, loadDesignSystem, loadTokens, mergeTokens, parseCssTokens, parseDesignMd, parseDoc, parseDtcgTokens, searchComponents, serveStdio, toDesignMd, toDtcg };
@@ -1,5 +1,5 @@
1
- import { $ as loadConfig, Ct as relativePath, F as TOKEN_CATEGORIES, G as DEFAULT_SEVERITY, H as CONFIG_FILES, J as LEGACY_CONFIG_FILES, Tt as defaultProjectConfig, Y as RULE_IDS, _ as NAME, _t as closest, at as LIBRARY_IDS, bt as unique, d as searchComponents, dt as detectProject, ft as findDesignSystemPackage, it as LIBRARIES, m as uncheckedNotice, n as DesignSystemHost, nt as formatPx, ot as libraryTitle, st as notReadYet, u as buildSearchIndex, v as VERSION, vt as plural, xt as isInside, yt as truncate } from "./design-system-C4bhqLLw.js";
2
- import { n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-Z0tZf_Dv.js";
1
+ import { Ct as isInside, Dt as defaultProjectConfig, F as TOKEN_CATEGORIES, G as DEFAULT_SEVERITY, H as CONFIG_FILES, J as LEGACY_CONFIG_FILES, Q as excludedBy, St as SCRIPT_FILE, Tt as relativePath, Y as RULE_IDS, _ as NAME, at as LIBRARIES, bt as truncate, ct as notReadYet, d as searchComponents, et as loadConfig, ft as detectProject, m as uncheckedNotice, n as DesignSystemHost, ot as LIBRARY_IDS, pt as findDesignSystemPackage, rt as formatPx, st as libraryTitle, u as buildSearchIndex, v as VERSION, vt as closest, xt as unique, yt as plural } from "./design-system-Dmcv7eHR.js";
2
+ import { n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-CM1RyqZA.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -47,7 +47,10 @@ function explainProject(ds, cwd, indent = "") {
47
47
  field("importPath", importPaths(ds));
48
48
  field("libraries", config.libraryIds === void 0 ? `every library onsystem reads (${LIBRARY_IDS.join(", ")})` : config.libraryIds !== false && config.libraryIds.length ? config.libraryIds.join(", ") : `none ("libraries": ${JSON.stringify(config.libraryIds)})`);
49
49
  if (config.tailwindConfig) field("tailwind", display(config.root, config.tailwindConfig));
50
- if (config.designSystems) field("primary", config.designSystems.map((d, i) => `${i ? "then " : ""}${d.source} (${d.imports} imports)`).join("; "));
50
+ if (config.designSystems) {
51
+ const ranked = config.designSystems.filter((d) => !d.registry).length > 1;
52
+ field("primary", config.designSystems.map((d, i) => `${i ? "then " : ""}${d.source}${d.registry ? ", read-only" : ranked ? ` (${d.imports} imports)` : ""}`).join("; "));
53
+ }
51
54
  const changed = RULE_IDS.filter((id) => config.rules[id].severity !== DEFAULT_SEVERITY[id]);
52
55
  field("rules", `${RULE_IDS.map((id) => `${id} ${config.rules[id].severity}`).join(", ")}${changed.length ? ` (changed: ${changed.join(", ")})` : ""}`);
53
56
  if (config.overrides?.length) for (const override of config.overrides) field("override", `${override.files.join(", ")}: ${Object.entries(override.rules).map(([id, rule]) => `${id} ${rule.severity}`).join(", ")}`);
@@ -236,7 +239,7 @@ function renderComponentList(ds, project) {
236
239
  const parts = ds.components.length - roots.length;
237
240
  const lines = [`${plural(roots.length, "component")}${parts ? ` (+${plural(parts, "part")})` : ""}${project ? ` in ${project}` : ""}. Call get_component before using one.`, ""];
238
241
  const own = roots.filter((c) => !c.library);
239
- for (const component of own) lines.push(...listItem(component));
242
+ for (const component of own) lines.push(...listItem(component, registryNote(ds, component)));
240
243
  for (const library of new Set(roots.flatMap((c) => c.library ? [c.library] : []))) {
241
244
  if (own.length) lines.push("");
242
245
  lines.push(`From ${library} (import { Name } from "${library}"):`, "");
@@ -244,6 +247,11 @@ function renderComponentList(ds, project) {
244
247
  }
245
248
  return lines.join("\n");
246
249
  }
250
+ /** ` (from @signoff-ui, read-only)` for a component copied from a shadcn registry. */
251
+ function registryNote(ds, component) {
252
+ const from = ds.registryOf(component);
253
+ return from ? ` (from ${from.registry}, read-only)` : "";
254
+ }
247
255
  function listItem(component, note = "", withImport = true) {
248
256
  const element = component.element ? ` <${component.element}>` : "";
249
257
  const lines = [`${component.name}${element}${note} — ${firstSentence(component.description) || "No description."}`];
@@ -376,6 +384,8 @@ function renderComponent(ds, component, origin) {
376
384
  ].filter(Boolean);
377
385
  lines.push(facts.join(" · "));
378
386
  if (origin) lines.push(`From ${origin.package}, used by ${listOf$1(origin.projects)}.`);
387
+ const registry = ds.registryOf(component);
388
+ if (registry) lines.push(`Installed from the shadcn registry ${registry.registry} into ${registry.folder}: use it as it is. Its code is the registry's, so change it upstream, not here; \`npx shadcn add --overwrite\` replaces the file.`);
379
389
  lines.push("", "## Props");
380
390
  if (!component.props.length && !component.inherits.length) lines.push("No props.");
381
391
  for (const prop of component.props) lines.push(renderProp(prop));
@@ -565,6 +575,10 @@ const MAX_CODE = 1e6;
565
575
  /** Diagnostics check_ui returns by default: a 3,000-line file can have thousands, more than a context holds. */
566
576
  const DEFAULT_LIMIT = 50;
567
577
  const MAX_LIMIT = 1e3;
578
+ /** check_ui's answer for a path or filename that is not JS or TS. */
579
+ function notScript(name) {
580
+ return errorResult(`check_ui checks .tsx, .jsx, .ts and .js files; ${name} is not one, so it was not checked.`);
581
+ }
568
582
  /**
569
583
  * An integer, without the ±9007199254740991 bounds zod adds to the JSON Schema,
570
584
  * which every client would otherwise read in each tool definition.
@@ -999,7 +1013,7 @@ function createServer({ getDesignSystem, getProjects }) {
999
1013
  description: "Lint TSX/JSX against the design system. Reports hardcoded colors, spacing and radius (with the nearest token), native elements that have a design-system component, unknown components, props and variant values, and icon-only buttons without an accessible name. Each diagnostic has line/column, a rule id, a message and a suggested fix. Run it on every snippet or file you write and fix all errors. Pass `code` for unsaved code, or `path` for a file in the project.",
1000
1014
  inputSchema: z.object({
1001
1015
  code: z.string().max(MAX_CODE).optional().describe(`TSX/JSX source to check (up to ${MAX_CODE.toLocaleString("en")} characters).`),
1002
- path: z.string().max(MAX_PATH).optional().describe("Path of a file to check: relative to the project root (the monorepo root, when served from there), or absolute."),
1016
+ path: z.string().max(MAX_PATH).optional().describe("Path of a .tsx, .jsx, .ts or .js file to check: relative to the project root (the monorepo root, when served from there), or absolute."),
1003
1017
  filename: z.string().max(MAX_PATH).optional().describe("Name to report `code` under; a .jsx extension parses it as JSX. At a monorepo root, the path the code will be saved under, which picks the project whose design system checks it. When the server was started outside your project, pass it as an absolute path: the project is found from it."),
1004
1018
  limit: z.number().int().min(1).max(MAX_LIMIT).default(DEFAULT_LIMIT).describe(`Most diagnostics to return, errors first (default ${DEFAULT_LIMIT}). The counts and byRule cover all of them.`)
1005
1019
  }),
@@ -1014,7 +1028,7 @@ function createServer({ getDesignSystem, getProjects }) {
1014
1028
  errors: int(),
1015
1029
  warnings: int()
1016
1030
  })).describe("Every finding per rule, shown or not."),
1017
- skipped: z.string().optional().describe("Why the file was not checked: it renders an image (next/og) or an email."),
1031
+ skipped: z.string().optional().describe("Why the file was not checked: it renders an image (next/og) or an email, or the config's `exclude` leaves it out."),
1018
1032
  unchecked: z.object({
1019
1033
  names: z.array(z.string()),
1020
1034
  modules: z.array(z.string())
@@ -1024,6 +1038,7 @@ function createServer({ getDesignSystem, getProjects }) {
1024
1038
  annotations: READ_ONLY
1025
1039
  }, async ({ code, path: filePath, filename, limit }) => {
1026
1040
  if (code === void 0 && !filePath) return errorResult("Pass either `code` or `path`.");
1041
+ if (code !== void 0 && filename !== void 0 && !SCRIPT_FILE.test(filename)) return notScript(filename);
1027
1042
  const routeBy = code === void 0 ? filePath : filename ?? void 0;
1028
1043
  const set = await getProjects?.(routeBy);
1029
1044
  let ds;
@@ -1048,6 +1063,7 @@ function createServer({ getDesignSystem, getProjects }) {
1048
1063
  const absolute = path.resolve(base, filePath);
1049
1064
  const outside = errorResult(`${filePath} is outside the project root (${base}).`);
1050
1065
  if (!isInside(base, absolute)) return outside;
1066
+ if (!SCRIPT_FILE.test(absolute)) return notScript(filePath);
1051
1067
  try {
1052
1068
  const [root, real] = await Promise.all([fsp.realpath(base), fsp.realpath(absolute)]);
1053
1069
  if (!isInside(root, real)) return outside;
@@ -1071,7 +1087,14 @@ function createServer({ getDesignSystem, getProjects }) {
1071
1087
  file = relativePath(ds.root, absolute);
1072
1088
  shown = relativePath(base, absolute);
1073
1089
  }
1074
- const checked = ds.check(source, file);
1090
+ const excluded = (filename !== void 0 || code === void 0) && !path.isAbsolute(file) ? excludedBy(ds.config, file) : void 0;
1091
+ const checked = excluded ? {
1092
+ file,
1093
+ diagnostics: [],
1094
+ errorCount: 0,
1095
+ warningCount: 0,
1096
+ skipped: `It is left out by "${excluded}" in the config's exclude, as in check, the ESLint plugin and the Claude Code hook.`
1097
+ } : ds.check(source, file);
1075
1098
  const result = file === shown ? checked : {
1076
1099
  ...checked,
1077
1100
  file: shown
@@ -1,4 +1,4 @@
1
- import { $ as loadConfig, Ct as relativePath, H as CONFIG_FILES, J as LEGACY_CONFIG_FILES, Q as hasOwnConfig, et as loadConfigWith, ft as findDesignSystemPackage, gt as workspacePatterns, ht as workspacePackageDirs, i as componentFiles, lt as PackageFinder, mt as isWorkspaceRoot, xt as isInside } from "./design-system-C4bhqLLw.js";
1
+ import { $ as hasOwnConfig, Ct as isInside, H as CONFIG_FILES, J as LEGACY_CONFIG_FILES, Tt as relativePath, _t as workspacePatterns, et as loadConfig, gt as workspacePackageDirs, ht as isWorkspaceRoot, i as componentFiles, pt as findDesignSystemPackage, tt as loadConfigWith, ut as PackageFinder } from "./design-system-Dmcv7eHR.js";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import os from "node:os";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onsystem",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "mcpName": "io.github.dgesteves/onsystem",
5
5
  "description": "Keeps coding agents on your design system: catches palette colors, native elements and APIs your system lacks, at every edit and pull request, with the fix. Set it up once in your design-system package; every app in your monorepo gets it.",
6
6
  "keywords": [
package/schema.json CHANGED
@@ -22,7 +22,7 @@
22
22
  ]
23
23
  },
24
24
  "exclude": {
25
- "description": "Globs to ignore when finding components and when `check` expands folders and globs (stories, tests). Replaces the defaults; node_modules is skipped either way unless a pattern names it.",
25
+ "description": "Globs for files to leave out, relative to the root: `check` in folders and globs, the ESLint plugin, the Claude Code hook and check_ui leave them out, and finding components skips them. A folder (`app/legacy`) covers everything in it. Adds to the defaults (node_modules, .d.ts files, tests and stories); a pattern that starts with `!` takes out the default it names, such as `!**/*.d.ts`.",
26
26
  "type": "array",
27
27
  "items": {
28
28
  "type": "string"