Repository navigation
onsystem@0.4.0
Minor Changes
-
#45
d43bb12Thanks @dgesteves! -checklints what you ship, andcheck_uianswers in a size an agent can read.- In folders and globs,
checkleaves out tests and stories (*.test.tsx,*.spec.tsx,*.stories.tsx;--include-testschecks them), what the config'sexcludelists, and what git ignores. A file named outright is always checked. The summary line counts what was left out. - Files that render an image or an email are not checked, since design tokens and classes don't apply there: those importing
next/og,@vercel/og,satorior@react-email/*, Next.jsopengraph-image,twitter-image,iconandapple-iconroutes, and files in anogfolder.--format jsonmarks themskipped, andcheck_uisays why. check_uireturns at most 50 diagnostics, errors first (limitsets another number, up to 1,000), with the total per rule and how many were left out, in both the text andstructuredContent(omitted,byRule). A 3,000-line file used to come back as about 500,000 tokens.
- In folders and globs,
-
#43
2cfaee2Thanks @dgesteves! - Color fixes keep the color's meaning. A pale arbitrary value or literal is a hue, not a gray: its chroma is judged against its lightness, sobg-[#fef2f2]is no longer fixed tobg-muted. Hues must be within 15° (was 30°), and status colors (destructive,warning,success,infoand their foregrounds) are only offered for nearly the same hue and never for a gray, so a brand-orange button is no longer fixed tobg-destructivewith a hint to usevariant="destructive". Token families that at most one design-system file uses (syntax-highlighting colors, a chart palette, one screen's background) are suggested only there, likesidebar-*andchart-*. Tokens built on Tailwind's palette (--primary: var(--color-neutral-800), as in coss/ui) now resolve, sono-hardcoded-colorruns on those projects, where it used to report "No color tokens found". -
#64
5c6b1c6Thanks @dgesteves! - Zero config finds design systems it used to miss, and checks each import against the component it leads to.- Built packages. An
exportstarget indist/(orbuild/,lib/) is read from its source: through theentryof a tsup, tsdown or Vite config, read as text and never run, else the same path undersrc/, and.fromsrc/index.*. Dub's@dub/ui, built by tsup, used to give "No design system found"; it now gives 430 components. - Barrels. An export that leads to an
index.tsstands for the.tsxfiles it re-exports, throughexport *,export { … } fromand imported bindings exported again, within the package.@calcom/uigoes from 4 components to 110. - Several design systems. Workspace design-system packages are added to what
components.jsonfinds instead of being skipped for it: openstatus's dashboard gets@openstatus/uinext to its owncomponents/ui, and Cal.com's app@calcom/uinext to@coss/ui. The one the app's code imports most is the primary: its components win a shared name and are the ones suggested for native elements.inspectshows each with its import count. - Imports. A renamed re-export (
export { Table as TableNew }) is checked against the component declared under its original name, and a module namespace (export * as RadioAreaGroup) is no longer taken for a component. A component file a package does not export is left unchecked rather than called invented. A generic component whose props depend on its type argument (Cal.com'sSkeleton<T>) has open props. Members written as property accesses (Object.assign(Tooltip, { Root: TooltipPrimitive.Root })) are extracted. prefer-design-system-componentleaves alone theasChildchild of a component with classes of its own (<SidebarMenuButton asChild><button>), a bare<button />,<a />or<label />passed to render into, an element styled by acvaortvdefinition in its own file, and an<input>whose every attribute is spread in (react-dropzone's hidden file input).checkleaves out files in__tests__,__stories__and__mocks__folders by default, as it does*.test.tsxand*.stories.tsx.
- Built packages. An
-
#46
2252ef0Thanks @dgesteves! - The Claude Code hook speaks up when it can't check, and stays quiet where there is nothing to check against.- When the CLI can't run (npx can't fetch it because of a min-release-age policy, a private registry or no network, or the config is broken), the hook says so once per session and project, on stderr and as a message shown to you, with the reason and what to do. It never blocks the edit, and Claude is not asked to fix it. It used to exit silently, so edits looked checked when they weren't.
check --quiet-without-design-system, which the hook uses, now stays quiet in any project without design-system components or a config. A few custom properties are not a design system: in an app styled with CSS-in-JS, such as Twenty, three stray tokens made the hook block Claude on findings with nothing to compare against.
-
#65
22fe3abThanks @dgesteves! -inspect --explainsays why, andinitwrites the config.inspect --explainprints every candidate zero config looked at (components.json, the package'sexports, each dependency named like a design system, a flatsrc/) and why it was taken or turned down (the ui alias ~/ui resolves through neither tsconfig paths nor a workspace package,not a workspace package: an installed package is compiled), the resolved config (globs, tokens, docs, import paths, rules and overrides), tokens whose value reads a custom property no token file defines, and components whose props did not resolve, saying when that is because the dependencies are not installed. At a monorepo root it says why the root is read as one, then explains each project.onsystem initwritesonsystem.config.jsonfrom what zero config found, or from the answers to up to three questions (where the components are, where the tokens are, how apps import them) when it found nothing. It then runscheck .once without failing, prints the findings by rule, and offers to record them inonsystem.baseline.jsonfor an existing codebase.--yeswrites without asking and records the baseline;--componentsand--tokensanswer for it in a script; an existing config is left alone without--force.
-
#66
e8f965dThanks @dgesteves! - The MCP server speaks the 2026-07-28 protocol revision as well as the 2025 one, on the same stdio command.- Both eras. A client's first message picks the era:
server/discoveror a request with the 2026-07-28_metagets the stateless revision, and aninitializerequest gets the 2025 handshake, served as before. On 2026-07-28,server/discoverreturns the supported versions, capabilities,serverInfoand instructions; every result carriesresultType; list results carryttlMsandcacheScope(the tools, prompts and resource templates for an hour, the component resources not at all, all private);tools/listkeeps one order; and an unsupported version gets-32022naming the supported ones. - Finding the project.
--root, a config file in the working directory, or the working directory itself when it holds a project (apackage.jsonorcomponents.json) come first. MCP roots, deprecated in 2026-07-28, are the fallback, for a 2025-era client that starts the server outside any project. - SDK. The server moves from
@modelcontextprotocol/sdk1.x to@modelcontextprotocol/server2.x, the SDK line that implements 2026-07-28. Its install no longer brings the SDK's HTTP stack (Express, Hono).check_ui's output schema describes a fix'srangeas an array of two integers rather than a tuple, which clients that validate structured content as draft-07 rejected. - For library users:
createServerreturns the v2 SDK'sMcpServer, andserveStdioresolves to a handle withclose()rather than to theMcpServer.
- Both eras. A client's first message picks the era:
-
#63
de818cfThanks @dgesteves! - onsystem works from a monorepo root, where Claude Code and Cursor sessions usually start. It used to find nothing there.- Projects. At a workspace root (pnpm, npm, Yarn and Bun workspaces, Lerna, Nx; Turborepo through its package manager's workspaces) with no design system of its own, every workspace package goes through zero config or its own config, and each one with components is a project: the apps that use a design system and the design-system packages themselves.
inspectlists each project with what it found, and the packages without one. check .at the root checks each file against its own project's design system: the nearest folder with a config, acomponents.json, apackage.jsonor aproject.json, the folder the Claude Code hook runs the check from. It reports whatcheck .in each of those folders reports, file for file, skips each folder's own design-system files, and keeps one baseline at the root.--require-design-systemthere fails when no package has components and color tokens. A root config that does not setcomponentsapplies to every project without a config of its own, with its globs relative to the root.- MCP tools at the root.
list_components,get_component,search_componentsandget_tokenstake an optionalpath(the file being edited, or a project folder) and answer for that project;check_uitakes the project frompath, or fromfilenamefor code. Without a path they list every project together, each component and token tagged with its package and the projects that use it, and a name two packages share (Buttonin@calcom/uiand@coss/ui) comes back as a list with the path that picks each. - Imports are followed to their declaration. A component imported through barrels (
export { Button } from "./Button",export *) is checked against the one declared where the import leads, so with two design systems in one project,<Button>from@calcom/uiis no longer checked against@coss/ui'sButton, and Dub'sCardList.Card(anObject.assignmember in a barrel) is no longer reported as missing. prefer-design-system-componentleaves alone elements kept from the user:hidden,aria-hidden, or out of the tab order and invisible, like the input that carries a custom select'srequired.- The Claude Code hook also treats a folder with an Nx
project.jsonas a project.
- Projects. At a workspace root (pnpm, npm, Yarn and Bun workspaces, Lerna, Nx; Turborepo through its package manager's workspaces) with no design system of its own, every workspace package goes through zero config or its own config, and each one with components is a project: the apps that use a design system and the design-system packages themselves.
-
#50
4b869b0Thanks @dgesteves! -@dgesteves/design-system-mcpis nowonsystem, and this is its first release under that name. Everything that had the old name moves, and what you already have keeps working until you rename it (migration guide):- Package and command:
npm install --save-dev onsystem,npx onsystem check .. The old package gets no new releases. - Config file:
onsystem.config.json(or.ts,.mts,.js,.mjs). Adesign-system-mcp.config.*is still read, with a notice that says how to rename it; the schema is athttps://unpkg.com/onsystem/schema.json, and a code config importsdefineConfigfromonsystem. - Baseline:
onsystem.baseline.json. Adesign-system-mcp.baseline.jsonis still read and updated where it is, with the same notice. - MCP: the server calls itself
onsystem, and the documented client configs useonsystemas the server's key, so tools appear asmcp__onsystem__check_uiand the like. The MCP Registry entry isio.github.dgesteves/onsystem. - Claude Code plugin: the plugin is
onsystemin the samedgestevesmarketplace, and it runs an exact version of the package (onsystem@<version>) rather than a range. Replace the old one with/plugin uninstall design-system@dgesteves,/plugin marketplace update dgestevesand/plugin install onsystem@dgesteves. The skill is/onsystem:onsystem. - Smaller things: the cache moved to
node_modules/.cache/onsystem, log lines start with[onsystem], GitHub annotations are titledonsystem, and the hook's test override isONSYSTEM_BIN(DESIGN_SYSTEM_MCP_BINstill works, with a notice).
- Package and command:
-
#44
2db9195Thanks @dgesteves! - Fewer false positives from the component rules, measured on the real-world corpus:prefer-design-system-componentsuggests a component for a native element only when the component is named for it (Button,Link,Input,TextField,Label,Select,Textarea,Image,Separator…), or when it renders the element and needs nothing the element does not (no required props of its own, and the element's attributes).<button>no longer becomes<DataTableColumnHeader>,<label>no longer becomes<FileUpload>, and<img>no longer becomes<Avatar>. Inside a component of the same name (a design system's ownTablearound a<table>), the element is left alone.<hr>now suggests aSeparator.icon-button-accessible-namecounts a child as an icon only when it looks like one: imported from an icon library or an icons module, or named like an icon (XIcon,Trash2). A child with text in its props (<IconMenu text="Export">) or an unknown component makes it skip. A conditionalrender={cond ? undefined : <button />}is followed, a rendered button's own children count, and a story'sargs.renderis skipped.no-unknown-componentno longer calls a component invented when it comes from a module the model left out (an excluded folder, a barrel that re-exports one, a package built todist/).checkandcheck_uisay once which components were not checked, and--format jsonlists them underunchecked. Members ofObject.assign(Root, { Title, Close })are linked even when they are not exported, a same-named component from another file is no longer taken for the model's, and a tag a local declaration shadows is left alone.- cva/tv variants come from the definitions on the component (
VariantProps<typeof x>, or the className of the element it returns). A definition used further in no longer adds classes to its variants: documenso's Buttonsizeno longer includes its spinner'sh-5 w-5.
-
#47
ad39127Thanks @dgesteves! - Findings can be suppressed where they are intended.// onsystem-disable-next-line <rule>[, <rule>]covers the next line ({/* … */}in JSX),// onsystem-disable-line <rule>its own line, and/* onsystem-disable [<rule>] */the rest of the file; text after--explains why. A misspelled rule name is reported as asuppressionwarning with the closest one. In the config,overridesset rules for the files that match globs, such as[{ "files": ["app/legacy/**"], "rules": { "no-hardcoded-color": "warn" } }], validated likerules;checkkeeps baseline entries of rules an override turns off.
Patch Changes
-
#51
56167baThanks @dgesteves! - The README on npm and GitHub is one screen: what onsystem is for, what it checks with its measured false-positive rate, a quickstart for the Claude Code plugin, MCP clients and CI, and the benchmark. Client configs, the config and rules references, how it works and a new troubleshooting page (nothing found, monorepo roots, components with 0 props, min-release-age policies) are on the website and indocs/. The package description and keywords say what it does now: it keeps coding agents on your design system at the edit and in CI, with the MCP server alongside. -
#42
68a8ff3Thanks @dgesteves! - Every change to the linter is now checked against a real-world corpus: 10 public repositories pinned by commit (Documenso, Dub, Cal.com, openstatus, Twenty, midday, Vercel's chatbot, shadcn/ui's website, Next.js SaaS Starter and React Aria's Tailwind starter), with findings counted per rule and a hand-labelled sample whose false-positive rate is published with each release. Nothing changes in the package itself. -
#48
0293e92Thanks @dgesteves! - Small fixes that make the package say what is true:enginesasks for Node.js 20.19 or later, which the CLI and the server run on; a TypeScript config file still needs 22.18, and on an older Node.js it now says so plainly and points to.jsonor.mjs.get_componenthas an output schema, like every other tool, and integer fields no longer carry ±9007199254740991 bounds in the tool definitions.get_tokensin a project without tokens says that none were found and how to configure them, instead of suggesting to call it without filters; with filters that match nothing, it lists the categories there are.isInvalidon a native field suggestsaria-invalid, notonInvalid.
-
#67
faa0b31Thanks @dgesteves! - Checked on Windows and macOS, and as an installed package.- On Windows,
checklinted files git ignores: the pathsgit check-ignorereturned never matched the ones it globbed (backslashes against forward slashes). They are left out now, as on Linux and macOS. - The Claude Code hook names files with forward slashes on Windows too, as the CLI does (
app/page.tsx, notapp\page.tsx). - CI runs the test suite on Windows and macOS as well as Linux, and on all three packs the package, installs the tarball into a new project, and runs the installed CLI (
inspect,check), the MCP server over stdio in both protocol eras, and the hook against it.
- On Windows,