@stridge/noctis-lint
v1.0.0-beta.44
Published
The custom lint plugin enforcing Noctis's hard rules, published so an app consuming `@stridge/noctis` enforces the same rules its components were built under. The plugin's `meta.name` is `stridge`, so rules are referenced as `stridge/<rule>`.
Readme
@stridge/noctis-lint
The custom lint plugin enforcing Noctis's hard rules, published so an app consuming
@stridge/noctis enforces the same rules its components were built under. The plugin's meta.name
is stridge, so rules are referenced as stridge/<rule>.
Rules are plain { meta, create(context) } ESLint-compatible modules. They run on oxlint today
(registered via jsPlugins in the root .oxlintrc.json) and stay portable to ESLint — each rule is
unit-tested with ESLint's RuleTester. Three governance rules read the generated token manifest,
resolved through @stridge/noctis-design-tokens/declarations.json by lib/manifest.js, so regenerate
it with pnpm tokens:gen whenever component declarations change, or these rules go stale.
Consuming it
@stridge/noctis-lint/configs ships two ready-made rule sets so an app never hand-copies rule names:
| Set | Rules | Needs the token manifest |
| --- | --- | --- |
| consumer | semantic-tokens, no-hardcoded-strings, logical-utilities | no |
| authoring | all ten | yes, for three of them |
Both sets are plain { "stridge/<rule>": "error" } maps. They say which rules, never where —
scope them with your own files/overrides.
From a JS/TS config, import and spread the map:
// oxlint.config.js
import { consumer } from "@stridge/noctis-lint/configs";
export default {
jsPlugins: ["@stridge/noctis-lint"],
overrides: [{ files: ["src/**"], rules: { ...consumer } }],
};JSON cannot import, so a .oxlintrc.json spells the same set out:
// .oxlintrc.json — the `consumer` set, written literally
{
"jsPlugins": ["@stridge/noctis-lint"],
"overrides": [
{
"files": ["src/**"],
"rules": {
"stridge/semantic-tokens": "error",
"stridge/no-hardcoded-strings": "error",
"stridge/logical-utilities": "error"
}
}
]
}@stridge/noctis-design-tokens is an optional peer: consumer is self-contained, so it loads in
an app that never installs it. Without the peer the three manifest-reading rules resolve an empty
manifest and report nothing rather than crashing the lint run. A manifest that is present but
malformed throws instead — silently disabling token governance while the run reports green is the
worst available outcome. tests/manifest.test.js asserts the resolution genuinely works where the
peer is installed, so the absent-peer fallback can't rot unnoticed.
Windows caveat: the oxlint JS-plugin host has a known memory issue on Windows. Develop and run the linter on WSL/Linux.
The ten rules
Scoping is set by the overrides in the root .oxlintrc.json. All rules are off in test/spec
files; no-hardcoded-strings + semantic-tokens are also off under apps/docs/**/examples/**.
| Rule | Enforces | Runs on |
| --- | --- | --- |
| semantic-tokens | Use only semantic token utilities/roles — never raw colors. | apps/**, packages/noctis/** |
| no-hardcoded-strings | Route all user-facing JSX copy through next-intl — no hardcoded strings. | apps/**, packages/noctis/** |
| logical-utilities | Use logical, RTL-safe utilities/properties — never physical, direction-locked ones. | apps/**, packages/noctis/** |
| banned-namespaces | Never reference the engine namespace or a canonical token name by literal string — use bridge utilities or minted internals. | apps/**, packages/noctis/** |
| no-raw-motion-z | Use the named motion and stacking scales — never raw duration/ease/z values. | packages/noctis/** |
| declared-tokens | Reference only declared tokens — T1 roles/scales and a component's own internals. | packages/noctis/** |
| minted-identity | Identity values flow through minted internal vars or declared consumes utilities — never raw steps. | packages/noctis/** |
| extracted-styles | Keep class strings in *.styles.ts; orchestration files merge only identifiers. | packages/noctis/src/** |
| slot-identity-order | Spread forwarded props before data-slot, so a part's identity survives render composition. | packages/noctis/src/** (not src/primitives/**) |
| namespace-example | Document a component's namespace root with a worked @example — the part tree an editor hover, an LSP jump, and a .d.ts reader all see. | packages/noctis/src/** |
declared-tokens, minted-identity, and banned-namespaces read the declarations manifest above.
Shared scanning helpers live in lib/ (classify.js — the Tailwind-candidate classifier;
match.js — string/template matchers that visit real string literals, never comments).
Layout
index.js plugin object (meta.name "stridge") + the rules map
rules/ one module per rule
lib/ shared classify/match helpers
stylelint/ stylelint port of minted-identity for the hand-authored component CSS
tests/ ESLint RuleTester suites (Vitest)The repo also ships a stylelint companion (stylelint/minted-identity.js) that ports the minting
line to the precompiled per-component .css (run via pnpm lint:css).
Adding a rule
- Drop a
{ meta, create(context) }module inrules/. - Register it in
index.jsunder therulesmap. - Enable it in the right
overridesblock of the root.oxlintrc.json. - Add a
RuleTestersuite intests/.
pnpm --filter @stridge/noctis-lint test runs the suites.
See /AGENTS.md for the repo's hard rules, stack, and conventions.
