@drkmttr/adhere 0.21.2 → 0.24.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drkmttr/adhere",
3
- "version": "0.21.2",
3
+ "version": "0.24.0",
4
4
  "private": false,
5
5
  "description": "Lints a repository against the rules in .adhere/, judged by Jev.",
6
6
  "license": "MIT",
@@ -34,11 +34,14 @@
34
34
  "typecheck": "tsc --noEmit && bun run lint",
35
35
  "build": "bun build --compile --minify --sourcemap --define ADHERE_BUILD=\"\\\"$(git describe --tags --dirty --always)\\\"\" src/main.ts --asset ./presets --outfile dist/adhere",
36
36
  "build:npm": "bun scripts/npm-packages.ts build",
37
+ "oxlint:catalog": "bun scripts/oxlint-catalog.ts",
38
+ "oxlint:catalog:check": "bun scripts/oxlint-catalog.ts --check",
37
39
  "release": "release-it",
38
40
  "prepare": "vp config"
39
41
  },
40
42
  "dependencies": {
41
- "effect": "4.0.3"
43
+ "effect": "4.0.3",
44
+ "mdast-util-from-markdown": "^2.1.0"
42
45
  },
43
46
  "devDependencies": {
44
47
  "@effect/platform-bun": "4.0.3",
@@ -90,10 +93,10 @@
90
93
  "--force"
91
94
  ],
92
95
  "optionalDependencies": {
93
- "@drkmttr/adhere-darwin-arm64": "0.21.2",
94
- "@drkmttr/adhere-darwin-x64": "0.21.2",
95
- "@drkmttr/adhere-linux-arm64": "0.21.2",
96
- "@drkmttr/adhere-linux-x64": "0.21.2",
97
- "@drkmttr/adhere-win32-x64": "0.21.2"
96
+ "@drkmttr/adhere-darwin-arm64": "0.24.0",
97
+ "@drkmttr/adhere-darwin-x64": "0.24.0",
98
+ "@drkmttr/adhere-linux-arm64": "0.24.0",
99
+ "@drkmttr/adhere-linux-x64": "0.24.0",
100
+ "@drkmttr/adhere-win32-x64": "0.24.0"
98
101
  }
99
102
  }
package/presets/README.md CHANGED
@@ -2,9 +2,17 @@
2
2
 
3
3
  Every rule in adhere's built-in presets is listed below, with a one-line
4
4
  summary. Each id links to the full rule, and is the id that reports,
5
- `overrides`, and `adhere-ignore` comments use. [Presets](../README.md#presets)
5
+ `overrides`, and `adhere-ignore` comments use. [Presets](https://dm.sh/adhere/presets/)
6
6
  says how to name a preset or one of its topics.
7
7
 
8
+ Some rules declare the context they need with `reads`, such as
9
+ `["symbols", "references"]` for a rule that compares declarations across
10
+ files. Enable `typeAware: true` or `--type-aware` for the compiler's
11
+ `symbols` and `references`. Without it, a bundled rule that declares either is
12
+ left out of the run, so a preset never judges with less context than its rules
13
+ declare. A project's own rule is different: it still runs, and lint warns that
14
+ it may be less accurate until a declared read is enabled.
15
+
8
16
  ## typescript
9
17
 
10
18
  - **Source.** The TypeScript Handbook and the Google TypeScript Style Guide.
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  description: When a runtime is split into a Tag class and a make Layer, other runtimes must import and bind only the Tag, and only the Stack provides the Layer; another Worker or Function must never import or provide that implementation Layer.
3
+ reads: ["symbols", "references"]
3
4
  ---
4
5
 
5
6
  ## Must
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  description: A primitive with semantic meaning in a domain type, such as an id, email, URL, or port in a service's contract, an event, or a stored record, must be declared as a branded schema, never as a bare string or number.
3
+ reads: ["symbols", "references"]
3
4
  ---
4
5
 
5
6
  Two ids of different things are both strings, so nothing stops one being
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  description: Variants of a domain type, alternatives that carry fields and that a service returns, stores, or decodes, must be tagged schemas such as Schema.TaggedClass or Schema.TaggedStruct members of a Schema.Union, never TypeScript object types joined by a tag field.
3
+ reads: ["symbols", "references"]
3
4
  ---
4
5
 
5
6
  A tagged schema decodes each variant at the boundary and lets `Match.tag`
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  description: A type that describes the same data as another type, a schema, a function, or a constant should be derived from it, with a utility type such as `Pick`, with `typeof` or `keyof`, or as a schema's inferred type, and should not restate its fields or values by hand.
3
3
  level: warning
4
+ reads: ["symbols", "references"]
4
5
  ---
5
6
 
6
7
  ## Should
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: adhere
3
- description: Reference for adhere, the linter for coding conventions a normal linter cannot check, judged by TypeSafe AI's Jev. Covers its commands, the Markdown rule format and how to word a rule, the config, presets, shared rules, comments and suppressions, the cache, reading a report, and tuning a noisy rule. Use when writing or editing adhere rules, configuring adhere, reading its findings, or answering questions about it. To set adhere up in a repo, use adhere-setup; to fix findings, adhere-fix.
3
+ description: Reference for adhere, the linter for coding conventions a normal linter cannot check, judged by TypeSafe AI's Jev. Covers its commands, the Markdown rule format and how to word a rule, the config, presets, shared repos and lint packages, comments and suppressions, the cache, reading a report, and tuning a noisy rule. Use when writing or editing adhere rules, configuring adhere, reading its findings, or answering questions about it. To set adhere up in a repo, use adhere-setup; to fix findings, adhere-fix.
4
4
  ---
5
5
 
6
6
  # adhere
@@ -19,11 +19,13 @@ first rules, first lint, and CI, and adhere-fix verifies and fixes findings.
19
19
  ## Commands
20
20
 
21
21
  ```sh
22
- adhere init [--force] # .adhere/config.ts, two example rules, and the dev dependency
23
- adhere init --shared org/repo # a repo of rules other repos install, with CI and an alchemy stack
22
+ adhere init [--force] # config, tsconfig, two example rules, and the dev dependency
23
+ adhere init --shared org/repo # rules, instructions, lint package, CI, and an alchemy stack
24
+ adhere postinstall # render .adhere/instructions/**/*.md into AGENTS.md
24
25
  adhere login # save a TypeSafe AI API key; TYPESAFE_API_KEY takes precedence
25
26
  adhere validate # rule wording, rules a linter could check, contradictions
26
- adhere rules create [idea] # wizard: claude, codex, or omp drafts a RULE.md or RULE.ts from excerpts you judge; org rules pushed for a PR
27
+ adhere rules create [idea] # native rule, approved plugin, or rule file; org PR
28
+ adhere rules readme [org/repo] # refresh a shared repo README's managed guide
27
29
  adhere lint # judge the working directory; exit 1 on an error finding
28
30
  adhere lint --limit 0 # compiler-free counts/cache plan; no budgeting
29
31
  adhere lint --limit 100 # first 100 files needing requests; all pending rules per file
@@ -33,16 +35,17 @@ adhere lint --preset effect # add a built-in rule set; the config becomes o
33
35
  adhere lint --filter 'src/**' # read only matching files; repeat, and ! to leave out
34
36
  adhere lint --threshold 0.9 # report at another threshold from the cache; judges nothing again
35
37
  adhere lint --deny-warnings # fail on warnings too
36
- adhere list org/repo # the rules in a GitHub repo's .adhere/rules/
37
- adhere install org/repo[/topic[/rule]][#ref] # copy them into .adhere/rules/org/repo/
38
+ adhere list org/repo # the rules in a GitHub repo's .adhere/rules/, with descriptions
39
+ adhere install org/repo[/topic[/rule]][#ref] [--all] # copy instructions and rules into .adhere/
38
40
  adhere skill [docs|setup|fix] # print a skill
39
41
  ```
40
42
 
41
43
  `adhere <command> --help` lists every flag. Version, help, skills, `validate`,
42
44
  and planning with `--limit 0` do not start the native compiler. Default `lint`
43
- is compiler-free. With `typeAware: true` or `--type-aware`, it starts the compiler
44
- only during execution, after confirmation, for pending judgments or cached
45
- findings still needing location.
45
+ is compiler-free, and leaves out bundled preset rules that declare native
46
+ reads. With `typeAware: true` or `--type-aware`, it includes them and starts the
47
+ compiler only during execution, after confirmation, for pending judgments or
48
+ cached findings still needing location.
46
49
 
47
50
  `--limit` now counts files, not rule checks; use the same flag without an extra
48
51
  unit selector. Each request-bearing file consumes one slot for all its pending
@@ -59,11 +62,11 @@ native-context caveat before execution.
59
62
  A repo's rules live in `.adhere/rules/`, a directory per rule holding a
60
63
  `RULE.md`, or a `RULE.ts` (below). The directory's path is the rule id:
61
64
  `.adhere/rules/data/brand-ports/RULE.md` is `data/brand-ports`. Other files in
62
- a rule's directory are not rules. A `*.md` rule elsewhere in `.adhere/`, as
63
- rules were once written, refuses the run with where it goes to keep its id.
64
- A `.adhere/rules/` in a subdirectory holds rules for that
65
- subtree only, and the most specific rule with an id wins. `cache/` and the
66
- config in `.adhere/` are not rules.
65
+ a rule's directory are not rules, and only `RULE.md` or `RULE.ts` in a rule's
66
+ directory defines one: other Markdown in `.adhere/`, such as
67
+ `.adhere/instructions/`, is ignored. A `.adhere/rules/` in a subdirectory holds
68
+ rules for that subtree only, and the most specific rule with an id wins.
69
+ `cache/` and the config in `.adhere/` are not rules.
67
70
 
68
71
  ````md
69
72
  ---
@@ -118,12 +121,31 @@ Front matter besides `description`, all optional:
118
121
  - `tests: only` for a rule about tests, `tests: include` for one that holds in
119
122
  tests too. Otherwise rules skip `.test` and `.spec` files and files under
120
123
  `test/`, `tests/`, `__tests__/`, and `fixtures/`.
124
+ - `include: {"paths":["src/**"],"content":["from\\s+[\"']effect[\"']"]}`
125
+ and `exclude: {"paths":["**/generated/**"],"content":["@generated"]}`,
126
+ JSON objects on one line, filter files locally before requests or cached
127
+ findings. TypeScript rules use ordinary objects with the same fields.
128
+ Paths are repository-root-relative globs, even for nested rules. Content
129
+ patterns are JavaScript regex source strings without delimiters or flags,
130
+ matched against original text including comments. Invalid regex refuses
131
+ loading. Entries within each list are alternatives; every nonempty include
132
+ list must match, and any exclude match vetoes the file. Empty lists impose
133
+ no restriction. Existing scope, test settings, and ignores still apply.
134
+ An import regex is not import resolution: check comments, alternative import
135
+ forms, and re-exports before narrowing a rule and silently missing violations.
121
136
  - `appliesTo: ["a declared union type or schema"]` and
122
137
  `excludeIf: ["a type that mirrors a third-party format"]`, JSON arrays on
123
138
  one line, scope a rule without widening its description: a finding stands
124
139
  only when Jev says the code that breaks the rule is as every `appliesTo`
125
140
  describes and as no `excludeIf` does.
126
141
 
142
+ - `reads: ["symbols", "references"]` declares the context the rule needs, a
143
+ JSON array on one line. It enables nothing: the config's `reads` and
144
+ `typeAware` still choose what this run sends. A project's own rule still
145
+ runs and lint warns that it may be less accurate until a declared read is
146
+ enabled; a bundled preset rule that declares either native read is left out
147
+ unless `typeAware` is on.
148
+
127
149
  A rule's directory can hold a `RULE.ts` instead, which default-exports
128
150
  `defineRule({...})` from `@drkmttr/adhere`, with the fields a config's inline
129
151
  rule takes:
@@ -174,9 +196,13 @@ Opting in with `typeAware: true` or `--type-aware` enables both `symbols` and
174
196
  `references`, regardless of `reads`. Legacy native-reader entries in `reads`
175
197
  alone do not enable the compiler; leaving them out does not disable it when
176
198
  type awareness is on. Code alone requires `reads: []` and type awareness off.
177
- Presets do not own or override this selection. A rule-level `reads`, in
178
- Markdown front matter, `defineRule`, or an inline rule, has no effect. The
179
- context maps are:
199
+ Presets do not own or override this selection. A rule may declare what it
200
+ needs with `reads`, in Markdown front matter, `defineRule`, or an inline rule;
201
+ the declaration enables nothing. A bundled preset rule that declares `symbols`
202
+ or `references` is left out unless `typeAware` is on, so it is never judged
203
+ with less than it declares. A project's own rule still runs, and a declared
204
+ read the run does not enable is a warning that it may be less accurate until
205
+ enabled. The context maps are:
180
206
 
181
207
  - `workspacePackages`: only packages matched by this file's literal imports,
182
208
  as `{ canonicalPackageName: directory }`. Subpaths match the owning package;
@@ -302,17 +328,170 @@ https://github.com/darkmatter/adhere/tree/main/presets. A preset leaves out
302
328
  what that ecosystem's linters check exactly. Quiet a preset rule with
303
329
  `overrides` rather than copying it.
304
330
 
305
- ## Shared rules
331
+ ## Linter-checkable rules
332
+
333
+ A convention a linter can decide exactly does not belong in a `RULE.md`.
334
+ `adhere rules create` asks the coding agent for one of three routes:
335
+
336
+ - **A native Oxlint rule.** The agent answers `linter` as a plain rule-id
337
+ string, or null, and adhere checks it against its bundled Oxlint catalog at
338
+ decode time; unknown ids are rejected. It also supplies the rule's exact
339
+ option values, which the config puts after the severity: a rule whose
340
+ defaults enforce the idea prints `"error"`, while an option-dependent rule
341
+ prints `["error", ...options]`, because bare `"error"` would enforce
342
+ nothing. Banning lodash with `no-restricted-imports` becomes
343
+ `["error", { "paths": [{ "name": "lodash" }] }]`. A project rule prints the
344
+ exact config; an organization rule enables it in the shared `oxlint/rules.json`
345
+ and pushes a branch for a pull request with the README guide refreshed. In a
346
+ shared repo itself, "this project" writes `oxlint/rules.json` locally instead
347
+ of printing the config. A rule from a plugin Oxlint leaves off
348
+ by default also needs that plugin in `plugins`, whose list replaces Oxlint's
349
+ default set. A type-aware rule needs
350
+ `options.typeAware` and `oxlint-tsgolint`, which Vite+ includes.
351
+ - **A custom Oxlint JS plugin.** Only when the convention is visible in the
352
+ syntax tree and scopes: a JS plugin cannot see TypeScript types, other
353
+ files, or another parser's AST. The plugin must be plain ESM, default-export
354
+ its object, and import nothing; the agent reports each diagnostic's message
355
+ literally, so its valid and invalid fixtures can match exact diagnostics.
356
+ adhere shows the plugin and fixtures, and runs nothing until the user
357
+ approves. It then runs the fixtures with the project's own Oxlint RuleTester,
358
+ through `oxlint/plugins-dev` or `vite-plus/lint/plugins-dev`, so the project
359
+ needs its own Oxlint or Vite+ and Node 22 or newer on `PATH`; Oxlint's
360
+ RuleTester refuses Bun and older Node, there is no Bun fallback, and without
361
+ it nothing is written and no repair is attempted. adhere bundles no Oxlint.
362
+ One failure goes back to the agent to repair; the user approves the repaired
363
+ plugin, then it runs again. Nothing is written unless the tests pass.
364
+ - **An adhere rule**, for judgment, facts outside the file, or types.
365
+
366
+ Whichever route it takes, the draft is trialled on real files before anything
367
+ is written: the first 100 applicable files by default, the entire repo, or a
368
+ number the user types, with the plan's known cost shown first. The authoring
369
+ agent reads the saved report in `.adhere/trial/` and the source it names. After
370
+ the first completed adhere trial, it is explicitly asked whether path/content
371
+ filters would help: recommend exact entries or explain why none are needed,
372
+ including the risk of hiding real violations. The user accepts the draft,
373
+ reviews findings, or asks for a revision. Findings and examples take optional
374
+ notes; revisions receive those notes and the filter recommendation, are
375
+ re-approved, and rerun the same sample. Newly filtered files are reported as
376
+ skipped, not replaced. The initial cap counts files that pass filters.
377
+ Skipping is allowed, but the write prompt then says `(not trialled)` and the
378
+ closing line says `Not trialled: …`; nothing is called validated on a skipped,
379
+ partial, or stale run. Native settings and approved plugins are trialled too,
380
+ without adhere-only filter recommendations.
381
+
382
+ A project plugin and its checked-in test go to
383
+ `.adhere/oxlint/<plugin>.mjs` and `.adhere/oxlint/<plugin>.test.mjs`; the
384
+ `.mjs` keeps ESM in a `type: commonjs` package. adhere prints the config to add
385
+ to `oxlint.config.ts` or Vite+'s lint block:
386
+
387
+ ```json
388
+ {
389
+ "jsPlugins": ["./.adhere/oxlint/no-foo.mjs"],
390
+ "rules": { "no-foo/no-foo": "error" }
391
+ }
392
+ ```
393
+
394
+ Run the test with Node 22 or newer: `node .adhere/oxlint/no-foo.test.mjs`.
395
+
396
+ A native project rule without options prints only its id:
397
+
398
+ ```json
399
+ { "rules": { "typescript/no-explicit-any": "error" } }
400
+ ```
401
+
402
+ A shared repo keeps plugins in `oxlint/plugins/` as `.js`, with `.test.js` beside
403
+ them, because the shared package is `type: module`; it registers them in
404
+ `oxlint/rules.json`. The catalog follows adhere's toolchain, not the project's
405
+ installed Oxlint, so a suggestion may be missing on an older Oxlint or a newer
406
+ rule may be absent from the catalog; the plugin tests always use the project's
407
+ Oxlint. `adhere validate` reports rules left over that a linter could check.
408
+
409
+ ## Shared repos
410
+
411
+ An organization keeps its instructions, rules, and lint settings in one repo,
412
+ scaffolded by `adhere init --shared org/repo`. Its `README.md` carries a
413
+ managed guide between `<!-- adhere:rules:start -->` and
414
+ `<!-- adhere:rules:end -->`: a contents list nesting every rule under its
415
+ topic, the rules grouped by topic with each bold statement and rule id opening
416
+ that rule's `RULE.md` verbatim: examples and explanation, then a source link
417
+ under a stable permalink, every enabled native setting in `oxlint/rules.json`
418
+ with its severity, and each custom plugin's enabled rules, linked to
419
+ `oxlint/plugins/<name>.js` when that is a local file. A `RULE.ts` is linked as
420
+ TypeScript source only: the guide never reads or runs it, and plugin code is
421
+ never loaded. Refresh it with `adhere rules readme [org/repo]` after renaming
422
+ or deleting a rule; it preserves prose outside the block, omits off lint
423
+ settings, and refuses malformed markers without writing.
424
+ `adhere rules create` refreshes it for local writes and stages it with
425
+ shared-repo branches.
426
+
427
+ The command chooses its target by argument, then by where it runs. A named
428
+ `org/repo` wins. Otherwise a working directory that is itself a shared repo
429
+ (`package.json` exporting `./oxlint`) is written in place. Anywhere else,
430
+ adhere reads the checkout's GitHub `origin` (`https`, `ssh`, or scp form) and
431
+ infers `<owner>/standards` (`acme/app` -> `acme/standards`, never `acme/app`);
432
+ a fork's owner is the fork's. That remote target is cloned at its default
433
+ branch, the changed block is committed alone on a fresh
434
+ `adhere/readme-<timestamp>` branch, pushed, and printed with a compare link.
435
+ No pull request is opened, the consumer checkout and the default branch stay
436
+ untouched, and an unchanged guide prints `No change, so nothing was pushed.`
437
+ without committing. A rules-only remote works, since the `./oxlint` export is
438
+ only required for the local write. A missing or non-GitHub `origin` fails and
439
+ names `adhere rules readme org/repo`; name the repo explicitly when the
440
+ organization's repo is not `<owner>/standards`.
441
+
442
+ Other repos copy the first two with `adhere install`:
443
+
444
+ ```sh
445
+ adhere list org/repo # rules and descriptions; instructions are not listed
446
+ adhere install org/repo # choose instruction files, then rules, at a terminal
447
+ adhere install org/repo --all # everything, without a terminal (CI)
448
+ adhere install org/repo/data # choose among the data topic's rules
449
+ adhere install org/repo/data/ports # exactly that rule, without asking
450
+ adhere install org/repo#v1.0.0 --all # everything at tag v1.0.0
451
+ ```
452
+
453
+ A bare repo chooses from instruction files and rules. A topic chooses only the
454
+ rules under it, never instructions, even a topic named `instructions`. An exact
455
+ rule id with no rule deeper under it is copied directly. Already-installed
456
+ items are preselected. A rerun skips what exists; `--force` replaces existing
457
+ files and rule directories, and deselecting never deletes them.
458
+
459
+ Instruction files land in `.adhere/instructions/org/repo/<path>`. Rules keep
460
+ their whole directory in `.adhere/rules/org/repo/`, so `data/brand-ports`
461
+ becomes the rule `org/repo/data/brand-ports`, and a copied `RULE.ts` runs
462
+ whenever lint does. Copies are the repo's own from then on. `adhere postinstall`
463
+ renders the instruction files into `AGENTS.md`; files whose path has at least
464
+ three segments come first (copied files, and local files two directories deep),
465
+ then files directly in the instructions directory, then files one directory
466
+ deep, each group sorted by path.
467
+
468
+ The shared `oxlint/` package is not copied. Pin it as a git dependency at a
469
+ repo-wide tag (there are no per-rule or per-package tags; one tag covers rules,
470
+ instructions, and lint together), then spread it into Vite+ or plain Oxlint:
471
+
472
+ ```ts
473
+ // vite.config.ts
474
+ import sharedLint from "@org/repo/oxlint";
475
+ import { defineConfig } from "vite-plus";
476
+
477
+ export default defineConfig({
478
+ lint: { ...sharedLint, jsPlugins: [...sharedLint.jsPlugins, "./.adhere/oxlint/local.mjs"] },
479
+ });
480
+ ```
481
+
482
+ ```ts
483
+ // oxlint.config.ts: oxlint -c oxlint.config.ts
484
+ import sharedLint from "@org/repo/oxlint";
485
+
486
+ export default { ...sharedLint };
487
+ ```
488
+
489
+ `oxlint/index.js` reads `oxlint/rules.json` and resolves plugin paths relative to
490
+ the package. Paths a project adds stay relative to its own config; project
491
+ plugins live in `.adhere/oxlint/`.
306
492
 
307
- An organization keeps rules in one repo's `.adhere/rules/`, scaffolded by
308
- `adhere init --shared org/repo`, and other repos copy them in with
309
- `adhere install org/repo`, or one topic or rule with
310
- `adhere install org/repo/<topic>[/<rule>]`. Each rule's whole directory lands
311
- in `.adhere/rules/org/repo/`, so `data/brand-ports` becomes the rule
312
- `org/repo/data/brand-ports`, and a copied `RULE.ts` runs whenever lint does.
313
- They are the repo's own rules from then on; a second install skips existing
314
- rules unless `--force`, which replaces their directories, local edits and all. The source is cloned with git,
315
- so a private repo needs git's credentials for GitHub.
493
+ The source is cloned with git, so a private repo needs git's credentials for
494
+ GitHub.
316
495
 
317
496
  ## Comments and suppressions
318
497
 
@@ -347,9 +526,10 @@ re-judges that rule; a threshold change re-judges nothing.
347
526
 
348
527
  Dependencies, related source, reader selection, shared read-context contents,
349
528
  and hook-owned external data are deliberately not fingerprinted. If `A.ts` imports
350
- `B.ts`, changing `B.ts` does not invalidate `A.ts`. Changing config `reads` or
351
- `typeAware` does not refresh its cached answers either. This keeps planning cheap but accepts
352
- stale dependency/context judgments.
529
+ `B.ts`, changing `B.ts` does not invalidate `A.ts`. Changing the config's
530
+ `reads` or `typeAware` re-judges every rule once; changing what those reads
531
+ find does not. This keeps planning cheap but accepts stale dependency/context
532
+ judgments.
353
533
 
354
534
  `planAudit` reads/counts/hashes code, checks the cache, and makes code plus cheap
355
535
  workspace estimates without compiler or `SourceReads` calls. It applies `--limit`
@@ -422,6 +602,10 @@ Per rule, read its findings and decide:
422
602
  - Many hits at 0.80 to 0.90, mostly not violations: the description is too
423
603
  broad. Narrow it, or scope it with `appliesTo` or `excludeIf`, before
424
604
  raising its `threshold`: a threshold hides, a sentence explains.
605
+ - Hits in irrelevant files: consider local `include`/`exclude` path or content
606
+ filters before paying to judge them. Verify the boundary against real
607
+ violations and unflagged files; an absent import does not prove the library
608
+ is unused when the project uses wrappers or re-exports.
425
609
  - Hits on code that follows the pattern through another API: the `must`
426
610
  block is too specific. Use the repo's most general correct example.
427
611
  - Right less than half the time and not fixable by wording: `level: warning`,
@@ -163,7 +163,7 @@ rules, and in the report give the user the counts and suggest one of:
163
163
  `{ threshold: 0.9 }`;
164
164
  - for a project rule, rewording it to say what code it is about and what is
165
165
  out of scope, following the rule writing tips:
166
- https://github.com/darkmatter/adhere#rule-writing-tips.
166
+ https://dm.sh/adhere/writing-good-rules/#rule-writing-tips.
167
167
 
168
168
  ## 7. Report
169
169
 
@@ -37,12 +37,14 @@ wherever this skill says `adhere`, run it the manager's way, as in
37
37
 
38
38
  With pnpm, at a workspace root, add `--ignore-workspace-root-check`.
39
39
 
40
- Default lint reads code and `workspacePackages` without starting a compiler.
40
+ Default lint reads code and `workspacePackages` without starting a compiler,
41
+ and leaves out bundled preset rules that declare `symbols` or `references`.
41
42
  `typeAware` defaults to `false`; `typeAware: true` or `--type-aware` opts into
42
- both `symbols` and `references`, regardless of `reads`. `reads` now controls
43
- workspace context only, defaulting to `["workspacePackages"]`; legacy native
44
- reader entries alone do not opt in. Code alone uses `reads: []` with type
45
- awareness off. Explain the extra context/cost and **Ask** before opting in.
43
+ both `symbols` and `references`, regardless of `reads`, and includes those
44
+ rules. `reads` now controls workspace context only, defaulting to
45
+ `["workspacePackages"]`; legacy native reader entries alone do not opt in.
46
+ Code alone uses `reads: []` with type awareness off. Explain the extra
47
+ context/cost and **Ask** before opting in.
46
48
 
47
49
  With type awareness enabled, native TypeScript starts only during execution,
48
50
  after confirmation, when pending judgments or cached findings needing location
@@ -91,7 +93,10 @@ from and the evidence.
91
93
 
92
94
  Keep only conventions that need judgment: about intent, shape, or placement.
93
95
  Drop any a regex, an import check, or the type checker could flag every time,
94
- and note where it belongs instead. Drop any a preset below already covers.
96
+ and note where it belongs instead: a native Oxlint rule, or a convention a
97
+ small syntax-only Oxlint JS plugin could decide. `adhere rules create` routes
98
+ those two, and leaves the rest to adhere. Drop any a preset below already
99
+ covers.
95
100
 
96
101
  **Presets.** Match the stack: `typescript` for TypeScript, `react` for React,
97
102
  `security` for code that handles secrets, SQL, shell commands, or paths,
@@ -100,9 +105,10 @@ and note where it belongs instead. Drop any a preset below already covers.
100
105
  https://github.com/darkmatter/adhere/tree/main/presets, to describe them and
101
106
  to avoid drafting duplicates.
102
107
 
103
- **A shared repo.** **Ask** whether the organization keeps adhere rules in a
104
- repo. If it does, `adhere list org/repo` prints its rules with their
105
- descriptions.
108
+ **A shared repo.** **Ask** whether the organization keeps adhere instructions,
109
+ rules, or lint settings in a repo. If it does, `adhere list org/repo` prints
110
+ its rules with their descriptions; its instruction files and `oxlint/` package
111
+ are separate, and `adhere install` offers the instruction files too.
106
112
 
107
113
  ## 3. Draft the repo's rules
108
114
 
@@ -131,7 +137,8 @@ Presets
131
137
  From acme/standards
132
138
  5. data/brand-ports A port must be a branded, range-checked integer, never a bare number.
133
139
  Dropped as a linter's job
134
- - "no default exports": eslint import/no-default-export
140
+ - "no default exports": native oxlint import/no-default-export
141
+ - "no local date formatting": custom syntax-only oxlint plugin
135
142
  ```
136
143
 
137
144
  For a preset, offer the whole preset or its topics. Show a draft's full text
@@ -152,8 +159,30 @@ the wording of any. Revise drafts the user wants changed and show them again.
152
159
  workspace context, not native maps. Only replace `reads` if the user chooses
153
160
  to limit workspace context. If requested type awareness lacks compiler resources,
154
161
  use the package launcher or a checkout; do not silently turn it off.
155
- - Install chosen shared rules: `adhere install org/repo/<id>` for each, or
156
- `adhere install org/repo` for all of them. They land in `.adhere/rules/org/repo/`.
162
+ - Copy chosen shared instructions and rules: run `adhere install org/repo` and
163
+ let the user pick at the terminal, or add `--all` only when they chose
164
+ everything (the non-interactive path). Name an exact rule to copy it without
165
+ asking. Instruction files land in `.adhere/instructions/org/repo/` and rules
166
+ in `.adhere/rules/org/repo/`; `adhere postinstall` renders the instructions,
167
+ copied files first.
168
+ - **Ask** before adding the shared repo's lint package: pin it as a git
169
+ dependency at the repo-wide tag, then spread `@org/repo/oxlint` into the
170
+ project's Vite+ lint block or `oxlint.config.ts`. It is a package, not an
171
+ `adhere install` copy. Project plugins live in `.adhere/oxlint/`.
172
+ - For a chosen linter candidate, run `adhere rules create "<convention>"` and
173
+ follow its route. A native Oxlint rule must be one of adhere's catalog ids;
174
+ an unknown id is rejected. It must carry the rule's exact option values when
175
+ it takes options; adhere prints `["error", ...options]`, never a bare
176
+ `"error"` that would enforce nothing. It prints the config for the user's lint
177
+ config, and adhere does not edit it. A custom syntax-only plugin is shown with
178
+ valid and invalid examples and runs nothing until approved, then its tests
179
+ use the project's Oxlint through `oxlint/plugins-dev` or
180
+ `vite-plus/lint/plugins-dev`. That needs its own Oxlint or Vite+ and Node 22
181
+ or newer on `PATH`; Oxlint's RuleTester refuses Bun and older Node, and
182
+ without it nothing is written and no repair is attempted. One failed run goes
183
+ back to the agent for repair; the user approves the repaired plugin, and it
184
+ runs again. Project files go to `.adhere/oxlint/` as `.mjs`; shared files go
185
+ to `oxlint/plugins/` as `.js` on a branch for a pull request.
157
186
  - Add `exclude` globs for generated or vendored code the default skips miss.
158
187
 
159
188
  ## 6. Validate
@@ -164,8 +193,10 @@ adhere validate
164
193
 
165
194
  It reports rules worded against the tips, rules a linter could check, and
166
195
  contradictions between rules that apply to the same files. Fix the wording
167
- yourself. For a linter-checkable rule or a contradiction, **Ask** the user
168
- which to keep, and apply it. Run it again until it reports no contradictions.
196
+ yourself. For a linter-checkable rule, **Ask** whether to move it to a native
197
+ Oxlint rule or an approved syntax-only plugin, or keep it as an adhere rule.
198
+ For a contradiction, **Ask** which rule to keep and apply the change. Run it
199
+ again until it reports no contradictions.
169
200
 
170
201
  ## 7. The first lint
171
202
 
@@ -402,9 +433,11 @@ This needs the cache committed on the base branch first.
402
433
 
403
434
  Tell the user:
404
435
 
405
- - the rules imported, by source, and where they are;
436
+ - the rules imported, by source, and where they are, plus any shared
437
+ instruction files copied and the `oxlint/` package pinned;
406
438
  - the presets and shared repo named in the config or installed;
407
- - the candidates dropped as a linter's job, with where each belongs;
439
+ - the candidates routed to native Oxlint, an approved plugin, or an adhere
440
+ rule, and which config registers each;
408
441
  - the last lint's findings, and what was fixed, made a warning, or suppressed;
409
442
  - the CI workflow and whether the secret is set;
410
443
  - what is left for them: pushing, setting the secret, fixing remaining findings.