adamantite 0.40.0 → 0.42.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.
@@ -0,0 +1,92 @@
1
+ import { readPositiveIntegerOption } from "../options.js";
2
+ //#region presets/lint/plugin/rules/no-overzealous-destructuring.ts
3
+ /**
4
+ * `const { data: { user } } = query` nests two patterns, which stays allowed.
5
+ */
6
+ const DEFAULT_MAX_DEPTH = 2;
7
+ /**
8
+ * Five properties cover typical component props. A rest element does not count.
9
+ */
10
+ const DEFAULT_MAX_PROPERTIES = 5;
11
+ function isPattern(node) {
12
+ return node.type === "ObjectPattern" || node.type === "ArrayPattern";
13
+ }
14
+ /**
15
+ * The pattern that directly contains `pattern`, through a property, a default value, or a rest
16
+ * element. A root pattern, such as a declaration target or a parameter, has none.
17
+ */
18
+ function getParentPattern(pattern) {
19
+ let node = pattern.parent;
20
+ while (node.type === "AssignmentPattern" || node.type === "RestElement") node = node.parent;
21
+ if (node.type === "Property" && node.parent.type === "ObjectPattern") return node.parent;
22
+ return isPattern(node) ? node : void 0;
23
+ }
24
+ function getDepth(pattern) {
25
+ let depth = 1;
26
+ let parent = getParentPattern(pattern);
27
+ while (parent !== void 0) {
28
+ depth += 1;
29
+ parent = getParentPattern(parent);
30
+ }
31
+ return depth;
32
+ }
33
+ const rule = {
34
+ create(context) {
35
+ const maxDepth = readPositiveIntegerOption(context.options, "maxDepth") ?? DEFAULT_MAX_DEPTH;
36
+ const maxProperties = readPositiveIntegerOption(context.options, "maxProperties") ?? DEFAULT_MAX_PROPERTIES;
37
+ function checkDepth(node) {
38
+ const depth = getDepth(node);
39
+ if (depth === maxDepth + 1) context.report({
40
+ data: {
41
+ depth: String(depth),
42
+ maxDepth: String(maxDepth)
43
+ },
44
+ messageId: "tooDeep",
45
+ node
46
+ });
47
+ }
48
+ return {
49
+ ArrayPattern: checkDepth,
50
+ ObjectPattern(node) {
51
+ checkDepth(node);
52
+ const count = node.properties.filter((property) => property.type === "Property").length;
53
+ if (count > maxProperties) context.report({
54
+ data: {
55
+ count: String(count),
56
+ maxProperties: String(maxProperties)
57
+ },
58
+ messageId: "tooManyProperties",
59
+ node
60
+ });
61
+ }
62
+ };
63
+ },
64
+ meta: {
65
+ defaultOptions: [{
66
+ maxDepth: DEFAULT_MAX_DEPTH,
67
+ maxProperties: DEFAULT_MAX_PROPERTIES
68
+ }],
69
+ docs: { description: "Disallow destructuring patterns that nest too deep or take too many properties from one object." },
70
+ messages: {
71
+ tooDeep: "This destructuring pattern is nested {{depth}} levels deep. The limit is {{maxDepth}}. Keep the outer object and read its members, or split the pattern into separate declarations.",
72
+ tooManyProperties: "This destructuring pattern takes {{count}} properties from one object. The limit is {{maxProperties}}. Keep the object and read its members where you use them, or split the pattern."
73
+ },
74
+ schema: [{
75
+ additionalProperties: false,
76
+ properties: {
77
+ maxDepth: {
78
+ minimum: 1,
79
+ type: "integer"
80
+ },
81
+ maxProperties: {
82
+ minimum: 1,
83
+ type: "integer"
84
+ }
85
+ },
86
+ type: "object"
87
+ }],
88
+ type: "suggestion"
89
+ }
90
+ };
91
+ //#endregion
92
+ export { rule as default };
@@ -0,0 +1,10 @@
1
+ import { Rule } from "@oxlint/plugins";
2
+ //#region presets/lint/plugin/rules/no-react-state-hooks.d.ts
3
+ /**
4
+ * Convert a glob to a regular expression that matches a whole path with forward slashes. Supports
5
+ * `**`, `*`, `?`, `[a-z]` and `[!a-z]` character classes, and `{a,b}` alternation.
6
+ */
7
+ declare function globToRegExp(glob: string): RegExp;
8
+ declare const rule: Rule;
9
+ //#endregion
10
+ export { rule as default, globToRegExp };
@@ -0,0 +1,160 @@
1
+ import { readStringArrayOption } from "../options.js";
2
+ //#region presets/lint/plugin/rules/no-react-state-hooks.ts
3
+ const HOOK_MESSAGES = /* @__PURE__ */ new Map([
4
+ ["useCallback", "memoization"],
5
+ ["useEffect", "effect"],
6
+ ["useLayoutEffect", "effect"],
7
+ ["useMemo", "memoization"],
8
+ ["useReducer", "localState"],
9
+ ["useState", "localState"],
10
+ ["useSyncExternalStore", "externalStore"]
11
+ ]);
12
+ const DEFAULT_HOOKS = [...HOOK_MESSAGES.keys()];
13
+ /**
14
+ * Modules where hooks are permitted: hook modules such as `useCart.ts` or `use-cart.ts`, and
15
+ * everything under a `hooks` directory. Everything else is feature code, including files such as
16
+ * `userProfile.tsx`.
17
+ */
18
+ const DEFAULT_ALLOW = [
19
+ "**/use[A-Z]*.{ts,tsx}",
20
+ "**/use-*.{ts,tsx}",
21
+ "**/hooks/**"
22
+ ];
23
+ const REACT_MODULE = "react";
24
+ function escapeRegExp(text) {
25
+ return text.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw`\$&`);
26
+ }
27
+ /**
28
+ * Convert a glob to a regular expression that matches a whole path with forward slashes. Supports
29
+ * `**`, `*`, `?`, `[a-z]` and `[!a-z]` character classes, and `{a,b}` alternation.
30
+ */
31
+ function globToRegExp(glob) {
32
+ let source = "";
33
+ let index = 0;
34
+ while (index < glob.length) {
35
+ const rest = glob.slice(index);
36
+ if (rest.startsWith("**/")) {
37
+ source += "(?:.*/)?";
38
+ index += 3;
39
+ } else if (rest === "/**") {
40
+ source += "(?:/.*)?";
41
+ index += 3;
42
+ } else if (rest.startsWith("**")) {
43
+ source += ".*";
44
+ index += 2;
45
+ } else if (rest.startsWith("*")) {
46
+ source += "[^/]*";
47
+ index += 1;
48
+ } else if (rest.startsWith("?")) {
49
+ source += "[^/]";
50
+ index += 1;
51
+ } else if (rest.startsWith("[") && rest.indexOf("]") > 1) {
52
+ const end = rest.indexOf("]");
53
+ const negated = rest.startsWith("[!");
54
+ const characters = rest.slice(negated ? 2 : 1, end).replaceAll(/[\\^]/g, String.raw`\$&`);
55
+ source += `[${negated ? "^" : ""}${characters}]`;
56
+ index += end + 1;
57
+ } else if (rest.startsWith("{") && rest.includes("}")) {
58
+ const end = rest.indexOf("}");
59
+ const alternatives = rest.slice(1, end).split(",").map((alternative) => escapeRegExp(alternative));
60
+ source += `(?:${alternatives.join("|")})`;
61
+ index += end + 1;
62
+ } else {
63
+ source += escapeRegExp(glob.charAt(index));
64
+ index += 1;
65
+ }
66
+ }
67
+ return new RegExp(`^${source}$`);
68
+ }
69
+ function toPosixPath(path) {
70
+ return path.replaceAll("\\", "/");
71
+ }
72
+ /**
73
+ * The path that `allow` globs match: relative to the working directory when the file is inside it,
74
+ * and absolute otherwise.
75
+ */
76
+ function getMatchPath(context) {
77
+ const filename = toPosixPath(context.filename);
78
+ const cwd = toPosixPath(context.cwd).replace(/\/$/, "");
79
+ return filename.startsWith(`${cwd}/`) ? filename.slice(cwd.length + 1) : filename;
80
+ }
81
+ function getImportedName(specifier) {
82
+ return specifier.imported.type === "Literal" ? specifier.imported.value : specifier.imported.name;
83
+ }
84
+ function isStringLiteral(node) {
85
+ return node.type === "Literal" && typeof node.value === "string";
86
+ }
87
+ function getPropertyName(member) {
88
+ if (!member.computed) return member.property.type === "Identifier" ? member.property.name : void 0;
89
+ return isStringLiteral(member.property) ? member.property.value : void 0;
90
+ }
91
+ function isCallee(node) {
92
+ const { parent } = node;
93
+ return parent?.type === "CallExpression" && parent.callee === node;
94
+ }
95
+ const rule = {
96
+ create(context) {
97
+ const hooks = new Set(readStringArrayOption(context.options, "hooks") ?? DEFAULT_HOOKS);
98
+ const allow = (readStringArrayOption(context.options, "allow") ?? DEFAULT_ALLOW).map((glob) => globToRegExp(glob));
99
+ const matchPath = getMatchPath(context);
100
+ if (allow.some((pattern) => pattern.test(matchPath))) return {};
101
+ function report(node, hook) {
102
+ context.report({
103
+ data: { hook },
104
+ messageId: HOOK_MESSAGES.get(hook) ?? "banned",
105
+ node
106
+ });
107
+ }
108
+ function getReferences(specifier) {
109
+ return context.sourceCode.getDeclaredVariables(specifier).flatMap((variable) => variable.references).map((reference) => reference.identifier);
110
+ }
111
+ return { ImportDeclaration(node) {
112
+ if (node.source.value !== REACT_MODULE || node.importKind === "type") return;
113
+ for (const specifier of node.specifiers) {
114
+ if (specifier.type === "ImportSpecifier") {
115
+ const hook = getImportedName(specifier);
116
+ if (specifier.importKind === "type" || !hooks.has(hook)) continue;
117
+ for (const identifier of getReferences(specifier)) if (isCallee(identifier)) report(identifier.parent, hook);
118
+ continue;
119
+ }
120
+ for (const identifier of getReferences(specifier)) {
121
+ const member = identifier.parent;
122
+ if (member.type !== "MemberExpression" || member.object !== identifier) continue;
123
+ const hook = getPropertyName(member);
124
+ if (hook !== void 0 && hooks.has(hook) && isCallee(member)) report(member.parent, hook);
125
+ }
126
+ }
127
+ } };
128
+ },
129
+ meta: {
130
+ defaultOptions: [{
131
+ allow: [...DEFAULT_ALLOW],
132
+ hooks: [...DEFAULT_HOOKS]
133
+ }],
134
+ docs: { description: "Disallow React state, effect, and memoization hooks in feature code. Hooks stay allowed in hook modules." },
135
+ messages: {
136
+ banned: "`{{hook}}` is not allowed in feature code. Move it into a hook module.",
137
+ effect: "`{{hook}}` runs a side effect in feature code. Move the side effect to a loader, an action, or an event handler.",
138
+ externalStore: "`{{hook}}` subscribes to an external store in feature code. Subscribe through a query or a store abstraction instead.",
139
+ localState: "`{{hook}}` keeps local state in feature code. Derive the value from loader data or URL params instead.",
140
+ memoization: "`{{hook}}` memoizes by hand. Remove it: the React Compiler handles memoization."
141
+ },
142
+ schema: [{
143
+ additionalProperties: false,
144
+ properties: {
145
+ allow: {
146
+ items: { type: "string" },
147
+ type: "array"
148
+ },
149
+ hooks: {
150
+ items: { type: "string" },
151
+ type: "array"
152
+ }
153
+ },
154
+ type: "object"
155
+ }],
156
+ type: "suggestion"
157
+ }
158
+ };
159
+ //#endregion
160
+ export { rule as default, globToRegExp };
@@ -0,0 +1,5 @@
1
+ import { OxlintConfig } from "oxlint";
2
+ //#region presets/lint/react-doctor.d.ts
3
+ declare const config: OxlintConfig;
4
+ //#endregion
5
+ export { config as default };
@@ -0,0 +1,20 @@
1
+ //#region presets/lint/react-doctor.ts
2
+ const config = {
3
+ jsPlugins: ["oxlint-plugin-react-doctor"],
4
+ rules: {
5
+ "react-doctor/no-create-store-in-render": "error",
6
+ "react-doctor/no-direct-state-mutation": "error",
7
+ "react-doctor/no-eager-new-in-use-state-initializer": "error",
8
+ "react-doctor/no-impure-state-updater": "error",
9
+ "react-doctor/no-mutating-reducer-state": "error",
10
+ "react-doctor/effect-observer-needs-disconnect": "error",
11
+ "react-doctor/effect-raf-loop-needs-cancel": "error",
12
+ "react-doctor/no-effect-event-handler": "error",
13
+ "react-doctor/no-event-trigger-state": "error",
14
+ "react-doctor/no-fetch-in-effect": "error",
15
+ "react-doctor/no-pass-live-state-to-parent": "error",
16
+ "react-doctor/no-set-state-after-await-in-effect": "error"
17
+ }
18
+ };
19
+ //#endregion
20
+ export { config as default };
@@ -0,0 +1,5 @@
1
+ import { OxlintConfig } from "oxlint";
2
+ //#region presets/lint/react-strict.d.ts
3
+ declare const config: OxlintConfig;
4
+ //#endregion
5
+ export { config as default };
@@ -0,0 +1,11 @@
1
+ //#region presets/lint/react-strict.ts
2
+ const PLUGIN_EXTENSION = import.meta.url.endsWith(".ts") ? "ts" : "js";
3
+ const config = {
4
+ jsPlugins: [{
5
+ name: "adamantite",
6
+ specifier: new URL(`plugin/index.${PLUGIN_EXTENSION}`, import.meta.url).href
7
+ }],
8
+ rules: { "adamantite/no-react-state-hooks": "error" }
9
+ };
10
+ //#endregion
11
+ export { config as default };
@@ -0,0 +1,5 @@
1
+ import { OxlintConfig } from "oxlint";
2
+ //#region presets/lint/shadcn.d.ts
3
+ declare const config: OxlintConfig;
4
+ //#endregion
5
+ export { config as default };
@@ -0,0 +1,22 @@
1
+ //#region presets/lint/shadcn.ts
2
+ const config = {
3
+ jsPlugins: ["@shadcn/lint"],
4
+ overrides: [{
5
+ files: ["**/components/ui/**"],
6
+ rules: {
7
+ "shadcn/no-arbitrary-values": "off",
8
+ "shadcn/no-restyle": "off",
9
+ "shadcn/require-static-classes": "off"
10
+ }
11
+ }],
12
+ rules: {
13
+ "shadcn/no-arbitrary-values": ["error", { allow: ["layout"] }],
14
+ "shadcn/no-inline-styles": "error",
15
+ "shadcn/no-raw-colors": "error",
16
+ "shadcn/no-restyle": ["error", { allow: ["layout"] }],
17
+ "shadcn/no-unknown-classes": "error",
18
+ "shadcn/require-static-classes": "error"
19
+ }
20
+ };
21
+ //#endregion
22
+ export { config as default };
@@ -0,0 +1,5 @@
1
+ import { OxlintConfig } from "oxlint";
2
+ //#region presets/lint/strict.d.ts
3
+ declare const config: OxlintConfig;
4
+ //#endregion
5
+ export { config as default };
@@ -0,0 +1,18 @@
1
+ //#region presets/lint/strict.ts
2
+ const PLUGIN_EXTENSION = import.meta.url.endsWith(".ts") ? "ts" : "js";
3
+ const config = {
4
+ jsPlugins: [{
5
+ name: "adamantite",
6
+ specifier: new URL(`plugin/index.${PLUGIN_EXTENSION}`, import.meta.url).href
7
+ }],
8
+ overrides: [{
9
+ files: ["**/*.{test,spec}.{ts,tsx,mts,cts}", "**/__tests__/**"],
10
+ rules: { "typescript/consistent-type-assertions": "error" }
11
+ }],
12
+ rules: {
13
+ "adamantite/no-overzealous-destructuring": "error",
14
+ "typescript/consistent-type-assertions": ["error", { assertionStyle: "never" }]
15
+ }
16
+ };
17
+ //#endregion
18
+ export { config as default };
@@ -1,11 +1,27 @@
1
1
  // Generated by scripts/vendor-plugins.ts — do not edit by hand.
2
2
  interface VendoredPluginRule {
3
- readonly create: (context: never) => Record<string, never>
3
+ readonly create: (context: object) => Record<string, never>
4
4
  }
5
5
 
6
6
  declare const vendoredPlugin: {
7
7
  readonly meta: { readonly name: string }
8
- readonly rules: { readonly [ruleName: string]: VendoredPluginRule }
8
+ readonly rules: {
9
+ readonly "no-chained-type-assertions": VendoredPluginRule
10
+ readonly "no-conditional-empty-object-spread": VendoredPluginRule
11
+ readonly "no-known-value-widening": VendoredPluginRule
12
+ readonly "no-module-mocking": VendoredPluginRule
13
+ readonly "no-object-parameters": VendoredPluginRule
14
+ readonly "no-reflect-apply": VendoredPluginRule
15
+ readonly "no-reflect-get": VendoredPluginRule
16
+ readonly "no-runtime-typeof": VendoredPluginRule
17
+ readonly "no-shape-in-symbol-names": VendoredPluginRule
18
+ readonly "no-unknown-parameters": VendoredPluginRule
19
+ readonly "no-unknown-returns": VendoredPluginRule
20
+ readonly "no-unknown-type-aliases": VendoredPluginRule
21
+ readonly "no-unsafe-dictionary-type": VendoredPluginRule
22
+ readonly "no-widen-then-assert": VendoredPluginRule
23
+ readonly "require-safety-comment-for-type-assertion": VendoredPluginRule
24
+ }
9
25
  }
10
26
 
11
27
  export default vendoredPlugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adamantite",
3
- "version": "0.40.0",
3
+ "version": "0.42.0",
4
4
  "description": "An strict and opinionated set of presets for modern TypeScript applications",
5
5
  "keywords": [
6
6
  "adamantite",
@@ -58,18 +58,19 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "@clack/prompts": "1.7.0",
61
- "@effect/platform-node": "4.0.0-rc.115",
61
+ "@effect/platform-node": "4.0.0",
62
62
  "defu": "6.1.7",
63
- "effect": "4.0.0-rc.115",
64
- "esrap": "2.3.6",
63
+ "effect": "4.0.0",
65
64
  "jsonc-parser": "3.3.1",
66
65
  "nypm": "0.6.9",
67
- "oxc-parser": "^0.147.0"
66
+ "oxc-parser": "0.150.0"
68
67
  },
69
68
  "devDependencies": {
70
69
  "@changesets/changelog-github": "1.0.0",
71
70
  "@changesets/cli": "3.0.1",
72
- "@effect/vitest": "4.0.0-rc.115",
71
+ "@effect/vitest": "4.0.0",
72
+ "@oxlint/plugins": "1.83.0",
73
+ "@shadcn/lint": "0.1.5",
73
74
  "@tanstack/intent": "0.3.7",
74
75
  "@types/figlet": "1.7.0",
75
76
  "@types/node": "22.20.1",
@@ -79,6 +80,7 @@
79
80
  "knip": "6.32.2",
80
81
  "oxfmt": "0.65.0",
81
82
  "oxlint": "1.83.0",
83
+ "oxlint-plugin-react-doctor": "0.9.14",
82
84
  "oxlint-tsgolint": "7.0.2001",
83
85
  "sherif": "1.13.0",
84
86
  "tsdown": "0.22.14",
@@ -107,7 +109,9 @@
107
109
  "test:build": "node scripts/test-build.ts",
108
110
  "test:coverage": "vitest run --coverage",
109
111
  "test:dist": "pnpm run build && pnpm run test:build && pnpm run test:smoke",
112
+ "test:presets": "vitest run --project presets",
110
113
  "test:smoke": "node scripts/test-smoke.ts",
114
+ "test:unit": "vitest run --project unit",
111
115
  "test:watch": "vitest",
112
116
  "version": "changeset version"
113
117
  }
@@ -22,25 +22,57 @@ non-interactive setup. Setup flags require `--non-interactive`, and at least one
22
22
  `--script` is required:
23
23
 
24
24
  ```shell
25
- npx adamantite init --non-interactive --script check --script fix --script format --typescript --agents
25
+ npx adamantite init --non-interactive --script check --script fix --typescript --agents
26
26
  ```
27
27
 
28
28
  Repeat `--script`, `--preset`, and `--editor` for multiple values. Available values are:
29
29
 
30
- - Scripts: `check`, `fix`, `format`, `analyze`, `check:monorepo`, `fix:monorepo`
31
- - Presets: `react`, `nextjs`, `vue`, `jest`, `vitest`, `node`, `antislop`; editors:
32
- `vscode`, `zed`
30
+ - Scripts: `check`, `fix`, `analyze`. In a detected monorepo, `analyze` also installs Sherif.
31
+ - Presets: `react`, `react-strict`, `react-doctor`, `nextjs`, `vue`, `jest`, `vitest`, `node`,
32
+ `strict`, `antislop`, `shadcn`;
33
+ editors: `vscode`, `zed`
33
34
  - Optional flags: `--typescript`, `--install-extensions`, `--github-actions`, `--agents`,
34
35
  `--overwrite-scripts`
35
36
 
36
- Only select options supported by the project. Presets and TypeScript require `check` or
37
- `fix`; extension installation requires an editor; monorepo scripts require a detected
38
- monorepo; `--github-actions` requires a CI-compatible script and a supported package
39
- manager (bun, deno, npm, pnpm, or yarn). Omitted boolean flags are disabled.
37
+ The `react-strict` preset needs no extra package. Its `adamantite/no-react-state-hooks` rule
38
+ reports React state, effect, and memoization hooks outside hook modules (`useCart.ts`,
39
+ `use-cart.ts`, and files under `hooks/`), and each message tells what to do instead. Do not move a hook call into
40
+ a hook module only to silence the rule: follow the message. Set the rule's `allow` option
41
+ when the project keeps hooks elsewhere.
42
+
43
+ The `strict` preset needs no extra package either. Its `adamantite/no-overzealous-destructuring`
44
+ rule reports destructuring nested more than 2 levels deep or object patterns with more than 5
45
+ properties; keep the object and read its members, or split the pattern. Do not raise
46
+ `maxDepth` or `maxProperties` only to silence a report. The preset also bans type assertions
47
+ outside tests.
48
+
49
+ The `react-doctor` preset requires the `react` preset and the `oxlint-plugin-react-doctor`
50
+ package, which `init` installs at a pinned version and doctor and `update` keep in sync. It
51
+ enables curated React Doctor rules for React state and effect misuse, such as
52
+ `no-effect-event-handler` and `no-fetch-in-effect`, and adds to `react-strict`. The package has
53
+ a modified MIT license: using it as machine learning training data or selling it as a
54
+ hosted product needs written permission from the copyright holder. Tell the user before you
55
+ select the preset.
56
+
57
+ The `shadcn` preset needs Tailwind v4 and the `@shadcn/lint` package, which `init` installs
58
+ at a pinned version and doctor and `update` keep in sync. The plugin finds components and
59
+ the theme through `components.json`. Without that file it looks in `components/ui` or
60
+ `src/components/ui` and discovers the stylesheet that imports Tailwind; when components
61
+ live elsewhere, set `settings.shadcn` (for example `{ ui: "@/ds" }`) in `oxlint.config.ts`.
62
+ The preset turns off the component-owned rules under `**/components/ui/**`; add the same
63
+ override for another component directory.
64
+
65
+ Adamantite requires TypeScript 7 or later as a dependency in the project's own
66
+ `package.json`. Without that entry, npm cannot add or update a managed plugin.
67
+
68
+ Only select options supported by the project. Presets and TypeScript require `check` or `fix`;
69
+ extension installation requires an editor; `--github-actions` requires a CI-compatible script
70
+ and a supported package manager (bun, deno, npm, pnpm, or yarn). Omitted boolean flags are
71
+ disabled.
40
72
 
41
73
  Existing package scripts whose commands differ from Adamantite's are kept and reported,
42
74
  not replaced; pass `--overwrite-scripts` to replace them. Custom flags can be forwarded
43
- to the Adamantite command after `--`, e.g. `adamantite monorepo -- --ignore-dependency tailwindcss`.
75
+ to the Adamantite command after `--`, e.g. `adamantite analyze -- --directory packages/app`.
44
76
 
45
77
  ## Daily workflow
46
78
 
@@ -49,25 +81,32 @@ Use the scripts written by `init` when available. Otherwise invoke the CLI direc
49
81
  ```shell
50
82
  adamantite check
51
83
  adamantite fix
52
- adamantite format
53
- adamantite format --check
54
84
  adamantite analyze
55
- adamantite monorepo
56
85
  ```
57
86
 
58
- - Use `check` for read-only lint and type-error validation.
59
- - Use `fix` for automatic oxlint fixes. Add `--suggested`, `--dangerous`, or `--all` only
60
- with explicit permission after reviewing their impact.
61
- - Use `format` to write formatting changes and `format --check` for read-only CI checks.
62
- - Use `analyze` for unused dependencies, exports, and files. `analyze --fix` may remove
63
- files, so inspect findings before using it.
64
- - Use `monorepo` to inspect workspace dependency consistency and `monorepo --fix` to fix it.
65
-
66
- To pass arguments to Knip, Oxlint, Oxfmt, or Sherif, place them after `--`:
87
+ - Use `check` for read-only formatting, lint, and type-error validation. `check --only lint` or `check --only format`
88
+ runs one stage.
89
+ - Use `fix` for automatic Oxlint fixes followed by Oxfmt formatting. Add `--suggested`, `--dangerous`, or `--all` only
90
+ with explicit permission after reviewing their impact. `fix --only lint` or `fix --only format` runs one stage.
91
+ - Use `analyze` for unused dependencies, exports, and files. In a monorepo it first checks
92
+ workspace dependency consistency with Sherif. `analyze --only monorepo` or
93
+ `analyze --only unused` runs one stage. `analyze --fix` may remove files, so inspect
94
+ findings before using it. Sherif refuses to fix when `CI` is set, so in a monorepo
95
+ `analyze --fix` fails there; use `analyze --only unused --fix` for the Knip fixes.
96
+ - In a monorepo, `analyze --fix` needs a terminal when Sherif must choose between versions.
97
+ Without a terminal, set `"sherif": { "select": "highest" }` in the root `package.json`, or
98
+ use `analyze --only unused --fix` to skip Sherif.
99
+ - `analyze` runs Sherif without flags. Put Sherif exceptions in the `sherif` field of the root
100
+ `package.json` (camelCase CLI options, e.g. `"ignoreDependency": ["tailwindcss"]`).
101
+
102
+ To pass arguments to Knip, Oxlint, or Sherif, place them after `--`. For `check` and `fix` they
103
+ go to Oxlint. For `analyze` they go to Knip, or to the stage that `--only` selects, so a Sherif
104
+ flag needs `--only monorepo`. For a permanent Sherif setting, use the `sherif` field instead.
67
105
 
68
106
  ```shell
69
107
  adamantite check src -- --deny-warnings
70
108
  bun run analyze -- -- --directory packages/app
109
+ adamantite analyze --only monorepo -- --ignore-package package-a
71
110
  ```
72
111
 
73
112
  ## Diagnose and repair
@@ -102,6 +141,6 @@ findings. Follow those findings and review the resulting diff.
102
141
  - New target project: `init`, then run the configured checks.
103
142
  - Suspected drift or broken setup: run `doctor` and follow its findings.
104
143
  - Existing project upgrading Adamantite: `update`, then the doctor sequence.
105
- - Code-quality failure: choose `check`, `format --check`, `analyze`, or `monorepo` based on
106
- the failing subsystem; do not reinitialize the project.
144
+ - Code-quality failure: choose `check` or `analyze` based on the failing subsystem; do not
145
+ reinitialize the project.
107
146
  - Unknown option or behavior: run `adamantite <command> --help` before guessing.