@kanso-labs/unplugin-style-dictionary 0.8.0 → 0.10.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,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Kanso
3
+ Copyright (c) 2026 Kanso Labs
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
package/README.md CHANGED
@@ -10,19 +10,23 @@ compile **Style Dictionary** design tokens ahead of your bundler, with automatic
10
10
  watching, rebuilding, and hot reloading (HMR) under Vite's dev server.
11
11
 
12
12
  Built on unplugin, the same core plugin targets **Vite**, **Rolldown**,
13
- **Rollup**, and **Webpack** from a single implementation — useful when a project
14
- has more than one build surface (e.g. Storybook/Vitest on Vite, and a package
15
- build on Rolldown/tsdown) that both need tokens compiled ahead of them.
13
+ **Rollup**, **Rspack**, and **Webpack** from a single implementation — useful
14
+ when a project has more than one build surface (e.g. Storybook/Vitest on Vite,
15
+ and a package build on Rolldown/tsdown) that both need tokens compiled ahead of
16
+ them.
16
17
 
17
18
  ## Features
18
19
 
19
- - **Multi-bundler**: One implementation, four entry points — Vite, Rolldown,
20
- Rollup, and Webpack.
21
- - **Asynchronous builds**: Native support for Style Dictionary v4/v5 async
22
- compilation API.
20
+ - **Multi-bundler**: One implementation, five entry points — Vite, Rolldown,
21
+ Rollup, Rspack, and Webpack.
22
+ - **Asynchronous builds**: Native support for Style Dictionary v5's async
23
+ compilation API. The `style-dictionary` range in
24
+ [`package.json`](package.json)'s `peerDependencies` is the contract — this
25
+ bullet follows it rather than standing beside it.
23
26
  - **Automatic watching**: Reads the `source` and `include` patterns from your
24
- Style Dictionary configurations and watches the files they match. What a
25
- change then triggers depends on the target see
27
+ Style Dictionary configurations and watches the files they match, including a
28
+ token package resolved through `node_modules` in a workspace. What a change
29
+ then triggers depends on the target — see
26
30
  [Watching, per target](#watching-per-target).
27
31
  - **Config flexibility**: Supports file paths (JSON, JSON5, JSONC, JS, MJS, TS),
28
32
  configuration objects, or functions — including registering custom formats at
@@ -47,9 +51,19 @@ build on Rolldown/tsdown) that both need tokens compiled ahead of them.
47
51
  npm install @kanso-labs/unplugin-style-dictionary style-dictionary --save-dev
48
52
  ```
49
53
 
50
- _Note: `style-dictionary` and your bundler (`vite`, `rolldown`, `rollup`, or
51
- `webpack`) are peer dependencies, so you can manage their versions
52
- independently._
54
+ _Note: `style-dictionary` and your bundler (`vite`, `rolldown`, `rollup`,
55
+ `@rspack/core`, or `webpack`) are peer dependencies, so you can manage their
56
+ versions independently._
57
+
58
+ **`style-dictionary` is the one peer that is not optional**, and its range is
59
+ `^5.0.0`. An older major is refused at the install rather than failing later:
60
+
61
+ ```
62
+ npm error code ERESOLVE
63
+ npm error Found: style-dictionary@4.4.0
64
+ npm error Could not resolve dependency:
65
+ npm error peer style-dictionary@"^5.0.0" from @kanso-labs/unplugin-style-dictionary@0.9.0
66
+ ```
53
67
 
54
68
  **This package needs Node 22.12 or newer.** The floor is Style Dictionary v5's,
55
69
  not this plugin's: every 5.x release declares `engines.node >= 22.0.0`, and it
@@ -98,11 +112,12 @@ generated token files exist before the rest of the build consumes them.
98
112
  Under a real `rolldown.watch()`, do not rely on a token edit triggering a
99
113
  rebuild — see [Watching, per target](#watching-per-target).
100
114
 
101
- ### Rollup / Webpack
115
+ ### Rollup / Webpack / Rspack
102
116
 
103
117
  ```typescript
104
118
  import StyleDictionary from '@kanso-labs/unplugin-style-dictionary/rollup'
105
119
  // or: import StyleDictionary from '@kanso-labs/unplugin-style-dictionary/webpack'
120
+ // or: import StyleDictionary from '@kanso-labs/unplugin-style-dictionary/rspack'
106
121
  ```
107
122
 
108
123
  A `webpack.config.js` is often CommonJS rather than ESM. This package ships ESM
@@ -119,6 +134,14 @@ module.exports = {
119
134
  }
120
135
  ```
121
136
 
137
+ **Rspack is webpack's plugin API, and this package treats it as one.** Swap the
138
+ import for `…/rspack` and an `rspack.config.js` reads the same, `.default` hop
139
+ included. Everything below that mentions webpack — where a relative `config` is
140
+ resolved from, where a failed compile is reported, what a `config` function is
141
+ told about the build — holds there too, and `tests/webpack-api.test.ts` runs the
142
+ same cases against both. It installs as `@rspack/core`, which is the peer this
143
+ package declares.
144
+
122
145
  _Note: that path needs Node 20.19+ or 22.12+, the versions that can `require` an
123
146
  ES module. Every Node release still in support clears it. Importing from ESM has
124
147
  no such floor._
@@ -127,6 +150,33 @@ _The subpaths also need a TypeScript `moduleResolution` of `bundler`, `node16`
127
150
  or `nodenext`. The deprecated `node10` cannot resolve them, and TypeScript 6
128
151
  already warns that it stops working in 7._
129
152
 
153
+ ### Finding a Config File
154
+
155
+ With no `config`, the root is searched for `sd.config.json`, `config.json`,
156
+ `sd.config.js` and `sd.config.mjs`, in that order — and the first one that
157
+ **looks like a Style Dictionary configuration** wins. That means declaring at
158
+ least one of `platforms`, `source`, `include` or `tokens`. A candidate that
159
+ fails the check is reported and skipped rather than adopted, so an unrelated
160
+ `config.json` — an extremely common name for something else — no longer gets
161
+ compiled over and added to the watch set. The path that was picked is printed,
162
+ so which configuration a build used is answerable from the console.
163
+
164
+ `config.json` stays in the list because Style Dictionary's own CLI defaults to
165
+ it, so a project relying on that default keeps working.
166
+
167
+ **Two of the four names are modules, and reading a module runs it.** A root
168
+ `sd.config.js` is imported — freshly, on every watch event — and validation
169
+ cannot prevent that, because the check can only look at what the import
170
+ returned. If you name your configuration explicitly, or have none, say so:
171
+
172
+ ```typescript
173
+ StyleDictionary({ config: false })
174
+ ```
175
+
176
+ That turns discovery off entirely: nothing is looked for, nothing is watched,
177
+ and nothing is compiled. A configuration you name yourself is never
178
+ second-guessed by the check above — it goes straight to Style Dictionary.
179
+
130
180
  ### Config File Formats
131
181
 
132
182
  A `config` path may be `.json`, `.json5`, `.jsonc`, `.js`, `.mjs` or `.ts`. The
@@ -262,6 +312,23 @@ export default defineConfig({
262
312
  })
263
313
  ```
264
314
 
315
+ ## Platforms
316
+
317
+ Linux, macOS and Windows. All three run the suite in CI, and Windows is a
318
+ `Test on Windows` job rather than a claim — it was added after two defects
319
+ reached `main` because nothing ran there.
320
+
321
+ Both are fixed. The atomic write used to fail under the condition it exists for:
322
+ with a reader holding the destination open, Windows refuses the rename, so the
323
+ compile failed rather than the read being protected. The rename now backs off on
324
+ `EPERM` and `EBUSY` and retries, bounded, so a rename that genuinely cannot
325
+ succeed still fails rather than hanging a dev server.
326
+
327
+ And a token package resolved through `node_modules` used to build once and never
328
+ rebuild. Vite's watcher cannot be made to deliver those events on Windows — no
329
+ spelling of the ignore-list negation reaches them, and `watcher.add()` does not
330
+ either — so the plugin watches those directories itself, on every platform.
331
+
265
332
  ## Watching, per target
266
333
 
267
334
  Every target compiles tokens before the build that consumes them. What a later
@@ -273,6 +340,7 @@ on what the host bundler does with the watch list the plugin registers.
273
340
  | **Vite** | yes | yes, under the dev server | yes |
274
341
  | **Rollup** | yes | yes, under `rollup --watch` | yes |
275
342
  | **Webpack** | yes | yes, under `webpack --watch` | yes |
343
+ | **Rspack** | yes | yes, under `rspack --watch` | yes |
276
344
  | **Rolldown** | yes | platform-dependent — see below | yes |
277
345
 
278
346
  Patterns and literal paths behave the same way wherever rebuilds happen at all.
@@ -287,12 +355,78 @@ reaches no hook, while on a Linux runner the same edit reaches a rebuild. Treat
287
355
  rolldown's watch mode as compiling once and not tracking tokens, and reach for a
288
356
  one-shot build or another target if you need rebuild-on-change.
289
357
 
358
+ **A token package resolved through `node_modules` is watched too, and that took
359
+ a fix.** In a workspace — `app/node_modules/@acme/tokens` symlinked to
360
+ `packages/tokens` — Vite's dev-server watcher is built with `**/node_modules/**`
361
+ already in its ignore list, and the entries a consumer adds are appended after
362
+ it rather than subtracted from it. So the first build was correct and no edit
363
+ ever rebuilt, with nothing printed to say so. The plugin now un-ignores exactly
364
+ the files it registers, by name, on Vite 6, 7 and 8. The rest of `node_modules`
365
+ stays ignored, which matters: handing the whole dependency tree to the watcher
366
+ is thousands of files no token build reads.
367
+
368
+ Nothing is needed from you for that. If you had worked around it with a
369
+ `server.watch.ignored` negation of your own, it still works — the plugin appends
370
+ to your list rather than replacing it.
371
+
290
372
  "Safe from rebuild loops" is worth stating because consuming code imports the
291
373
  generated file, so every regenerate is itself a change the host reacts to. The
292
374
  plugin subtracts its own output from the watch list, skips recompiling when a
293
375
  watch rebuild re-enters `buildStart`, and skips the write entirely when a
294
376
  rebuild renders bytes identical to what is already on disk.
295
377
 
378
+ ## Building Only Some Platforms
379
+
380
+ Every rebuild used to compile every platform, so a dev server serving a web app
381
+ paid for Objective-C headers, Android XML and Dart classes on every token save.
382
+ `platforms` narrows it:
383
+
384
+ ```typescript
385
+ StyleDictionary({
386
+ config: 'sd.config.json',
387
+ // Build everything once, then rebuild only css while serving.
388
+ platforms: { watch: ['css'] },
389
+ })
390
+ ```
391
+
392
+ Measured on a six-platform configuration (css, scss, js, ios, android, flutter),
393
+ with the timer around the build call alone:
394
+
395
+ | Tokens | All platforms | css only | Saved |
396
+ | ------ | ------------- | -------- | ------ |
397
+ | 500 | 12 ms | 1 ms | 10 ms |
398
+ | 3,000 | 36 ms | 2 ms | 34 ms |
399
+ | 10,000 | 103 ms | 4 ms | 99 ms |
400
+ | 30,000 | 330 ms | 12 ms | 319 ms |
401
+
402
+ An array — `platforms: ['css']` — applies to every build. The object form splits
403
+ the first compile from the watch rebuilds, and an omitted key means every
404
+ platform. A name the configuration does not define is an error, matching Style
405
+ Dictionary's own CLI.
406
+
407
+ **Unselected platforms keep whatever they last wrote.** Nothing removes or
408
+ refreshes their files, so scoping the `build` half ships stale output for the
409
+ rest. Scope `watch` unless that is what you want.
410
+
411
+ ## Generated Output Is Disposable
412
+
413
+ Nothing removes a generated file, ever. Drop a `files` entry from a
414
+ configuration, remove a whole platform, or move a `buildPath`, and the old
415
+ output stays where it was — still resolving, still importable, still carrying
416
+ its old token values, and in a package build still published, with nothing in
417
+ the log mentioning it.
418
+
419
+ So treat the build directory as disposable: delete it when a configuration
420
+ changes shape, and keep it out of version control and out of any directory
421
+ holding hand-written files.
422
+
423
+ There is deliberately no `clean` option. Style Dictionary's
424
+ `cleanAllPlatforms()` does not solve this — it removes the destinations the
425
+ _current_ configuration declares, which are exactly the files that are not
426
+ orphans, and it removes the `buildPath` directory along with them. Measured:
427
+ after dropping `legacy.scss` from a configuration, a clean run left
428
+ `legacy.scss` standing and deleted `vars.css`, the file still in use.
429
+
296
430
  ## Skipping a Build That Would Change Nothing
297
431
 
298
432
  A configuration whose output is already newer than everything it reads is not
@@ -340,8 +474,8 @@ and does not cache anything for later.
340
474
  Two bases, and which one applies depends on whose path it is.
341
475
 
342
476
  **The `config` option is the plugin's**, so a relative path is looked up under
343
- the host's root: Vite's `root`, webpack's `context`, and the working directory
344
- for rollup and rolldown, which report none. `root` overrides that.
477
+ the host's root: Vite's `root`, webpack's or rspack's `context`, and the working
478
+ directory for rollup and rolldown, which report none. `root` overrides that.
345
479
 
346
480
  **Everything inside a Style Dictionary configuration is Style Dictionary's**, so
347
481
  `source`, `include` and `buildPath` are resolved against the working directory.
@@ -399,13 +533,20 @@ own way:
399
533
  | ------------------ | ------------------------ | ---------------------------------- |
400
534
  | Vite | `config.logger.info` | `config.logger.error` |
401
535
  | Rollup, Rolldown | the plugin context's log | the context's warning channel |
402
- | Webpack | the console | `compilation.warnings`, so `stats` |
536
+ | Webpack, Rspack | the console | `compilation.warnings`, so `stats` |
403
537
  | No host (one-shot) | the console | the console |
404
538
 
405
539
  That is what makes a `customLogger` and `clearScreen` work under Vite, and what
406
540
  puts a failed compile into `stats.toJson()` under webpack — where it reaches CI
407
541
  annotations and anything else reading the build's own output.
408
542
 
543
+ **Rspack reformats what it is given, and webpack does not.** The plugin hands
544
+ both the same plain text; rspack wraps every diagnostic in its own frame — a `⚠`
545
+ marker and a `│` gutter — and colours that frame whenever it thinks colour is
546
+ wanted, which setting `CI` is enough to do. So a warning read out of rspack's
547
+ `stats` can carry ANSI escapes that came from rspack rather than from here.
548
+ Strip them before feeding that text to anything that expects plain output.
549
+
409
550
  **A failure is reported as a warning, never on the host's error channel.**
410
551
  Rollup's `this.error` aborts the bundle, so reporting a failure through it would
411
552
  stop every build that reported one — taking the decision `failOnError` exists to
@@ -443,6 +584,19 @@ StyleDictionary({
443
584
  })
444
585
  ```
445
586
 
587
+ **A configuration that resolves no tokens is a failure, not an empty build.**
588
+ Style Dictionary writes the destination with nothing in it and reports success,
589
+ so a token file deleted mid-session used to take the generated output down with
590
+ it, and a `source` matching nothing shipped an empty stylesheet from a build
591
+ that exited 0. The check runs before the compile, so the previous good output is
592
+ still on disk when it fires and nothing is overwritten. The message names the
593
+ configuration and the patterns that matched no files.
594
+
595
+ This is about the resolved token set, not about the patterns: a configuration
596
+ that supplies `tokens` inline and declares no `source` at all is valid and
597
+ builds. And it goes through `failOnError` like any other compile failure, so
598
+ `failOnError: false` reports it and carries on.
599
+
446
600
  A failure is always reported by the plugin, whatever `failOnError` and `silent`
447
601
  are set to — see [Where the messages go](#where-the-messages-go) for which
448
602
  channel it arrives on, and for the one thing that can still suppress it.
@@ -497,14 +651,14 @@ the host stops, the other is that a build went wrong.
497
651
 
498
652
  ## Public API
499
653
 
500
- Small on purpose. Four bundler entry points, one root entry, and two types.
654
+ Small on purpose. Five bundler entry points, one root entry, and two types.
501
655
 
502
- | Import | What it is |
503
- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
504
- | `…/vite`, `…/rolldown`, `…/rollup`, `…/webpack` | Default export: the plugin for that bundler. Call it with the options below. |
505
- | `…` (the root) | Default export, also named `unplugin`: the unplugin instance, carrying `.vite`, `.rolldown`, `.rollup` and `.webpack`. |
506
- | `UnpluginStyleDictionaryOptions` | The options type, exported from every entry above. |
507
- | `StyleDictionaryConfigContext` | What the function form of `config` is handed, exported from every entry above. |
656
+ | Import | What it is |
657
+ | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
658
+ | `…/vite`, `…/rolldown`, `…/rollup`, `…/rspack`, `…/webpack` | Default export: the plugin for that bundler. Call it with the options below. |
659
+ | `…` (the root) | Default export, also named `unplugin`: the unplugin instance, carrying `.vite`, `.rolldown`, `.rollup`, `.rspack` and `.webpack`. |
660
+ | `UnpluginStyleDictionaryOptions` | The options type, exported from every entry above. |
661
+ | `StyleDictionaryConfigContext` | What the function form of `config` is handed, exported from every entry above. |
508
662
 
509
663
  Anything not in that table is internal, whatever a build output happens to
510
664
  contain. In particular the watch filter and the raw unplugin factory are not
@@ -524,7 +678,7 @@ const plugin = styleDictionary.rollup({ config: 'sd.config.json' })
524
678
 
525
679
  ## Options Reference
526
680
 
527
- ```typescript
681
+ ````typescript
528
682
  /**
529
683
  * Options for the Style Dictionary unplugin factory, shared across all bundler
530
684
  * targets (Vite, Rolldown, Rollup, Webpack).
@@ -651,7 +805,19 @@ export interface UnpluginStyleDictionaryOptions {
651
805
  *
652
806
  * If not provided, the root directory is searched for 'sd.config.json',
653
807
  * 'config.json', 'sd.config.js' and 'sd.config.mjs', in that order. The
654
- * first one that exists wins, and the rest are not looked at.
808
+ * first one that *looks like a Style Dictionary configuration* wins it has
809
+ * to declare at least one of `platforms`, `source`, `include` or `tokens` —
810
+ * and the path it picked is announced, so which file a build used is
811
+ * answerable from the console. A candidate that fails that check is reported
812
+ * and skipped rather than adopted, because `config.json` is an extremely
813
+ * common name for something else entirely.
814
+ *
815
+ * **`false` turns discovery off.** Two of the four names are modules rather
816
+ * than data, and reading a module means running it: a `sd.config.js` in the
817
+ * root is imported, freshly, on every watch event. Validation cannot prevent
818
+ * that, because the check can only look at what the import returned — so a
819
+ * project that names its configuration explicitly, or has none, should say
820
+ * `config: false` rather than rely on there being nothing to find.
655
821
  */
656
822
  config?:
657
823
  | ((
@@ -659,6 +825,7 @@ export interface UnpluginStyleDictionaryOptions {
659
825
  ) => Config | Config[] | Promise<Config | Config[]>)
660
826
  | Config
661
827
  | Config[]
828
+ | false
662
829
  | string
663
830
  | string[]
664
831
 
@@ -780,6 +947,48 @@ export interface UnpluginStyleDictionaryOptions {
780
947
  */
781
948
  onBuildStart?: () => Promise<void> | void
782
949
 
950
+ /**
951
+ * Which platforms to build, by the names the configuration defines.
952
+ *
953
+ * Every rebuild used to compile every platform. Measured on a six-platform
954
+ * configuration (css, scss, js, ios, android, flutter), with the timer around
955
+ * the build call alone:
956
+ *
957
+ * ```
958
+ * tokens all platforms css only saved
959
+ * 500 12 ms 1 ms 10 ms
960
+ * 3000 36 ms 2 ms 34 ms
961
+ * 10000 103 ms 4 ms 99 ms
962
+ * 30000 330 ms 12 ms 319 ms
963
+ * ```
964
+ *
965
+ * So a dev server serving a web app paid for Objective-C headers, Android
966
+ * XML and Dart classes on every token save, and the cost grows with the
967
+ * token count.
968
+ *
969
+ * Two shapes. An array selects the same platforms for every build. An object
970
+ * splits the first compile from the watch rebuilds, which is the common
971
+ * want — build everything once, then rebuild only what the page uses:
972
+ *
973
+ * ```typescript
974
+ * platforms: ['css']
975
+ * platforms: { watch: ['css'] }
976
+ * ```
977
+ *
978
+ * An omitted key means every platform, so `{ watch: ['css'] }` builds all of
979
+ * them once and then only css. A name the configuration does not define is an
980
+ * error, matching Style Dictionary's own CLI — "Must be defined in the
981
+ * config".
982
+ *
983
+ * **Unselected platforms keep whatever they last wrote.** Their files are not
984
+ * removed and not refreshed, so a one-shot build that scopes platforms ships
985
+ * stale output for the rest. Scope the watch half rather than the build half
986
+ * unless that is what you want.
987
+ *
988
+ * @default undefined, which builds every platform
989
+ */
990
+ platforms?: string[] | { build?: string[]; watch?: string[] }
991
+
783
992
  /**
784
993
  * Whether the table of generated files and their sizes is produced.
785
994
  *
@@ -843,7 +1052,7 @@ export interface UnpluginStyleDictionaryOptions {
843
1052
  */
844
1053
  watch?: string | string[]
845
1054
  }
846
- ```
1055
+ ````
847
1056
 
848
1057
  ## Migrating from `vite-plugin-style-dictionary`
849
1058
 
@@ -864,6 +1073,68 @@ plugin. As of this unplugin-based rewrite:
864
1073
  - Behavior under Vite is unchanged: the same `buildStart`-time compilation and
865
1074
  dev-server watch/rebuild logic as before.
866
1075
 
1076
+ ## Development
1077
+
1078
+ Fork, then clone the repository:
1079
+
1080
+ ```shell
1081
+ git clone https://github.com/your-username/unplugin-style-dictionary.git
1082
+ ```
1083
+
1084
+ Install with the Node version in [`.tool-versions`](.tool-versions). CI resolves
1085
+ it from that file, and an older npm rewrites `package-lock.json` as it installs.
1086
+ If `node --version` disagrees:
1087
+
1088
+ ```shell
1089
+ mise exec node@"$(awk '/^nodejs/{print $2}' .tool-versions)" -- npm install
1090
+ ```
1091
+
1092
+ ### The four commands
1093
+
1094
+ These are what CI runs, and between them they are the whole gate:
1095
+
1096
+ ```shell
1097
+ npm run lint # oxlint, then ESLint, then oxfmt --check
1098
+ npm run build # tsc -b, then tsdown into dist/
1099
+ npm test # vitest, one run, no watch
1100
+ npm run package:check # publint, attw, then scripts/check-package.mjs
1101
+ ```
1102
+
1103
+ `npm run package:check` reads `dist/`, so it needs a build first.
1104
+ `npm run peers:check` is the slower one that packs the tarball and builds it
1105
+ against both ends of every declared peer range; `Build` runs it, and it is worth
1106
+ running locally when you touch the exports map or the peer declarations.
1107
+
1108
+ **oxfmt formats this repository, not Prettier**, and it covers Markdown, JSON
1109
+ and YAML as well as TypeScript. `npm run lint -- --fix` will not reformat
1110
+ anything — reach for `npm run format`.
1111
+
1112
+ ### Tests
1113
+
1114
+ `tests/` drives real bundlers against real files in temporary directories rather
1115
+ than mocking Style Dictionary: a real Vite dev server, a real `rollup.watch()`,
1116
+ a real `webpack()` compile, a real `rspack()` compile, and all five targets
1117
+ through their own entry points. A new hook needs a matching caller in
1118
+ `tests/index.test.ts` — calling one bare leaves `this` undefined and the failure
1119
+ reads as a plugin bug.
1120
+
1121
+ The suite prints nothing. If you add a test that provokes the plugin's failure
1122
+ report, spy on `console.error`, **assert the message is there**, and restore in
1123
+ a `finally`. You can check the whole suite is quiet with:
1124
+
1125
+ ```shell
1126
+ npx vitest run --reporter=verbose 2>&1 | grep -E '^std(out|err) \|'
1127
+ ```
1128
+
1129
+ That should print nothing.
1130
+
1131
+ [`AGENTS.md`](AGENTS.md) carries the conventions, the traps, and the reasoning
1132
+ behind both. It is written for coding agents and is equally the fullest thing a
1133
+ human contributor can read.
1134
+
1135
+ Contribution guidelines for the organization are in
1136
+ [`kanso-labs/.github`](https://github.com/kanso-labs/.github).
1137
+
867
1138
  ## License
868
1139
 
869
1140
  MIT
@@ -875,7 +1146,7 @@ MIT
875
1146
  [codecov]: https://codecov.io/gh/kanso-labs/unplugin-style-dictionary
876
1147
  [coverage-shield]:
877
1148
  https://img.shields.io/codecov/c/github/kanso-labs/unplugin-style-dictionary?label=Coverage
878
- [license]: ./LICENSE
1149
+ [license]: ./LICENSE.md
879
1150
  [license-shield]:
880
1151
  https://img.shields.io/github/license/kanso-labs/unplugin-style-dictionary
881
1152
  [npm]: https://www.npmjs.com/package/@kanso-labs/unplugin-style-dictionary