eslint-plugin-nextfriday 3.2.1 → 4.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # eslint-plugin-nextfriday
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#124](https://github.com/next-friday/eslint-plugin-nextfriday/pull/124) [`be12809`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/be128098e2af102a153c3d01546a2921518f4b96) Thanks [@joetakara](https://github.com/joetakara)! - add two JSX rules targeting unnecessary wrapper noise (the "Divitis" anti-pattern):
8
+ - `no-ghost-wrapper` flags bare `<div>` / `<span>` elements that carry no meaningful attributes. Self-closing variants are checked the same way. The `key` prop alone does not silence the rule, since `key` carries no structural intent. Any other attribute — `className`, `style`, `id`, `ref`, `role`, `aria-*`, `data-*`, `tabIndex`, event handlers, or spread attributes — is considered meaningful and lets the element pass.
9
+ - `no-redundant-fragment` flags Fragments (`<>...</>`, `<Fragment>`, `<React.Fragment>`) that wrap zero or one child. JSX text consisting only of whitespace is not counted. Long-form Fragments carrying a `key` attribute are exempt, since `key` is the canonical reason long-form Fragment exists (the shorthand `<>...</>` cannot accept attributes).
10
+
11
+ Both rules are report-only — no autofix is provided so authors retain control over which structural alternative (Fragment, semantic element, unwrapping the children) best fits the surrounding code. Both are added to the JSX rule tier and ship in the `react`, `react/recommended`, `nextjs`, and `nextjs/recommended` presets at `warn` and `error` severity respectively.
12
+
13
+ ## 4.0.0
14
+
15
+ ### Major Changes
16
+
17
+ - [#122](https://github.com/next-friday/eslint-plugin-nextfriday/pull/122) [`a9c5237`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/a9c52375dfe7d7064ead37c611c680e4c8d6be9f) Thanks [@joetakara](https://github.com/joetakara)! - Breaking changes
18
+ - Remove `enforce-curly-newline` rule. Use ESLint's built-in `curly: "all"` instead. Consumers should add `"curly": ["error", "all"]` to their ESLint config.
19
+ - Narrow `no-inline-nested-object` to truly nested structures only. Flat collections (objects of primitive properties, arrays of identifiers/member expressions/primitives) are now allowed inline because Prettier already controls their wrapping via `printWidth`. Eliminates the fix loop with Prettier on cases like `{ allow: [target.utils, target.types, target.constants] }`.
20
+
21
+ New rules
22
+ - Add `prefer-props-with-children`. Reports `children: ReactNode` declarations and recommends `PropsWithChildren<T>`. Included in `react` and `nextjs` presets.
23
+ - Add `prefer-interface-for-component-props`. Auto-fixes `type FooProps = {...}` to `interface FooProps {...}` in `.tsx` and `.jsx` files. Included in `react` and `nextjs` presets.
24
+
25
+ Fixes
26
+ - `prefer-import-type` skips imports with inline `type` markers, deferring to `no-inline-type-import`.
27
+ - `prefer-named-param-types` skips React component functions with single non-destructured `Identifier` param, deferring to `prefer-interface-over-inline-types`.
28
+
3
29
  ## 3.2.1
4
30
 
5
31
  ### Patch Changes
package/README.md CHANGED
@@ -50,7 +50,7 @@ export default [nextfriday.configs["nextjs/recommended"]];
50
50
 
51
51
  To use a preset and adjust individual rules, append a second config object after the preset. Later objects override earlier ones, so you can change severity, swap options, or add rules without re-declaring the entire preset.
52
52
 
53
- For example, enforce PascalCase for React components via the `react/recommended` preset (which already runs `nextfriday/jsx-pascal-case` and `nextfriday/enforce-camel-case` as errors), and add a rule override on top:
53
+ For example, start with the `react/recommended` preset (which runs `nextfriday/enforce-camel-case` and `nextfriday/enforce-props-suffix` as errors) and add a rule override on top:
54
54
 
55
55
  ```js
56
56
  import nextfriday from "eslint-plugin-nextfriday";
@@ -60,7 +60,6 @@ export default [
60
60
 
61
61
  {
62
62
  rules: {
63
- "nextfriday/jsx-pascal-case": "error",
64
63
  "nextfriday/enforce-props-suffix": "error",
65
64
  "nextfriday/sort-imports": "warn",
66
65
  },
@@ -68,7 +67,7 @@ export default [
68
67
  ];
69
68
  ```
70
69
 
71
- The first object enables every rule in `react/recommended`. The second object reaffirms `jsx-pascal-case` (already enforced — useful when you want it loud and explicit), enables `enforce-props-suffix`, and downgrades `sort-imports` from error to warning.
70
+ The first object enables every rule in `react/recommended`. The second object reaffirms `enforce-props-suffix` (already enforced — useful when you want it loud and explicit) and downgrades `sort-imports` from error to warning.
72
71
 
73
72
  ### Manual Configuration
74
73
 
@@ -108,10 +107,6 @@ export default [
108
107
  "nextfriday/enforce-property-case": "error",
109
108
  "nextfriday/no-misleading-constant-case": "error",
110
109
 
111
- // File Naming
112
- "nextfriday/file-kebab-case": "error",
113
- "nextfriday/jsx-pascal-case": "error",
114
-
115
110
  // Code Style
116
111
  "nextfriday/no-emoji": "error",
117
112
  "nextfriday/prefer-destructuring-params": "error",
@@ -130,7 +125,6 @@ export default [
130
125
  "nextfriday/no-inline-nested-object": "error",
131
126
  "nextfriday/no-inline-return-properties": "error",
132
127
  "nextfriday/prefer-async-await": "error",
133
- "nextfriday/enforce-curly-newline": "error",
134
128
  "nextfriday/no-nested-ternary": "error",
135
129
  "nextfriday/prefer-guard-clause": "error",
136
130
 
@@ -166,9 +160,6 @@ export default [
166
160
  "nextfriday/react-props-destructure": "error",
167
161
  "nextfriday/enforce-props-suffix": "error",
168
162
  "nextfriday/enforce-readonly-component-props": "error",
169
-
170
- // Next.js
171
- "nextfriday/nextjs-require-public-env": "error",
172
163
  },
173
164
  },
174
165
  ];
@@ -388,11 +379,11 @@ Once a directory hits zero, lock it in (step 4). Once the warn-level count hits
388
379
 
389
380
  When the warn-level preset surfaces hundreds of violations, fix them in this order — high-impact rules catch real bugs, while low-impact rules are style preferences that can wait.
390
381
 
391
- | Tier | Examples | Why first |
392
- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
393
- | High — correctness and runtime safety | `no-direct-date`, `no-env-fallback`, `nextjs-require-public-env`, `enforce-readonly-component-props`, `jsx-no-non-component-function`, `enforce-hook-naming`, `no-logic-in-params` | Each violation can mask a bug, leak config, or break React's rules of hooks. Fix before they ship. |
394
- | Medium — structure and naming | `boolean-naming-prefix`, `enforce-camel-case`, `enforce-constant-case`, `file-kebab-case`, `jsx-pascal-case`, `enforce-props-suffix`, `prefer-import-type` | No runtime impact, but inconsistent naming compounds review and onboarding cost. Fix once the high tier is clean. |
395
- | Low — formatting and ordering | `sort-imports`, `sort-exports`, `sort-type-alphabetically`, `jsx-sort-props`, `newline-before-return`, `newline-after-multiline-block`, `no-emoji` | Cosmetic. Most are auto-fixable, so a single `pnpm eslint --fix` pass typically clears the whole codebase. Save these for last. |
382
+ | Tier | Examples | Why first |
383
+ | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
384
+ | High — correctness and runtime safety | `no-direct-date`, `no-env-fallback`, `enforce-readonly-component-props`, `jsx-no-non-component-function`, `enforce-hook-naming`, `no-logic-in-params` | Each violation can mask a bug, leak config, or break React's rules of hooks. Fix before they ship. |
385
+ | Medium — structure and naming | `boolean-naming-prefix`, `enforce-camel-case`, `enforce-constant-case`, `enforce-props-suffix`, `prefer-import-type` | No runtime impact, but inconsistent naming compounds review and onboarding cost. Fix once the high tier is clean. |
386
+ | Low — formatting and ordering | `sort-imports`, `sort-exports`, `sort-type-alphabetically`, `jsx-sort-props`, `newline-before-return`, `newline-after-multiline-block`, `no-emoji` | Cosmetic. Most are auto-fixable, so a single `pnpm eslint --fix` pass typically clears the whole codebase. Save these for last. |
396
387
 
397
388
  In practice: turn the high tier on as `"error"` first, leave the medium tier as `"warn"` while you migrate, and run the auto-fixers for the low tier in a single dedicated PR.
398
389
 
@@ -412,13 +403,6 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
412
403
  | [enforce-property-case](docs/rules/ENFORCE_PROPERTY_CASE.md) | Enforce camelCase for unquoted object property keys | ❌ |
413
404
  | [no-misleading-constant-case](docs/rules/NO_MISLEADING_CONSTANT_CASE.md) | Disallow SCREAMING_SNAKE_CASE in local scope and for dynamic values | ❌ |
414
405
 
415
- ### File Naming Rules
416
-
417
- | Rule | Description | Fixable |
418
- | ------------------------------------------------ | ---------------------------------------------------- | ------- |
419
- | [file-kebab-case](docs/rules/FILE_KEBAB_CASE.md) | Enforce kebab-case filenames for .ts and .js files | ❌ |
420
- | [jsx-pascal-case](docs/rules/JSX_PASCAL_CASE.md) | Enforce PascalCase filenames for .jsx and .tsx files | ❌ |
421
-
422
406
  ### Code Style Rules
423
407
 
424
408
  | Rule | Description | Fixable |
@@ -441,7 +425,6 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
441
425
  | [no-inline-nested-object](docs/rules/NO_INLINE_NESTED_OBJECT.md) | Require nested objects and arrays to span multiple lines | ✅ |
442
426
  | [no-inline-return-properties](docs/rules/NO_INLINE_RETURN_PROPERTIES.md) | Require return object properties to use shorthand notation | ❌ |
443
427
  | [prefer-async-await](docs/rules/PREFER_ASYNC_AWAIT.md) | Enforce async/await over .then() promise chains | ❌ |
444
- | [enforce-curly-newline](docs/rules/ENFORCE_CURLY_NEWLINE.md) | Enforce curly braces for multi-line if, forbid for single-line | ✅ |
445
428
  | [no-nested-ternary](docs/rules/NO_NESTED_TERNARY.md) | Disallow nested ternary expressions | ❌ |
446
429
  | [prefer-guard-clause](docs/rules/PREFER_GUARD_CLAUSE.md) | Enforce guard clause pattern instead of nested if statements | ❌ |
447
430
 
@@ -458,16 +441,17 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
458
441
 
459
442
  ### Type Pattern Rules
460
443
 
461
- | Rule | Description | Fixable |
462
- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------- |
463
- | [enforce-type-declaration-order](docs/rules/ENFORCE_TYPE_DECLARATION_ORDER.md) | Enforce referenced types are declared after their consumer | ❌ |
464
- | [no-nested-interface-declaration](docs/rules/NO_NESTED_INTERFACE_DECLARATION.md) | Disallow inline object types in interface/type properties | ❌ |
465
- | [prefer-named-param-types](docs/rules/PREFER_NAMED_PARAM_TYPES.md) | Enforce named types for function parameters with object types | ❌ |
466
- | [prefer-inline-literal-union](docs/rules/PREFER_INLINE_LITERAL_UNION.md) | Enforce inlining literal union types for better IDE hover info | ✅ |
467
- | [prefer-inline-type-export](docs/rules/PREFER_INLINE_TYPE_EXPORT.md) | Require type/interface exports inline at the declaration | ✅ |
468
- | [prefer-interface-over-inline-types](docs/rules/PREFER_INTERFACE_OVER_INLINE_TYPES.md) | Enforce interface declarations over inline types for React props | |
469
- | [sort-type-alphabetically](docs/rules/SORT_TYPE_ALPHABETICALLY.md) | Enforce A-Z sorting of properties within type groups | |
470
- | [sort-type-required-first](docs/rules/SORT_TYPE_REQUIRED_FIRST.md) | Enforce required properties before optional in types/interfaces | ✅ |
444
+ | Rule | Description | Fixable |
445
+ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | ------- |
446
+ | [enforce-type-declaration-order](docs/rules/ENFORCE_TYPE_DECLARATION_ORDER.md) | Enforce referenced types are declared after their consumer | ❌ |
447
+ | [no-nested-interface-declaration](docs/rules/NO_NESTED_INTERFACE_DECLARATION.md) | Disallow inline object types in interface/type properties | ❌ |
448
+ | [prefer-named-param-types](docs/rules/PREFER_NAMED_PARAM_TYPES.md) | Enforce named types for function parameters with object types | ❌ |
449
+ | [prefer-inline-literal-union](docs/rules/PREFER_INLINE_LITERAL_UNION.md) | Enforce inlining literal union types for better IDE hover info | ✅ |
450
+ | [prefer-inline-type-export](docs/rules/PREFER_INLINE_TYPE_EXPORT.md) | Require type/interface exports inline at the declaration | ✅ |
451
+ | [prefer-interface-for-component-props](docs/rules/PREFER_INTERFACE_FOR_COMPONENT_PROPS.md) | Enforce interface over type alias for component prop declarations | |
452
+ | [prefer-interface-over-inline-types](docs/rules/PREFER_INTERFACE_OVER_INLINE_TYPES.md) | Enforce interface declarations over inline types for React props | |
453
+ | [sort-type-alphabetically](docs/rules/SORT_TYPE_ALPHABETICALLY.md) | Enforce A-Z sorting of properties within type groups | ✅ |
454
+ | [sort-type-required-first](docs/rules/SORT_TYPE_REQUIRED_FIRST.md) | Enforce required properties before optional in types/interfaces | ✅ |
471
455
 
472
456
  ### React/JSX Rules
473
457
 
@@ -483,46 +467,41 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
483
467
  | [jsx-simple-props](docs/rules/JSX_SIMPLE_PROPS.md) | Enforce simple prop values (strings, variables, callbacks, ReactNode) | ❌ |
484
468
  | [jsx-sort-props](docs/rules/JSX_SORT_PROPS.md) | Enforce JSX props are sorted by value type | ✅ |
485
469
  | [jsx-spread-props-last](docs/rules/JSX_SPREAD_PROPS_LAST.md) | Enforce JSX spread attributes appear after all other props | ❌ |
470
+ | [no-ghost-wrapper](docs/rules/NO_GHOST_WRAPPER.md) | Disallow bare `<div>`/`<span>` with no meaningful attributes | ❌ |
471
+ | [no-redundant-fragment](docs/rules/NO_REDUNDANT_FRAGMENT.md) | Disallow Fragments wrapping zero or one child | ❌ |
486
472
  | [prefer-jsx-template-literals](docs/rules/PREFER_JSX_TEMPLATE_LITERALS.md) | Enforce template literals instead of mixing text and JSX expressions | ✅ |
473
+ | [prefer-props-with-children](docs/rules/PREFER_PROPS_WITH_CHILDREN.md) | Prefer PropsWithChildren over manually declaring children: ReactNode | ❌ |
487
474
  | [react-props-destructure](docs/rules/REACT_PROPS_DESTRUCTURE.md) | Enforce destructuring props inside React component body | ❌ |
488
475
  | [enforce-props-suffix](docs/rules/ENFORCE_PROPS_SUFFIX.md) | Enforce 'Props' suffix for interfaces and types in \*.tsx files | ❌ |
489
476
  | [enforce-readonly-component-props](docs/rules/ENFORCE_READONLY_COMPONENT_PROPS.md) | Enforce Readonly wrapper for React component props | ✅ |
490
477
 
491
- ### Next.js Rules
492
-
493
- | Rule | Description | Fixable |
494
- | -------------------------------------------------------------------- | ------------------------------------------------------------- | ------- |
495
- | [nextjs-require-public-env](docs/rules/NEXTJS_REQUIRE_PUBLIC_ENV.md) | Require NEXT*PUBLIC* prefix for env vars in client components | ❌ |
496
-
497
478
  ## Configurations
498
479
 
499
480
  ### Configuration Presets Overview
500
481
 
501
- | Preset | Severity | Base Rules | JSX Rules | Next.js Rules | Total Rules |
502
- | -------------------- | -------- | ---------- | --------- | ------------- | ----------- |
503
- | `base` | warn | 42 | 0 | 0 | 42 |
504
- | `base/recommended` | error | 42 | 0 | 0 | 42 |
505
- | `react` | warn | 42 | 16 | 0 | 58 |
506
- | `react/recommended` | error | 42 | 16 | 0 | 58 |
507
- | `nextjs` | warn | 42 | 16 | 1 | 59 |
508
- | `nextjs/recommended` | error | 42 | 16 | 1 | 59 |
482
+ | Preset | Severity | Base Rules | JSX Rules | Total Rules |
483
+ | -------------------- | -------- | ---------- | --------- | ----------- |
484
+ | `base` | warn | 40 | 0 | 40 |
485
+ | `base/recommended` | error | 40 | 0 | 40 |
486
+ | `react` | warn | 40 | 19 | 59 |
487
+ | `react/recommended` | error | 40 | 19 | 59 |
488
+ | `nextjs` | warn | 40 | 19 | 59 |
489
+ | `nextjs/recommended` | error | 40 | 19 | 59 |
509
490
 
510
- The `nextjs` and `nextjs/recommended` presets ship as an array of two flat-config objects: the rule set above, plus a routing override that disables `nextfriday/file-kebab-case` and `nextfriday/jsx-pascal-case` for files matching `app/**/*.{js,jsx,ts,tsx}`, `src/app/**/*.{js,jsx,ts,tsx}`, `pages/**/*.{js,jsx,ts,tsx}`, and `src/pages/**/*.{js,jsx,ts,tsx}`. Next.js owns the filenames in those directories (`page.tsx`, `layout.tsx`, `route.ts`, `middleware.ts`, etc.), so the plugin steps out of the way. ESLint 9+ flattens nested config arrays automatically, so spreading the preset works as expected.
491
+ The `nextjs` and `nextjs/recommended` presets currently share the same rule set as `react` and `react/recommended`; they are kept as named aliases for ergonomics.
511
492
 
512
- ### Base Configuration Rules (42 rules)
493
+ ### Base Configuration Rules (40 rules)
513
494
 
514
495
  Included in `base`, `base/recommended`, and all other presets:
515
496
 
516
497
  - `nextfriday/boolean-naming-prefix`
517
498
  - `nextfriday/enforce-camel-case`
518
499
  - `nextfriday/enforce-constant-case`
519
- - `nextfriday/enforce-curly-newline`
520
500
  - `nextfriday/enforce-hook-naming`
521
501
  - `nextfriday/enforce-property-case`
522
502
  - `nextfriday/enforce-service-naming`
523
503
  - `nextfriday/enforce-sorted-destructuring`
524
504
  - `nextfriday/enforce-type-declaration-order`
525
- - `nextfriday/file-kebab-case`
526
505
  - `nextfriday/index-export-only`
527
506
  - `nextfriday/newline-after-multiline-block`
528
507
  - `nextfriday/newline-before-return`
@@ -556,7 +535,7 @@ Included in `base`, `base/recommended`, and all other presets:
556
535
  - `nextfriday/sort-type-alphabetically`
557
536
  - `nextfriday/sort-type-required-first`
558
537
 
559
- ### JSX Rules (16 rules)
538
+ ### JSX Rules (19 rules)
560
539
 
561
540
  Additionally included in `react`, `react/recommended`, `nextjs`, `nextjs/recommended`:
562
541
 
@@ -568,21 +547,18 @@ Additionally included in `react`, `react/recommended`, `nextjs`, `nextjs/recomme
568
547
  - `nextfriday/jsx-no-non-component-function`
569
548
  - `nextfriday/jsx-no-ternary-null`
570
549
  - `nextfriday/jsx-no-variable-in-callback`
571
- - `nextfriday/jsx-pascal-case`
572
550
  - `nextfriday/jsx-require-suspense`
573
551
  - `nextfriday/jsx-simple-props`
574
552
  - `nextfriday/jsx-sort-props`
575
553
  - `nextfriday/jsx-spread-props-last`
554
+ - `nextfriday/no-ghost-wrapper`
555
+ - `nextfriday/no-redundant-fragment`
556
+ - `nextfriday/prefer-interface-for-component-props`
576
557
  - `nextfriday/prefer-interface-over-inline-types`
577
558
  - `nextfriday/prefer-jsx-template-literals`
559
+ - `nextfriday/prefer-props-with-children`
578
560
  - `nextfriday/react-props-destructure`
579
561
 
580
- ### Next.js Only Rules (1 rule)
581
-
582
- Additionally included in `nextjs`, `nextjs/recommended` only:
583
-
584
- - `nextfriday/nextjs-require-public-env`
585
-
586
562
  ### Severity Levels
587
563
 
588
564
  - **`base` / `react` / `nextjs`**: All rules set to `"warn"`
@@ -599,16 +575,6 @@ Additionally included in `nextjs`, `nextjs/recommended` only:
599
575
  - **Clean code practices**: Prevents emoji usage, enforces parameter destructuring, and more
600
576
  - **Formatting rules**: Enforces consistent blank lines around multi-line blocks and return statements
601
577
 
602
- ## Agent Skill
603
-
604
- This plugin ships with an [Agent Skill](https://github.com/anthropics/skills) that teaches AI coding assistants (Claude Code, Cursor, etc.) all 56 rules so they generate compliant code from the start.
605
-
606
- ```bash
607
- npx skills add next-friday/eslint-plugin-nextfriday --skill eslint-plugin-nextfriday
608
- ```
609
-
610
- Once installed, AI assistants will automatically follow the naming, code style, type, JSX, import, and formatting patterns enforced by this plugin — reducing lint errors to zero.
611
-
612
578
  ## Need Help?
613
579
 
614
580
  If you encounter any issues or have questions:
@@ -0,0 +1,51 @@
1
+ # Domain Docs
2
+
3
+ How the engineering skills should consume this repo's domain documentation when exploring the codebase.
4
+
5
+ ## Before exploring, read these
6
+
7
+ - **`CONTEXT.md`** at the repo root, or
8
+ - **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
9
+ - **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
10
+
11
+ If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The producer skill (`/grill-with-docs`) creates them lazily when terms or decisions actually get resolved.
12
+
13
+ ## File structure
14
+
15
+ Single-context repo (most repos):
16
+
17
+ ```
18
+ /
19
+ ├── CONTEXT.md
20
+ ├── docs/adr/
21
+ │ ├── 0001-event-sourced-orders.md
22
+ │ └── 0002-postgres-for-write-model.md
23
+ └── src/
24
+ ```
25
+
26
+ Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
27
+
28
+ ```
29
+ /
30
+ ├── CONTEXT-MAP.md
31
+ ├── docs/adr/ ← system-wide decisions
32
+ └── src/
33
+ ├── ordering/
34
+ │ ├── CONTEXT.md
35
+ │ └── docs/adr/ ← context-specific decisions
36
+ └── billing/
37
+ ├── CONTEXT.md
38
+ └── docs/adr/
39
+ ```
40
+
41
+ ## Use the glossary's vocabulary
42
+
43
+ When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
44
+
45
+ If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/grill-with-docs`).
46
+
47
+ ## Flag ADR conflicts
48
+
49
+ If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
50
+
51
+ > _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_
@@ -0,0 +1,22 @@
1
+ # Issue tracker: GitHub
2
+
3
+ Issues and PRDs for this repo live as GitHub issues. Use the `gh` CLI for all operations.
4
+
5
+ ## Conventions
6
+
7
+ - **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies.
8
+ - **Read an issue**: `gh issue view <number> --comments`, filtering comments by `jq` and also fetching labels.
9
+ - **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters.
10
+ - **Comment on an issue**: `gh issue comment <number> --body "..."`
11
+ - **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
12
+ - **Close**: `gh issue close <number> --comment "..."`
13
+
14
+ Infer the repo from `git remote -v` — `gh` does this automatically when run inside a clone.
15
+
16
+ ## When a skill says "publish to the issue tracker"
17
+
18
+ Create a GitHub issue.
19
+
20
+ ## When a skill says "fetch the relevant ticket"
21
+
22
+ Run `gh issue view <number> --comments`.
@@ -0,0 +1,15 @@
1
+ # Triage Labels
2
+
3
+ The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker.
4
+
5
+ | Label in mattpocock/skills | Label in our tracker | Meaning |
6
+ | -------------------------- | -------------------- | ---------------------------------------- |
7
+ | `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
8
+ | `needs-info` | `needs-info` | Waiting on reporter for more information |
9
+ | `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
10
+ | `ready-for-human` | `ready-for-human` | Requires human implementation |
11
+ | `wontfix` | `wontfix` | Will not be actioned |
12
+
13
+ When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
14
+
15
+ Edit the right-hand column to match whatever vocabulary you actually use.
@@ -0,0 +1,75 @@
1
+ # no-ghost-wrapper
2
+
3
+ Disallow bare `<div>` and `<span>` elements that have no meaningful attributes.
4
+
5
+ ## Rule Details
6
+
7
+ This rule flags `<div>` and `<span>` elements that carry zero meaningful attributes — the "ghost wrapper" anti-pattern (also called "Divitis"). These elements add depth to the DOM tree without contributing semantics, styling, behavior, accessibility hooks, or test hooks.
8
+
9
+ The `key` prop alone does not count as meaningful: `key` is a React-only signal for list reconciliation and carries no structural intent.
10
+
11
+ ### Why?
12
+
13
+ - **Semantic clarity**: A wrapper without attributes signals that the author has not decided whether the element is structural, presentational, or semantic.
14
+ - **DOM bloat**: Empty wrappers extend the DOM tree, increasing layout work and accessibility-tree noise.
15
+ - **Cognitive load**: Reviewers must guess whether a bare wrapper is intentional or a leftover from refactoring.
16
+ - **Better alternatives exist**: Fragments (`<>...</>`), semantic elements (`<section>`, `<article>`, `<header>`, `<nav>`), or simply unwrapping the children.
17
+
18
+ ## Examples
19
+
20
+ ### Incorrect
21
+
22
+ ```tsx
23
+ <div>x</div>
24
+ <span>text</span>
25
+ <div></div>
26
+ <div> </div>
27
+ <div />
28
+ <span />
29
+ <div key={item.id}>x</div>
30
+ <div>{children}</div>
31
+ ```
32
+
33
+ ### Correct
34
+
35
+ ```tsx
36
+ <div className="container">x</div>
37
+ <div data-testid="root">x</div>
38
+ <div role="button">x</div>
39
+ <div aria-label="close">x</div>
40
+ <div ref={ref}>x</div>
41
+ <div onClick={handleClick}>x</div>
42
+ <div id="anchor">x</div>
43
+ <div style={{ color: "red" }}>x</div>
44
+ <div {...props}>x</div>
45
+ <div tabIndex={0}>x</div>
46
+ <section>x</section>
47
+ <article>x</article>
48
+ <>x</>
49
+ ```
50
+
51
+ ## What This Rule Checks
52
+
53
+ The rule fires on a `<div>` or `<span>` opening element when, after filtering out a lone `key` attribute, it has zero remaining attributes (including spread attributes). Self-closing elements (`<div />`, `<span />`) are checked the same way.
54
+
55
+ Any of the following attributes count as meaningful and silence the rule:
56
+
57
+ - `className`
58
+ - `style`
59
+ - `id`
60
+ - `ref`
61
+ - `role`
62
+ - `aria-*`
63
+ - `data-*`
64
+ - `tabIndex`
65
+ - Event handlers (`onClick`, `onChange`, etc.)
66
+ - Spread attributes (`{...props}`)
67
+ - Any other JSX attribute except `key`
68
+
69
+ ## When Not To Use It
70
+
71
+ If your codebase intentionally uses bare `<div>` and `<span>` as structural placeholders, or if you rely on parent CSS selectors that target a fixed wrapper depth.
72
+
73
+ ## Related Rules
74
+
75
+ - [no-redundant-fragment](NO_REDUNDANT_FRAGMENT.md)
@@ -1,62 +1,80 @@
1
1
  # no-inline-nested-object
2
2
 
3
- Require nested objects and arrays with multiple properties to span multiple lines.
3
+ Require object or array values that contain further nested objects or arrays to span multiple lines.
4
4
 
5
5
  ## Rule Details
6
6
 
7
- This rule enforces that when an object property's value is another object or array with more than one element, it should span multiple lines rather than being written inline. Single-property nested objects are allowed inline. This improves readability and makes diffs cleaner when properties are added or removed.
7
+ This rule enforces that when an object property's value is itself an object or array **that contains further nested structures inside**, it must span multiple lines. Flat collections objects of primitive properties or arrays of simple references are allowed inline because Prettier already controls their wrapping via `printWidth`.
8
+
9
+ A "nested structure" is any object or array element/property whose value is another object or array. The rule deliberately ignores depth-1 collections so it does not fight Prettier on simple data and configuration tables.
10
+
11
+ ### Why?
12
+
13
+ Truly nested structures are easy to misread when collapsed onto a single line, and adding or removing an inner element produces noisy diffs. Flat collections do not have the same problem and are best left to Prettier's line-length logic.
8
14
 
9
15
  ## Examples
10
16
 
11
17
  ### Incorrect
12
18
 
19
+ <!-- prettier-ignore -->
13
20
  ```ts
14
- const config = {
15
- database: { host: "localhost", port: 5432, name: "myapp" },
21
+ const obj = {
22
+ items: [{ a: 1 }, { b: 2 }],
23
+ };
24
+ ```
25
+
26
+ <!-- prettier-ignore -->
27
+ ```ts
28
+ const obj = {
29
+ matrix: [[1, 2], [3, 4]],
16
30
  };
17
31
  ```
18
32
 
19
33
  ```ts
20
- const routes = {
21
- api: { users: "/api/users", posts: "/api/posts" },
22
- auth: { login: "/auth/login", logout: "/auth/logout" },
34
+ const obj = {
35
+ layer: { inner: { leaf: 1 } },
36
+ };
37
+ ```
38
+
39
+ ```ts
40
+ const obj = {
41
+ wrap: { items: [1, 2, 3] },
23
42
  };
24
43
  ```
25
44
 
26
45
  ### Correct
27
46
 
47
+ <!-- prettier-ignore -->
28
48
  ```ts
29
- const config = {
30
- database: { host: "localhost" },
49
+ const obj = {
50
+ items: [
51
+ { a: 1 },
52
+ { b: 2 },
53
+ ],
31
54
  };
32
55
  ```
33
56
 
34
57
  ```ts
35
- const config = {
36
- database: {
37
- host: "localhost",
38
- port: 5432,
39
- name: "myapp",
58
+ const obj = {
59
+ layer: {
60
+ inner: { leaf: 1 },
40
61
  },
41
62
  };
42
63
  ```
43
64
 
65
+ Flat values stay inline:
66
+
44
67
  ```ts
45
- const routes = {
46
- api: {
47
- users: "/api/users",
48
- posts: "/api/posts",
49
- },
50
- auth: {
51
- login: "/auth/login",
52
- logout: "/auth/logout",
53
- },
68
+ const obj = {
69
+ config: { enabled: true, timeout: 5000 },
70
+ database: { host: "localhost", port: 5432, name: "myapp" },
54
71
  };
55
72
  ```
56
73
 
57
74
  ```ts
58
- const validationRules = {
59
- required: ["name", "email", "password"],
75
+ const obj = {
76
+ options: ["primary", "foreground", "danger", "outline", "ghost", "link"],
77
+ allow: [target.utils, target.types, target.constants],
60
78
  };
61
79
  ```
62
80
 
@@ -71,8 +89,8 @@ const initialState = {
71
89
 
72
90
  ## When Not To Use It
73
91
 
74
- If you prefer compact inline nested objects for all cases, or if your team has different formatting preferences.
92
+ If your team prefers compact single-line nested structures regardless of depth, or if your project has different formatting preferences.
75
93
 
76
94
  ## Fixable
77
95
 
78
- This rule is auto-fixable. Running ESLint with the `--fix` flag will automatically expand inline nested objects and arrays with multiple properties to multiple lines.
96
+ This rule is auto-fixable. Running ESLint with the `--fix` flag will expand inline collections that contain nested structures onto multiple lines.
@@ -0,0 +1,56 @@
1
+ # no-redundant-fragment
2
+
3
+ Disallow Fragments that wrap zero or one child.
4
+
5
+ ## Rule Details
6
+
7
+ This rule flags Fragments (`<>...</>`, `<Fragment>...</Fragment>`, `<React.Fragment>...</React.Fragment>`) that wrap zero or one child. A Fragment is only justified when grouping multiple sibling nodes, or when a `key` prop is required during list iteration.
8
+
9
+ A Fragment with a single child is structurally equivalent to that child alone. An empty Fragment renders nothing and adds noise to the source.
10
+
11
+ ### Why?
12
+
13
+ - **Source clarity**: A Fragment around a single child is dead syntax — it tells the reader nothing.
14
+ - **Less noise in the AST**: Devtools, search, and codemods do not have to step over an extra layer.
15
+ - **Forces a decision**: Either the children are siblings (Fragment is the right tool) or they are not (drop it).
16
+
17
+ ## Examples
18
+
19
+ ### Incorrect
20
+
21
+ ```tsx
22
+ <></>
23
+ <>{x}</>
24
+ <><A /></>
25
+ <>text</>
26
+ <Fragment>x</Fragment>
27
+ <React.Fragment>{x}</React.Fragment>
28
+ <React.Fragment></React.Fragment>
29
+ ```
30
+
31
+ ### Correct
32
+
33
+ ```tsx
34
+ <>{a}{b}</>
35
+ <><A /><B /></>
36
+ <Fragment>{a}{b}</Fragment>
37
+ <React.Fragment>{a}{b}</React.Fragment>
38
+
39
+ // key is the legitimate reason to use long-form Fragment:
40
+ <React.Fragment key={item.id}>{item.label}{item.value}</React.Fragment>
41
+ <Fragment key={k}>{x}</Fragment>
42
+ ```
43
+
44
+ ## What This Rule Checks
45
+
46
+ A Fragment is flagged when its meaningful children count is `0` or `1`. JSX text nodes that contain only whitespace are not counted as children.
47
+
48
+ The rule does not fire on a long-form Fragment (`<Fragment>` or `<React.Fragment>`) that carries a `key` attribute — `key` is the canonical reason long-form Fragment exists, since the shorthand `<>...</>` cannot accept attributes.
49
+
50
+ ## When Not To Use It
51
+
52
+ If your codebase uses single-child Fragments to keep diffs stable around conditionally-rendered siblings, or if you rely on a build-time transform that depends on the Fragment wrapper.
53
+
54
+ ## Related Rules
55
+
56
+ - [no-ghost-wrapper](NO_GHOST_WRAPPER.md)
@@ -36,12 +36,14 @@ function validate(user) {
36
36
  function process(data) {
37
37
  if (!data) return [];
38
38
  if (!data.items) return [];
39
+
39
40
  return data.items.map(toItem);
40
41
  }
41
42
 
42
43
  function validate(user) {
43
44
  if (!user) return null;
44
45
  if (!user.isAdmin) return null;
46
+
45
47
  return adminDashboard();
46
48
  }
47
49
  ```
@@ -61,6 +61,10 @@ import { TSESTree } from "@typescript-eslint/utils";
61
61
  import type { TSESTree } from "@typescript-eslint/utils";
62
62
  ```
63
63
 
64
+ ## Exceptions
65
+
66
+ This rule defers to [`no-inline-type-import`](./NO_INLINE_TYPE_IMPORT.md) when an import already contains an inline `type` marker (for example, `import { type Foo } from "x"`). Reporting both rules on the same statement produces overlapping messages and conflicting fixes, so `prefer-import-type` skips the import in that case and lets `no-inline-type-import` produce the canonical fix.
67
+
64
68
  ## When Not To Use It
65
69
 
66
70
  - If your project doesn't use TypeScript
@@ -69,4 +73,5 @@ import type { TSESTree } from "@typescript-eslint/utils";
69
73
 
70
74
  ## Related Rules
71
75
 
76
+ - [no-inline-type-import](./NO_INLINE_TYPE_IMPORT.md) - Owns the `import { type Foo }` case
72
77
  - [@typescript-eslint/consistent-type-imports](https://typescript-eslint.io/rules/consistent-type-imports/) - Similar rule from typescript-eslint