adamantite 0.41.0 → 0.43.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.
- package/README.md +393 -35
- package/dist/index.js +47 -28
- package/dist/presets/analyze.d.ts +6 -2
- package/dist/presets/analyze.js +7 -1
- package/dist/presets/lint/core.d.ts +5 -3
- package/dist/presets/lint/core.js +1 -0
- package/dist/presets/lint/custom.d.ts +60 -0
- package/dist/presets/lint/custom.js +142 -0
- package/dist/presets/lint/effect.d.ts +5 -0
- package/dist/presets/lint/effect.js +106 -0
- package/dist/presets/lint/plugin/imports.d.ts +19 -0
- package/dist/presets/lint/plugin/imports.js +53 -0
- package/dist/presets/lint/plugin/index.d.ts +17 -0
- package/dist/presets/lint/plugin/index.js +20 -0
- package/dist/presets/lint/plugin/options.d.ts +11 -0
- package/dist/presets/lint/plugin/options.js +33 -0
- package/dist/presets/lint/plugin/rules/no-overzealous-destructuring.d.ts +5 -0
- package/dist/presets/lint/plugin/rules/no-overzealous-destructuring.js +92 -0
- package/dist/presets/lint/plugin/rules/no-query-data-in-state.d.ts +5 -0
- package/dist/presets/lint/plugin/rules/no-query-data-in-state.js +85 -0
- package/dist/presets/lint/plugin/rules/no-react-state-hooks.d.ts +10 -0
- package/dist/presets/lint/plugin/rules/no-react-state-hooks.js +128 -0
- package/dist/presets/lint/plugin/rules/query-from-loader.d.ts +5 -0
- package/dist/presets/lint/plugin/rules/query-from-loader.js +110 -0
- package/dist/presets/lint/react-doctor.d.ts +5 -0
- package/dist/presets/lint/react-doctor.js +20 -0
- package/dist/presets/lint/react-strict.d.ts +5 -0
- package/dist/presets/lint/react-strict.js +11 -0
- package/dist/presets/lint/shadcn.d.ts +5 -0
- package/dist/presets/lint/shadcn.js +22 -0
- package/dist/presets/lint/strict.d.ts +5 -0
- package/dist/presets/lint/strict.js +18 -0
- package/dist/presets/lint/tanstack.d.ts +5 -0
- package/dist/presets/lint/tanstack.js +14 -0
- package/dist/presets/lint/vendor/antislop/plugin.d.mts +18 -2
- package/dist/presets/rules/index.d.ts +9 -0
- package/dist/presets/rules/index.js +29 -0
- package/package.json +31 -22
- package/skills/adamantite/SKILL.md +105 -11
package/README.md
CHANGED
|
@@ -34,6 +34,8 @@ and Sherif so humans and coding agents can use the same project workflow.
|
|
|
34
34
|
through an interactive or non-interactive initializer.
|
|
35
35
|
- **Setup maintenance**: Assess managed integrations with `doctor`, update managed
|
|
36
36
|
dependencies, and give humans or agents repair instructions.
|
|
37
|
+
- **Custom rules**: Write project-specific Oxlint rules in `.adamantite/rules/`, one rule
|
|
38
|
+
for each file, and enable all of them with one line.
|
|
37
39
|
- **Workspace checks**: Find unused code with Knip and dependency inconsistencies with
|
|
38
40
|
Sherif.
|
|
39
41
|
- **Agent guidance**: Add a managed Adamantite section to `AGENTS.md`.
|
|
@@ -86,8 +88,9 @@ npx adamantite init \
|
|
|
86
88
|
Available setup values:
|
|
87
89
|
|
|
88
90
|
- Scripts: `check`, `fix`, and `analyze`. In a detected monorepo, `analyze` also installs
|
|
89
|
-
Sherif.
|
|
90
|
-
- Presets: `react`, `
|
|
91
|
+
Sherif.
|
|
92
|
+
- Presets: `react`, `react-strict`, `react-doctor`, `tanstack`, `nextjs`, `vue`, `effect`,
|
|
93
|
+
`jest`, `vitest`, `node`, `strict`, `antislop`, and `shadcn`. `react-doctor` requires `react`.
|
|
91
94
|
- Editors: `vscode` and `zed`.
|
|
92
95
|
|
|
93
96
|
Presets and TypeScript require the `check` or `fix` script. Editor extension installation
|
|
@@ -118,6 +121,14 @@ adamantite check
|
|
|
118
121
|
adamantite check src
|
|
119
122
|
```
|
|
120
123
|
|
|
124
|
+
Use `--only` to run one stage. Arguments after `--` go to that stage, and to Oxlint when
|
|
125
|
+
both run:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
adamantite check --only lint
|
|
129
|
+
adamantite check --only format
|
|
130
|
+
```
|
|
131
|
+
|
|
121
132
|
### `adamantite fix`
|
|
122
133
|
|
|
123
134
|
Apply safe Oxlint fixes, then format the files with Oxfmt. Suggested and dangerous fixes require explicit flags:
|
|
@@ -129,10 +140,13 @@ adamantite fix --dangerous
|
|
|
129
140
|
adamantite fix --all
|
|
130
141
|
```
|
|
131
142
|
|
|
132
|
-
|
|
143
|
+
Use `--only` to run one stage. Arguments after `--` go to that stage, and to Oxlint when
|
|
144
|
+
both run:
|
|
133
145
|
|
|
134
|
-
|
|
135
|
-
|
|
146
|
+
```sh
|
|
147
|
+
adamantite fix --only lint
|
|
148
|
+
adamantite fix --only format
|
|
149
|
+
```
|
|
136
150
|
|
|
137
151
|
### `adamantite analyze`
|
|
138
152
|
|
|
@@ -168,13 +182,6 @@ adamantite analyze --only monorepo --fix -- --select highest
|
|
|
168
182
|
`CI` is set), so `analyze --fix` fails there in a monorepo. CI runs `adamantite analyze`
|
|
169
183
|
without `--fix`.
|
|
170
184
|
|
|
171
|
-
### `adamantite monorepo` (deprecated)
|
|
172
|
-
|
|
173
|
-
`adamantite monorepo` is deprecated and prints a warning with the replacement command. The
|
|
174
|
-
next release removes it. Use `adamantite analyze`, or `adamantite analyze --only monorepo`
|
|
175
|
-
to run only Sherif. `adamantite doctor` reports the legacy `check:monorepo` and
|
|
176
|
-
`fix:monorepo` scripts and says how to remove them.
|
|
177
|
-
|
|
178
185
|
### `adamantite doctor`
|
|
179
186
|
|
|
180
187
|
Assess every Adamantite-managed integration. Doctor is read-only. Each finding describes
|
|
@@ -186,15 +193,13 @@ adamantite doctor
|
|
|
186
193
|
|
|
187
194
|
In an interactive terminal, Doctor presents each finding as formatted text and offers to
|
|
188
195
|
hand off to an installed coding agent, or to copy one combined Markdown repair prompt. A
|
|
189
|
-
handoff starts the selected agent CLI in the terminal with
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
to be clean. OpenCode pre-fills its input with the seed prompt, so that handoff needs
|
|
197
|
-
one Enter press to start.
|
|
196
|
+
handoff starts the selected agent CLI in the terminal seeded with that same Markdown
|
|
197
|
+
repair prompt; the agent edits the project under its own permission and trust flow. When
|
|
198
|
+
the agent session ends, Doctor reassesses and exits 0 only when no findings remain.
|
|
199
|
+
Doctor detects installed agents by probing each supported CLI — Claude Code (`claude`),
|
|
200
|
+
Codex (`codex`), Cursor (`cursor-agent`), Gemini CLI (`gemini`), Grok Build (`grok`),
|
|
201
|
+
and OpenCode (`opencode`) — and lists only the ones found on `PATH`. OpenCode pre-fills
|
|
202
|
+
its input with the seed prompt, so that handoff needs one Enter press to start.
|
|
198
203
|
|
|
199
204
|
A coding agent can also run `adamantite doctor` in the target project to receive Markdown
|
|
200
205
|
directly. In a non-interactive run, Doctor prints the combined repair prompt and exits 1
|
|
@@ -211,7 +216,34 @@ adamantite doctor
|
|
|
211
216
|
```
|
|
212
217
|
|
|
213
218
|
`update` exits 0 when dependency updates succeed, even if doctor findings remain. Use
|
|
214
|
-
`adamantite doctor` as the CI gate.
|
|
219
|
+
`adamantite doctor` as the CI gate. After it installs packages, `update` patches Oxlint and TypeScript again for
|
|
220
|
+
[the effect preset](#the-effect-preset).
|
|
221
|
+
|
|
222
|
+
### `adamantite prepare`
|
|
223
|
+
|
|
224
|
+
Patch Oxlint and TypeScript for [the effect preset](#the-effect-preset). The `prepare`
|
|
225
|
+
script in `package.json` runs it after each install:
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{ "scripts": { "prepare": "adamantite prepare" } }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Without the effect preset, `prepare` does nothing and exits 0. If the patch fails, `prepare`
|
|
232
|
+
fails, so the install fails.
|
|
233
|
+
|
|
234
|
+
### `adamantite rule`
|
|
235
|
+
|
|
236
|
+
Write and inspect [custom rules](#custom-rules):
|
|
237
|
+
|
|
238
|
+
```sh
|
|
239
|
+
adamantite rule add no-process-env
|
|
240
|
+
adamantite rule add no-window --dir tooling/lint/rules
|
|
241
|
+
adamantite rule ast src/index.ts
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`rule add` writes a rule stub to `.adamantite/rules/`, or to the folder that `--dir` names,
|
|
245
|
+
and an `AGENTS.md` with the authoring guidance when the folder has none. `rule ast` prints the
|
|
246
|
+
ESTree-shaped AST that Oxlint rules visit, so you can read node names off real code.
|
|
215
247
|
|
|
216
248
|
### Pass arguments to underlying tools
|
|
217
249
|
|
|
@@ -237,19 +269,27 @@ underlying CLI.
|
|
|
237
269
|
|
|
238
270
|
Adamantite publishes configuration that can also be consumed directly:
|
|
239
271
|
|
|
240
|
-
| Export
|
|
241
|
-
|
|
|
242
|
-
| `adamantite/lint`
|
|
243
|
-
| `adamantite/lint/react`
|
|
244
|
-
| `adamantite/lint/
|
|
245
|
-
| `adamantite/lint/
|
|
246
|
-
| `adamantite/lint/
|
|
247
|
-
| `adamantite/lint/
|
|
248
|
-
| `adamantite/lint/
|
|
249
|
-
| `adamantite/lint/
|
|
250
|
-
| `adamantite/
|
|
251
|
-
| `adamantite/
|
|
252
|
-
| `adamantite/
|
|
272
|
+
| Export | Purpose |
|
|
273
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
274
|
+
| `adamantite/lint` | Core Oxlint rules. |
|
|
275
|
+
| `adamantite/lint/react` | React, JSX accessibility, and performance. |
|
|
276
|
+
| `adamantite/lint/react-strict` | Opinions for React feature code: no state, effect, or memoization hooks outside hook modules. See [the react-strict preset](#the-react-strict-preset). |
|
|
277
|
+
| `adamantite/lint/react-doctor` | Curated [React Doctor](https://github.com/millionco/react-doctor) rules for React state and effect misuse. See [the react-doctor preset](#the-react-doctor-preset). |
|
|
278
|
+
| `adamantite/lint/tanstack` | Opinions for [TanStack Query](https://tanstack.com/query) with route loaders: queries defined once with `queryOptions()`, and no query data copied into state. See [the tanstack preset](#the-tanstack-preset). |
|
|
279
|
+
| `adamantite/lint/nextjs` | Next.js rules. |
|
|
280
|
+
| `adamantite/lint/vue` | Vue rules. |
|
|
281
|
+
| `adamantite/lint/effect` | Curated [@effect/tsgo](https://github.com/Effect-TS/tsgo) rules for Effect misuse, such as effects that never run and unhandled errors. See [the effect preset](#the-effect-preset). |
|
|
282
|
+
| `adamantite/lint/node` | Node.js rules. |
|
|
283
|
+
| `adamantite/lint/jest` | Jest rules. |
|
|
284
|
+
| `adamantite/lint/vitest` | Vitest rules. |
|
|
285
|
+
| `adamantite/lint/strict` | Framework-neutral opinions: no deep or wide destructuring, and no type assertions outside tests. See [the strict preset](#the-strict-preset). |
|
|
286
|
+
| `adamantite/lint/antislop` | Vendored [anti-slop](https://github.com/dmmulroy/anti-slop) rules that reject low-evidence, low-signal patterns. Also turns off `typescript/consistent-indexed-object-style` and `unicorn/no-immediate-mutation` from the core preset, which conflict with these rules. |
|
|
287
|
+
| `adamantite/lint/shadcn` | [@shadcn/lint](https://github.com/shadcn-ui/lint) rules for Tailwind v4 design systems: no restyled components, raw colors, arbitrary values, inline styles, unknown classes, or unreadable class expressions. shadcn/ui is not required. See [the shadcn preset](#the-shadcn-preset). |
|
|
288
|
+
| `adamantite/lint/custom` | A function, `custom()`, that loads the project's own rules from `.adamantite/rules/`. See [custom rules](#custom-rules). |
|
|
289
|
+
| `adamantite/rules` | `defineRule` and the rule and AST types for custom rules. |
|
|
290
|
+
| `adamantite/format` | Oxfmt configuration. |
|
|
291
|
+
| `adamantite/analyze` | Knip configuration. The `ignoreDependencies` named export groups suggested ignore lists; `init` sets `ignoreDependencies.monorepo` in a monorepo and `ignoreDependencies.effect` with the effect preset. |
|
|
292
|
+
| `adamantite/typescript` | Strict TypeScript configuration for TS 7+. |
|
|
253
293
|
|
|
254
294
|
When consuming the lint presets directly, hoist the core preset's ignore patterns onto the
|
|
255
295
|
root config. Oxlint does not merge `ignorePatterns` from extended configs, so without the
|
|
@@ -268,10 +308,328 @@ export default defineConfig({
|
|
|
268
308
|
|
|
269
309
|
Configs generated by `adamantite init` include this automatically.
|
|
270
310
|
|
|
311
|
+
### The react-strict preset
|
|
312
|
+
|
|
313
|
+
`adamantite/lint/react-strict` keeps state and side effects out of React feature code. Use
|
|
314
|
+
it together with `adamantite/lint/react`. It needs no extra package: the rules come from
|
|
315
|
+
Adamantite's own Oxlint plugin, which ships inside the package.
|
|
316
|
+
|
|
317
|
+
`adamantite/no-react-state-hooks` reports calls to `useState`, `useReducer`, `useEffect`,
|
|
318
|
+
`useLayoutEffect`, `useSyncExternalStore`, `useMemo`, and `useCallback` imported from
|
|
319
|
+
`react`, including aliased imports and `React.useState(...)`. Each message tells what to do
|
|
320
|
+
instead: derive state from loader data or URL params, move side effects to loaders, actions,
|
|
321
|
+
or event handlers, and let the React Compiler memoize. For the opinions that do not depend
|
|
322
|
+
on React, add [the strict preset](#the-strict-preset).
|
|
323
|
+
|
|
324
|
+
Hooks stay allowed in hook modules: files that match `**/use[A-Z]*.{ts,tsx}`, `**/use-*.{ts,tsx}`, or `**/hooks/**`, such as `useCart.ts`
|
|
325
|
+
or `use-cart.ts`. A file such as `userProfile.tsx` is feature code.
|
|
326
|
+
Globs match paths relative to the working directory. Set `allow` to use another layout, and
|
|
327
|
+
`hooks` to change the list of reported hooks:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import { defineConfig } from "oxlint"
|
|
331
|
+
import core from "adamantite/lint"
|
|
332
|
+
import react from "adamantite/lint/react"
|
|
333
|
+
import reactStrict from "adamantite/lint/react-strict"
|
|
334
|
+
|
|
335
|
+
export default defineConfig({
|
|
336
|
+
extends: [core, react, reactStrict],
|
|
337
|
+
ignorePatterns: core.ignorePatterns,
|
|
338
|
+
rules: {
|
|
339
|
+
"adamantite/no-react-state-hooks": [
|
|
340
|
+
"error",
|
|
341
|
+
{ allow: ["src/state/**", "**/use[A-Z]*.{ts,tsx}"], hooks: ["useState", "useEffect"] },
|
|
342
|
+
],
|
|
343
|
+
},
|
|
344
|
+
})
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Each option replaces its default list.
|
|
348
|
+
|
|
349
|
+
### The react-doctor preset
|
|
350
|
+
|
|
351
|
+
`adamantite/lint/react-doctor` reports specific misuse of React state and effects. Use it
|
|
352
|
+
together with `adamantite/lint/react`. It needs the `oxlint-plugin-react-doctor` package.
|
|
353
|
+
`adamantite init` installs the pinned version when you select the preset, and
|
|
354
|
+
`adamantite doctor` and `adamantite update` keep it on that version. If you add the preset
|
|
355
|
+
by hand, install `oxlint-plugin-react-doctor` as a devDependency too.
|
|
356
|
+
|
|
357
|
+
The plugin exports more than 900 rules. The preset enables only rules that run on one file
|
|
358
|
+
and that the native `react`, `react-hooks`, `jsx-a11y`, and `unicorn` rules do not already
|
|
359
|
+
report. Examples are an effect that does the work of an event handler (`no-effect-event-handler`), data fetched in an effect
|
|
360
|
+
(`no-fetch-in-effect`), and state changed in place (`no-direct-state-mutation`). The
|
|
361
|
+
TanStack Query and TanStack Start rules are not in this preset. See
|
|
362
|
+
[ADR 0005](docs/adr/0005-curate-react-doctor-rules.md) for the criteria.
|
|
363
|
+
|
|
364
|
+
The preset works together with `adamantite/lint/react-strict`. React Doctor rules report
|
|
365
|
+
specific misuse, and `react-strict` bans the hooks in feature code. Some React Doctor rules
|
|
366
|
+
skip test files and fixtures by path, such as `__tests__/` and `*.test.tsx`.
|
|
367
|
+
|
|
368
|
+
`oxlint-plugin-react-doctor` has a modified MIT license. It needs written permission from
|
|
369
|
+
the copyright holder to use the software as machine learning training data, or to sell it
|
|
370
|
+
as a hosted product. Read the
|
|
371
|
+
[license](https://www.npmjs.com/package/oxlint-plugin-react-doctor?activeTab=code) before
|
|
372
|
+
you select the preset.
|
|
373
|
+
|
|
374
|
+
### The tanstack preset
|
|
375
|
+
|
|
376
|
+
`adamantite/lint/tanstack` holds opinions for TanStack Query (`@tanstack/react-query`) in
|
|
377
|
+
apps that preload queries in route loaders, such as TanStack Router and TanStack Start apps.
|
|
378
|
+
Like `react-strict`, it needs no extra package.
|
|
379
|
+
|
|
380
|
+
- `adamantite/query-from-loader` reports `useQuery`, `useSuspenseQuery`, `useInfiniteQuery`,
|
|
381
|
+
and `useSuspenseInfiniteQuery` calls in route files that take a plain options object or a `queryOptions()`
|
|
382
|
+
call made inside a function. Define each query once with `queryOptions()` at module scope
|
|
383
|
+
or in an exported factory such as `userQuery(id)`, preload it in the route loader with
|
|
384
|
+
`queryClient.ensureQueryData()`, and pass the same options to the hook. For the infinite
|
|
385
|
+
hooks, use `infiniteQueryOptions()` and `queryClient.ensureInfiniteQueryData()`. The rule
|
|
386
|
+
looks through type-only wrappers such as `as const` and `satisfies`. Then a route with
|
|
387
|
+
`preload="intent"` renders from the cache without a request waterfall. An object that
|
|
388
|
+
spreads shared options, such as `{ ...userQuery(id), select }`, stays allowed.
|
|
389
|
+
- `adamantite/no-query-data-in-state` reports `useState` calls whose initial value comes from
|
|
390
|
+
the data of a query in the same component, such as `useState(query.data)` or
|
|
391
|
+
`useState(() => data.user)`. The copy stops updating when the query refetches. Read the
|
|
392
|
+
value from the query result, and keep only the user's edits in state.
|
|
393
|
+
|
|
394
|
+
A route file is a file that calls a route factory from `@tanstack/react-router`, such as
|
|
395
|
+
`createFileRoute`, `createRootRoute`, or `createRoute`. Only a route has a loader to preload
|
|
396
|
+
the query, so components outside route files can define their queries inline.
|
|
397
|
+
`query-from-loader` checks one file at a time. It cannot prove that a loader preloads the
|
|
398
|
+
options, so it moves queries toward shared definitions but does not guarantee the preload.
|
|
399
|
+
The `react-doctor` preset reports other query misuse, such as refetches from effects and
|
|
400
|
+
mutations that do not invalidate queries. See
|
|
401
|
+
[ADR 0008](docs/adr/0008-tanstack-query-options-from-loaders.md).
|
|
402
|
+
|
|
403
|
+
### The effect preset
|
|
404
|
+
|
|
405
|
+
`adamantite/lint/effect` reports misuse of [Effect](https://effect.website), such as an
|
|
406
|
+
effect that is neither yielded nor assigned (`floating-effect`), an unhandled error or
|
|
407
|
+
requirement (`missing-effect-error`, `missing-effect-context`), and a combinator with a
|
|
408
|
+
simpler replacement (`catch-all-to-map-error`). The rules come from
|
|
409
|
+
[@effect/tsgo](https://github.com/Effect-TS/tsgo), the Effect Language Service for
|
|
410
|
+
TypeScript-Go, and need Oxlint's type-aware mode.
|
|
411
|
+
|
|
412
|
+
The rules are not a JavaScript plugin. They exist only after `effect-tsgo patch --oxlint
|
|
413
|
+
--typescript` replaces the Oxlint, oxlint-tsgolint, and TypeScript binaries in
|
|
414
|
+
`node_modules`. Without the patch, Oxlint stops with `Unknown plugin: 'effecttsgo'`. Each
|
|
415
|
+
install of one of those packages restores the original binaries, so the patch must run again
|
|
416
|
+
after it. Adamantite runs it in these places:
|
|
417
|
+
|
|
418
|
+
- `adamantite prepare`, which the `prepare` script runs after a bare install, such as
|
|
419
|
+
`npm install`, `npm ci`, or a fresh clone in CI.
|
|
420
|
+
- `adamantite init` and `adamantite update`, after they install packages. A named install,
|
|
421
|
+
such as `npm install -D oxlint@1.86.0`, does not run the `prepare` script. If you change one
|
|
422
|
+
of those versions yourself, doctor reports the version drift, and `adamantite update`
|
|
423
|
+
installs the pinned version and patches it.
|
|
424
|
+
|
|
425
|
+
When you select the preset, `adamantite init` does these steps:
|
|
426
|
+
|
|
427
|
+
- Installs the pinned `@effect/tsgo` and runs the patch.
|
|
428
|
+
- Adds `"prepare": "adamantite prepare"` to `package.json`. If the project already has a
|
|
429
|
+
`prepare` script, init rewrites it as `adamantite prepare && (<existing>)`. A command such as
|
|
430
|
+
`cd ..` then cannot move the patch out of the project, and a fallback such as `|| true`
|
|
431
|
+
cannot hide a failed patch.
|
|
432
|
+
- Adds `{ "name": "@effect/language-service", "diagnostics": false }` to
|
|
433
|
+
`compilerOptions.plugins` in `tsconfig.json`. Editors that use the workspace TypeScript
|
|
434
|
+
get Effect quick fixes, refactors, and hovers. Oxlint reports the diagnostics, so the
|
|
435
|
+
language service does not report them a second time.
|
|
436
|
+
- Sets `ignoreDependencies.effect` in `knip.config.ts`, because no package has the name
|
|
437
|
+
`@effect/language-service`.
|
|
438
|
+
|
|
439
|
+
`adamantite doctor` reports each missing part, and `adamantite update` keeps
|
|
440
|
+
`@effect/tsgo` on its pinned version. Each `@effect/tsgo` release supports only some Oxlint,
|
|
441
|
+
oxlint-tsgolint, and TypeScript versions, so Adamantite moves the pins together.
|
|
442
|
+
|
|
443
|
+
A production-only install, such as `npm ci --omit=dev` or `pnpm install --prod`, still runs
|
|
444
|
+
the `prepare` script, but it does not install Adamantite, so the install fails with
|
|
445
|
+
`adamantite: not found`. Add `--ignore-scripts` to those installs. Nothing in a production
|
|
446
|
+
install needs the patch.
|
|
447
|
+
|
|
448
|
+
The rules read their options from the tsconfig entry. For example, `unstable-api-usage`
|
|
449
|
+
reports each use of an API marked `@stability unstable`. To allow a module on purpose, list
|
|
450
|
+
it in `allowedUnstableApis`:
|
|
451
|
+
|
|
452
|
+
```jsonc
|
|
453
|
+
{
|
|
454
|
+
"compilerOptions": {
|
|
455
|
+
"plugins": [
|
|
456
|
+
{
|
|
457
|
+
"name": "@effect/language-service",
|
|
458
|
+
"diagnostics": false,
|
|
459
|
+
"allowedUnstableApis": ["effect/cli"],
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
The preset enables only rules that report Effect code. It does not ban platform APIs, such
|
|
467
|
+
as `Date.now()` or `process.env`, in code that is not Effect code. Inside Effect code, it
|
|
468
|
+
reports them, because `Clock`, `Random`, and `Config` let tests control them. See
|
|
469
|
+
[ADR 0007](docs/adr/0007-effect-preset-patches-oxlint.md) for the criteria.
|
|
470
|
+
|
|
471
|
+
### The strict preset
|
|
472
|
+
|
|
473
|
+
`adamantite/lint/strict` holds framework-neutral opinions. Use it with any other preset. Like
|
|
474
|
+
`react-strict`, it needs no extra package.
|
|
475
|
+
|
|
476
|
+
- `adamantite/no-overzealous-destructuring` reports destructuring patterns nested more than
|
|
477
|
+
`maxDepth` levels deep (default 2), such as `const { data: { user: { name } } } = query`,
|
|
478
|
+
and object patterns that take more than `maxProperties` properties (default 5). A rest
|
|
479
|
+
element does not count. Keep the object and read its members, or split the pattern.
|
|
480
|
+
- `typescript/consistent-type-assertions` bans type assertions. `as const` stays allowed,
|
|
481
|
+
and test files (`*.test.*`, `*.spec.*`, and `__tests__/`) keep the core preset's setting.
|
|
482
|
+
|
|
483
|
+
Set `maxDepth` and `maxProperties` to change the destructuring limits:
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
import { defineConfig } from "oxlint"
|
|
487
|
+
import core from "adamantite/lint"
|
|
488
|
+
import strict from "adamantite/lint/strict"
|
|
489
|
+
|
|
490
|
+
export default defineConfig({
|
|
491
|
+
extends: [core, strict],
|
|
492
|
+
ignorePatterns: core.ignorePatterns,
|
|
493
|
+
rules: {
|
|
494
|
+
"adamantite/no-overzealous-destructuring": ["error", { maxDepth: 1, maxProperties: 3 }],
|
|
495
|
+
},
|
|
496
|
+
})
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### The shadcn preset
|
|
500
|
+
|
|
501
|
+
`adamantite/lint/shadcn` needs Tailwind v4 and the `@shadcn/lint` package. `adamantite init`
|
|
502
|
+
installs the pinned version when you select the preset, and `adamantite doctor` and
|
|
503
|
+
`adamantite update` keep it on that version. If you add the preset by hand, install
|
|
504
|
+
`@shadcn/lint` as a devDependency too. The plugin finds your components and theme
|
|
505
|
+
through `components.json`. Without that file it looks for components in `components/ui`
|
|
506
|
+
or `src/components/ui` and discovers the stylesheet that imports Tailwind. When your
|
|
507
|
+
components live elsewhere, set `settings.shadcn.ui`:
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
import { defineConfig } from "oxlint"
|
|
511
|
+
import core from "adamantite/lint"
|
|
512
|
+
import shadcn from "adamantite/lint/shadcn"
|
|
513
|
+
|
|
514
|
+
export default defineConfig({
|
|
515
|
+
extends: [core, shadcn],
|
|
516
|
+
ignorePatterns: core.ignorePatterns,
|
|
517
|
+
settings: { shadcn: { ui: "@/ds" } },
|
|
518
|
+
})
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
The preset allows layout classes such as `mt-4` and `w-[320px]` on components, and turns
|
|
522
|
+
off `no-restyle`, `no-arbitrary-values`, and `require-static-classes` under
|
|
523
|
+
`**/components/ui/**`, where components own their appearance. Add the same override for
|
|
524
|
+
another component directory. See the
|
|
525
|
+
[@shadcn/lint documentation](https://github.com/shadcn-ui/lint#settings) for settings,
|
|
526
|
+
contracts, and custom messages.
|
|
527
|
+
|
|
528
|
+
## Custom rules
|
|
529
|
+
|
|
530
|
+
Custom rules are Oxlint rules that your project writes and owns. Each file in
|
|
531
|
+
`.adamantite/rules/` is one rule. `custom()` from `adamantite/lint/custom` loads the folder
|
|
532
|
+
and enables each rule as `error` under the `project` plugin, so `no-process-env.ts` becomes
|
|
533
|
+
`project/no-process-env`. Add a file to add a rule, and delete the file to remove it. A file
|
|
534
|
+
that starts with `_`, such as `_helpers.ts`, is a helper, not a rule.
|
|
535
|
+
|
|
536
|
+
```text
|
|
537
|
+
.adamantite/rules/
|
|
538
|
+
no-process-env.ts # enabled as project/no-process-env
|
|
539
|
+
_helpers.ts # skipped
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
// .adamantite/rules/no-process-env.ts
|
|
544
|
+
import { defineRule } from "adamantite/rules"
|
|
545
|
+
|
|
546
|
+
export default defineRule({
|
|
547
|
+
meta: {
|
|
548
|
+
messages: { unexpected: "Read configuration through the config module." },
|
|
549
|
+
type: "problem",
|
|
550
|
+
},
|
|
551
|
+
createOnce(context) {
|
|
552
|
+
return {
|
|
553
|
+
MemberExpression(node) {
|
|
554
|
+
if (node.object.type === "Identifier" && node.object.name === "process") {
|
|
555
|
+
context.report({ messageId: "unexpected", node })
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
})
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
// oxlint.config.ts
|
|
565
|
+
import { defineConfig } from "oxlint"
|
|
566
|
+
import core from "adamantite/lint"
|
|
567
|
+
import custom from "adamantite/lint/custom"
|
|
568
|
+
|
|
569
|
+
export default defineConfig({
|
|
570
|
+
extends: [core, custom()],
|
|
571
|
+
ignorePatterns: core.ignorePatterns,
|
|
572
|
+
})
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
`custom()` returns an empty config when the folder does not exist, so `adamantite init` adds
|
|
576
|
+
it to every generated config. It takes these options:
|
|
577
|
+
|
|
578
|
+
- `dir`: the rules folder. A relative path resolves from the file that calls `custom()`, not
|
|
579
|
+
from the working directory. Default: `.adamantite/rules`.
|
|
580
|
+
- `name`: the plugin name that prefixes each rule. Default: `project`.
|
|
581
|
+
- `rules`: severity or options for a rule, by rule name without the prefix, such as
|
|
582
|
+
`{ "no-process-env": "warn" }`.
|
|
583
|
+
|
|
584
|
+
Rule files run with Node.js type stripping, so use erasable TypeScript only: no `enum`,
|
|
585
|
+
`namespace` with values, or parameter properties. Rules get no type information. A rule
|
|
586
|
+
that throws prints an error for each file, and the lint run still exits 0. Adamantite owns
|
|
587
|
+
the wiring and the authoring guidance; your project owns the correctness and the tests of
|
|
588
|
+
its rules. To test a rule, use `RuleTester` from `oxlint/plugins-dev`. `adamantite rule add`
|
|
589
|
+
writes the full authoring guidance to `.adamantite/rules/AGENTS.md`.
|
|
590
|
+
|
|
591
|
+
`adamantite doctor` reports a rule file that cannot load, with the reason, a rules folder
|
|
592
|
+
that no `custom()` call loads, and two rules folders that use the same plugin name.
|
|
593
|
+
|
|
594
|
+
### Custom rules in a monorepo
|
|
595
|
+
|
|
596
|
+
Oxlint rejects two plugins with the same name in one run, so give each rules folder its own
|
|
597
|
+
name. A nested Oxlint config replaces the root config, so a workspace package that needs the
|
|
598
|
+
root rules extends the root config:
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
// packages/web/oxlint.config.ts
|
|
602
|
+
import { defineConfig } from "oxlint"
|
|
603
|
+
import custom from "adamantite/lint/custom"
|
|
604
|
+
import root from "../../oxlint.config.ts"
|
|
605
|
+
|
|
606
|
+
export default defineConfig({ extends: [root, custom({ name: "web" })] })
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
To share one rules folder across the workspace, keep it in a tooling package and call
|
|
610
|
+
`custom()` there. The relative `dir` resolves from the tooling package, so every config that
|
|
611
|
+
extends it loads the same rules:
|
|
612
|
+
|
|
613
|
+
```ts
|
|
614
|
+
// tooling/lint/index.ts
|
|
615
|
+
import custom from "adamantite/lint/custom"
|
|
616
|
+
|
|
617
|
+
export default custom({ dir: "rules", name: "acme" })
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Run `adamantite rule add <name> --dir tooling/lint/rules` to add a rule to that folder.
|
|
621
|
+
Doctor starts from the Oxlint config of the root and of each workspace package, and follows
|
|
622
|
+
relative imports and imports of workspace packages to find the `custom()` calls. It resolves
|
|
623
|
+
`dir` and `name` when they are string literals.
|
|
624
|
+
|
|
271
625
|
## Requirements and boundaries
|
|
272
626
|
|
|
273
627
|
- Adamantite requires Bun 1.0 or later when run with Bun, or Node.js 22.19 or later when
|
|
274
628
|
run with Node.js.
|
|
629
|
+
- Adamantite requires TypeScript 7 or later. List `typescript` as a dependency in your own
|
|
630
|
+
`package.json`. Do not rely on the copy that a package manager installs for Adamantite's
|
|
631
|
+
peer dependency: npm cannot add or update a managed plugin such as `@shadcn/lint` in a
|
|
632
|
+
project that has no `typescript` entry of its own.
|
|
275
633
|
- This repository uses pnpm and Node.js for development, but the CLI can configure
|
|
276
634
|
projects that use Bun, Deno, npm, pnpm, or Yarn where the selected integration supports
|
|
277
635
|
them.
|