@geonosis/oxlint-plugin-biological-architecture 0.1.0 → 0.1.2

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.
Files changed (93) hide show
  1. package/README.md +107 -0
  2. package/bin/geonosis-lint-parity.mjs +4 -0
  3. package/corpus/.oxlintrc.json +114 -0
  4. package/corpus/api/store/catalog/route.ts +6 -0
  5. package/corpus/api/store/orders/route.ts +6 -0
  6. package/corpus/api/store/orders/scoped.route.ts +13 -0
  7. package/corpus/app/bad-route/page.tsx +3 -0
  8. package/corpus/apps/api/src/bad-unscoped-worker.ts +3 -0
  9. package/corpus/atoms/bad-import.tsx +5 -0
  10. package/corpus/atoms/good-atom.tsx +4 -0
  11. package/corpus/brand/bad-brand.tsx +2 -0
  12. package/corpus/brand/good-media-url.tsx +2 -0
  13. package/corpus/cells/bad-cross-cell.tsx +4 -0
  14. package/corpus/cells/bad-logic.ts +3 -0
  15. package/corpus/cells/bad-stateless-display.tsx +6 -0
  16. package/corpus/cells/bad-tissue-import.tsx +5 -0
  17. package/corpus/cells/bad-triplicate.tsx +9 -0
  18. package/corpus/cells/bad-two-exports.tsx +6 -0
  19. package/corpus/cells/bad-type-def.tsx +8 -0
  20. package/corpus/cells/dynamic.tsx +1 -0
  21. package/corpus/cells/good-iterated.tsx +9 -0
  22. package/corpus/cells/good-one-export.tsx +10 -0
  23. package/corpus/cells/good-own-sibling.sections.tsx +3 -0
  24. package/corpus/cells/good-own-sibling.tsx +4 -0
  25. package/corpus/cells/good-shape.types.ts +3 -0
  26. package/corpus/cells/good-stateful.tsx +12 -0
  27. package/corpus/cells/index.ts +2 -0
  28. package/corpus/cells/other-cell.tsx +3 -0
  29. package/corpus/comments/bad-comment-density.ts +5 -0
  30. package/corpus/compounds/bad-card-div.tsx +3 -0
  31. package/corpus/compounds/bad-inline-data.tsx +5 -0
  32. package/corpus/compounds/bad-stateful.tsx +8 -0
  33. package/corpus/compounds/bad-store-import.tsx +5 -0
  34. package/corpus/compounds/good-compound.tsx +6 -0
  35. package/corpus/config/bad-process-env.ts +1 -0
  36. package/corpus/data/bad-d1-transaction.ts +5 -0
  37. package/corpus/data/bad-dialect-leak.ts +3 -0
  38. package/corpus/data/bad-orm-outside-db.ts +3 -0
  39. package/corpus/data/bad-raw-sql.ts +3 -0
  40. package/corpus/features/invoice/cells/bad-cross-feature-store.tsx +6 -0
  41. package/corpus/features/invoice/widgets/bad-folder.ts +1 -0
  42. package/corpus/inline/uses-engine-step.ts +5 -0
  43. package/corpus/manifest.json +59 -0
  44. package/corpus/molecules/bad-cell-import.tsx +7 -0
  45. package/corpus/molecules/bad-passthrough.tsx +8 -0
  46. package/corpus/molecules/bad-renamed-props.tsx +9 -0
  47. package/corpus/molecules/good-molecule.tsx +6 -0
  48. package/corpus/organelles/bad-imports-cell.tsx +7 -0
  49. package/corpus/organelles/bad-two-sources.tsx +9 -0
  50. package/corpus/organelles/good-imports-atom.tsx +6 -0
  51. package/corpus/organelles/use-counter-effect.ts +11 -0
  52. package/corpus/organelles/use-counter.ts +12 -0
  53. package/corpus/packages/db/src/queries/bad-unscoped.ts +3 -0
  54. package/corpus/packages/db/src/schema/bad-undeclared-plane.ts +3 -0
  55. package/corpus/packages/db/src/schema/tenant/bad-no-org-id.ts +3 -0
  56. package/corpus/packages/db/src/schema/tenant/bad-pg-per-kind.ts +8 -0
  57. package/corpus/packages/db/src/schema/tenant/bad-sqlite-per-kind.ts +8 -0
  58. package/corpus/packages/db/src/schema/tenant/good-one-table.ts +9 -0
  59. package/corpus/packages/workflows/src/bad-per-kind-saga.ts +3 -0
  60. package/corpus/ports/bad-void-port.ts +1 -0
  61. package/corpus/queue/bad-hand-rolled-loop.ts +3 -0
  62. package/corpus/react/bad-hook-disguise.tsx +9 -0
  63. package/corpus/react/bad-inert-hidden.tsx +5 -0
  64. package/corpus/react/bad-react-namespace.tsx +8 -0
  65. package/corpus/react/bad-render-prop.tsx +5 -0
  66. package/corpus/react/bad-zustand.tsx +6 -0
  67. package/corpus/react/good-react-namespace.tsx +7 -0
  68. package/corpus/time/bad-raw-date.ts +1 -0
  69. package/corpus/tissues/bad-data-props.tsx +5 -0
  70. package/corpus/tissues/bad-hooks-entry.tsx +6 -0
  71. package/corpus/tissues/bad-organelle-import.tsx +5 -0
  72. package/corpus/tissues/bad-passthrough.tsx +3 -0
  73. package/corpus/tissues/bad-store-import.tsx +5 -0
  74. package/corpus/tissues/good-composes.tsx +4 -0
  75. package/corpus/tissues/good-hooks.parts.tsx +5 -0
  76. package/corpus/tissues/good-types.types.ts +1 -0
  77. package/corpus/workflows/bad-engine-step.ts +6 -0
  78. package/corpus/workflows/good-wrapper-step.ts +4 -0
  79. package/corpus/workflows/step.ts +6 -0
  80. package/dist/chunk-2VSZAP5M.js +240 -0
  81. package/dist/chunk-BFFWNFJM.js +6 -0
  82. package/dist/chunk-BGX2XYEL.js +153 -0
  83. package/dist/chunk-FGUOGKA5.js +228 -0
  84. package/dist/chunk-FJ3J7B5J.js +155 -0
  85. package/dist/chunk-NZVMV3Y5.js +339 -0
  86. package/dist/chunk-RNY5NDH5.js +155 -0
  87. package/dist/chunk-SUTNA6YD.js +268 -0
  88. package/dist/chunk-UM6UIUH3.js +250 -0
  89. package/dist/chunk-YIAUS4GG.js +298 -0
  90. package/dist/index.js +19 -6
  91. package/dist/parity-cli.js +84 -0
  92. package/dist/parity.js +32 -0
  93. package/package.json +16 -8
package/README.md CHANGED
@@ -68,6 +68,113 @@ everything below the organelle is inert. A cell never composes another cell.
68
68
  A rule that is false on another stack gets its own preset rather than riding along in a generic
69
69
  one: a preset carrying a repo fact is a repo fact wearing a preset's name.
70
70
 
71
+ ## Adopting
72
+
73
+ A repo that already has its own copy of these rules does not delete it because the version numbers
74
+ match. It deletes it when the **findings** match: same tree, same rule ids, two configs, and a diff.
75
+ `geonosis-lint-parity` is that diff.
76
+
77
+ ```bash
78
+ geonosis-lint-parity --a <config-a.json> --b <config-b.json> [--oxlint <path>] [--out <dir>] -- <paths…>
79
+ ```
80
+
81
+ **Write both configs inside the workspace being linted** — `<ws>/.oxlintrc.parity-a.json`, deleted
82
+ afterwards — or name the plugin by absolute path in `jsPlugins`. oxlint resolves a `jsPlugins`
83
+ specifier relative to the **config file's** directory, not the working directory, so a config
84
+ written to a temp dir cannot find a plugin installed in the workspace: it fails with
85
+ `Cannot find module '@…/plugin'`, and the tool correctly refuses with
86
+ `config A: oxlint exited 1 without reporting a single finding`.
87
+
88
+ ### Step 0 — reach, before parity
89
+
90
+ Parity between two configs that both do nothing is perfect parity. In one source repo 34 of 51
91
+ configured rules fired nowhere in any corpus it had: 34 rules a consumer would inherit as claims
92
+ nothing could check. So this package ships the **reach corpus** — the fixture tree its own gates run
93
+ against, one deliberately broken file per rule — and answers the question directly:
94
+
95
+ ```bash
96
+ geonosis-lint-parity --corpus [<dir>] --a oxlintrc.fork.json --b oxlintrc.published.json [--out <dir>]
97
+ ```
98
+
99
+ Both configs run over the corpus (copied to a temp root first, so neither a `.gitignore` nor
100
+ oxlint's `node_modules` skip can quietly turn "every rule fired nowhere" into the answer). It prints
101
+ a fired-under-A / fired-under-B row per in-scope rule and **exits 1 when one fires under neither**.
102
+ `--out` writes `CORPUS.md`.
103
+
104
+ **A corpus covers the rules its own `manifest.json` names, and only those.** The one this package
105
+ ships names `biological-architecture/*` — one deliberately broken file per rule, the list generated
106
+ from the plugin's own rules export at build time. A configured `eqeqeq`, `unicorn/*` or
107
+ `typescript/*` is listed under **outside this corpus** and gets no verdict: this tree is no evidence
108
+ either way about somebody else's rule, and judging one would fail every real config for saying
109
+ nothing. `--corpus <dir>` points the same run at another plugin's corpus; the scope moves with it.
110
+
111
+ Every rule this package exports has at least one corpus file that fires it, and a test fails if that
112
+ stops being true — so a rule cannot be added without evidence it reaches anything.
113
+
114
+ ### Then parity
115
+
116
+ It runs oxlint twice with `--format=unix` over the same paths, strips the working-directory prefix,
117
+ sorts, and prints the two counts, `only in A`, `only in B` and a per-rule tally. **It exits 1 when
118
+ `only in A` is non-empty** — a finding the old config caught and the new one does not. New findings
119
+ are what an upgrade is for; lost ones stop the adoption. `--out` writes `a.txt`, `b.txt`,
120
+ `only-in-a.txt`, `only-in-b.txt`, `changed.txt` and `SUMMARY.md` for the proof file.
121
+
122
+ **A finding is identified by where it is and which rule found it — `path:line:col` plus
123
+ `plugin(rule)`.** The message is not part of it, and neither is the severity. A release that only
124
+ rewords a rule's message would otherwise show as one finding lost and one gained, and stop an
125
+ adoption over a sentence. Those pair up instead under **changed**, reported in full and never
126
+ reaching the exit code; a severity change lands there too, because that is a fact about the config
127
+ rather than a rule that stopped firing.
128
+
129
+ **Fork vs published**, before deleting a vendored copy — two configs identical but for `jsPlugins`:
130
+
131
+ ```bash
132
+ # oxlintrc.fork.json → "jsPlugins": ["./packages/eslint-plugin-biological-architecture"]
133
+ # oxlintrc.published.json → "jsPlugins": ["@geonosis/oxlint-plugin-biological-architecture"]
134
+ geonosis-lint-parity \
135
+ --a oxlintrc.fork.json --b oxlintrc.published.json \
136
+ --out proofs/adoption-parity \
137
+ -- apps packages
138
+ ```
139
+
140
+ **Version vs version**, as the kit's own release check — the same config, pointing first at the
141
+ installed version and then at a local build, by absolute path:
142
+
143
+ ```bash
144
+ geonosis-lint-parity \
145
+ --a oxlintrc.0.1.0.json --b oxlintrc.0.1.1.json \
146
+ --out proofs/parity-0.1.0-vs-0.1.1 \
147
+ -- apps packages
148
+ ```
149
+
150
+ `--oxlint` overrides the binary; by default it walks up from the working directory for
151
+ `node_modules/.bin/oxlint`, so the comparison runs under the consumer's own linter rather than one
152
+ `npx` might fetch. Every difference the run reports has to trace to a decision written down in
153
+ `RULES.md`; one that does not is a defect, not a difference.
154
+
155
+ ### What the two configs must say for the findings to match
156
+
157
+ A fork usually had its repo's vocabulary compiled in. Here it is configuration, and a config that
158
+ omits it turns the rule off — which reads as a lost finding, correctly. Carry these across:
159
+
160
+ | Rule | Option | Without it |
161
+ | --- | --- | --- |
162
+ | `no-brand-names` | `{ brands, allowedSubstrings }` | does nothing |
163
+ | `organelle-dependency` | `{ sharedFeatures }` | only `shared` crosses a feature boundary |
164
+ | `documents-share-one-table` | `{ perKindTables }` | does nothing |
165
+ | `document-sagas-are-generic` | `{ perKindPrefixes }` | does nothing |
166
+ | `step-opens-its-own-cell` | `{ engine, within, wrapper }` | does nothing |
167
+ | `no-raw-html-atoms` | the positional element→atom map | does nothing |
168
+
169
+ Two more facts a fork's config will trip on:
170
+
171
+ - **`cell-handles-are-scoped` is `worker-handles-are-scoped` here.** Same rule; it gave up the word
172
+ "cell" because in this kit a cell is a tier, and a backend rule meaning a D1 database handle by it
173
+ would teach the wrong vocabulary at every diagnostic. Rename the key in the config.
174
+ - **Rule ids keep the `biological-architecture/` prefix whatever the package is called.** The
175
+ namespace is the plugin's identity, not the package's, so renaming or re-scoping the package does
176
+ not rename a single rule id in a single consumer config.
177
+
71
178
  ## Where to read on
72
179
 
73
180
  - `RULES.md` — every rule, its preset, which source tree it came from, and how each divergence was
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // Committed, so `pnpm install` can link the bin on a fresh clone — before `pnpm build` has
3
+ // produced dist/. A bin that only exists after a build is a bin that is missing when you need it.
4
+ import '../dist/parity-cli.js'
@@ -0,0 +1,114 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
3
+ "jsPlugins": [
4
+ "@geonosis/oxlint-plugin-biological-architecture"
5
+ ],
6
+ "categories": {
7
+ "correctness": "off",
8
+ "suspicious": "off"
9
+ },
10
+ "rules": {
11
+ "biological-architecture/atom-no-deps": "error",
12
+ "biological-architecture/cell-must-be-stateful": "error",
13
+ "biological-architecture/cell-must-not-compose-cell": "error",
14
+ "biological-architecture/cell-no-tissues": "error",
15
+ "biological-architecture/cells-folder-index-is-barrel": "error",
16
+ "biological-architecture/compound-must-be-stateless": "error",
17
+ "biological-architecture/compound-no-stores": "error",
18
+ "biological-architecture/constants-in-constants-file": "error",
19
+ "biological-architecture/dialect-through-the-seam": "error",
20
+ "biological-architecture/document-sagas-are-generic": [
21
+ "error",
22
+ {
23
+ "perKindPrefixes": [
24
+ "invoice"
25
+ ]
26
+ }
27
+ ],
28
+ "biological-architecture/documents-share-one-table": [
29
+ "error",
30
+ {
31
+ "perKindTables": [
32
+ "invoices",
33
+ "quotes"
34
+ ]
35
+ }
36
+ ],
37
+ "biological-architecture/effect-hook-naming": "error",
38
+ "biological-architecture/max-comment-density": "error",
39
+ "biological-architecture/molecule-atoms-only": "error",
40
+ "biological-architecture/molecule-must-compose": "error",
41
+ "biological-architecture/next-route-segment-is-thin-delegate": "error",
42
+ "biological-architecture/no-brand-names": [
43
+ "error",
44
+ {
45
+ "brands": [
46
+ "acme"
47
+ ],
48
+ "allowedSubstrings": [
49
+ "media.acme.com"
50
+ ]
51
+ }
52
+ ],
53
+ "biological-architecture/no-card-shaped-div": "error",
54
+ "biological-architecture/no-cross-feature-stores": "error",
55
+ "biological-architecture/no-d1-transaction": "error",
56
+ "biological-architecture/no-duplicate-jsx-patterns": "error",
57
+ "biological-architecture/no-hook-in-component-disguise": "error",
58
+ "biological-architecture/no-inert-hidden-jsx": "error",
59
+ "biological-architecture/no-inline-data-in-jsx": "error",
60
+ "biological-architecture/no-invalid-feature-folders": "error",
61
+ "biological-architecture/no-logic-in-component-files": "error",
62
+ "biological-architecture/no-orm-outside-db": "error",
63
+ "biological-architecture/no-raw-html-atoms": [
64
+ "error",
65
+ [
66
+ {
67
+ "element": "button",
68
+ "atom": "Button",
69
+ "importPath": "@kit/ui/atoms/button"
70
+ }
71
+ ]
72
+ ],
73
+ "biological-architecture/no-raw-sql-outside-allowed": "error",
74
+ "biological-architecture/no-react-namespace": "error",
75
+ "biological-architecture/no-renamed-html-props": "error",
76
+ "biological-architecture/no-render-prop-reader": "error",
77
+ "biological-architecture/no-trivial-wrapper-component": "error",
78
+ "biological-architecture/no-ts-in-bio-folders": "error",
79
+ "biological-architecture/no-type-definitions-in-components": "error",
80
+ "biological-architecture/no-void-port": "error",
81
+ "biological-architecture/organelle-dependency": [
82
+ "error",
83
+ {
84
+ "sharedFeatures": [
85
+ "shared"
86
+ ]
87
+ }
88
+ ],
89
+ "biological-architecture/organelle-single-source": "error",
90
+ "biological-architecture/queries-require-org-scope": "error",
91
+ "biological-architecture/queue-loop-is-the-library": "error",
92
+ "biological-architecture/ssot-no-inline-facts": "error",
93
+ "biological-architecture/ssot-no-process-env": "error",
94
+ "biological-architecture/step-opens-its-own-cell": [
95
+ "error",
96
+ {
97
+ "engine": "sagaflow-js",
98
+ "within": "(?:__fixtures__|corpus)/workflows/",
99
+ "wrapper": "(?:__fixtures__|corpus)/workflows/step\\.ts$"
100
+ }
101
+ ],
102
+ "biological-architecture/store-route-scopes-tenant-data": "error",
103
+ "biological-architecture/tables-declare-their-plane": "error",
104
+ "biological-architecture/tenant-tables-carry-org-id": "error",
105
+ "biological-architecture/time-through-the-door": "error",
106
+ "biological-architecture/tissue-must-compose": "error",
107
+ "biological-architecture/tissue-no-data-props": "error",
108
+ "biological-architecture/tissue-no-hooks": "error",
109
+ "biological-architecture/tissue-no-organelles": "error",
110
+ "biological-architecture/tissue-no-stores": "error",
111
+ "biological-architecture/worker-handles-are-scoped": "error",
112
+ "biological-architecture/zustand-v5-best-practices": "error"
113
+ }
114
+ }
@@ -0,0 +1,6 @@
1
+ // ACCEPTED: global catalog data is the same for every caller — must NOT be flagged.
2
+ export async function GET(req: unknown, res: { json: (b: unknown) => void }): Promise<void> {
3
+ const query = { graph: async (_c: unknown) => ({ data: [] as unknown[] }) }
4
+ const { data } = await query.graph({ entity: 'product_variant', fields: ['id'] })
5
+ res.json({ variants: data })
6
+ }
@@ -0,0 +1,6 @@
1
+ // BREACH: reads a customer-owned entity with no caller scoping at all.
2
+ export async function GET(req: unknown, res: { json: (b: unknown) => void }): Promise<void> {
3
+ const query = { graph: async (_c: unknown) => ({ data: [] as unknown[] }) }
4
+ const { data } = await query.graph({ entity: 'order', fields: ['id'] })
5
+ res.json({ orders: data })
6
+ }
@@ -0,0 +1,13 @@
1
+ // ACCEPTED: the same read, narrowed to the authenticated caller.
2
+ export async function GET(
3
+ req: { auth_context?: { actor_id?: string } },
4
+ res: { json: (b: unknown) => void },
5
+ ): Promise<void> {
6
+ const query = { graph: async (_c: unknown) => ({ data: [] as unknown[] }) }
7
+ const { data } = await query.graph({
8
+ entity: 'order',
9
+ fields: ['id'],
10
+ filters: { customer_id: req.auth_context?.actor_id },
11
+ })
12
+ res.json({ orders: data })
13
+ }
@@ -0,0 +1,3 @@
1
+ export default function Page() {
2
+ return <div>inline markup instead of a tissue</div>
3
+ }
@@ -0,0 +1,3 @@
1
+ export default {
2
+ fetch: async (): Promise<Response> => new Response('ok'),
3
+ }
@@ -0,0 +1,5 @@
1
+ // Breaches atom-no-deps: atoms are the lowest tier — they must not import from a
2
+ // higher level (molecules/compounds/cells/...). Atoms are dependency-free leaves.
3
+ import { Field } from '../molecules/field'
4
+
5
+ export const BadAtom = Field
@@ -0,0 +1,4 @@
1
+ // A clean atom: the lowest tier, no higher-level imports.
2
+ export function GoodAtom() {
3
+ return <button type="button">x</button>
4
+ }
@@ -0,0 +1,2 @@
1
+ // A bare brand name in kit source — no-brand-names must FIRE.
2
+ export const FOOTER_NOTE = 'Powered by Acme'
@@ -0,0 +1,2 @@
1
+ // Only an allowed media.acme.com demo URL — no-brand-names must NOT fire.
2
+ export const HERO = 'https://media.acme.com/2023/06/hero.jpg'
@@ -0,0 +1,4 @@
1
+ import { OtherCell } from './other-cell'
2
+ export function BadCrossCell() {
3
+ return <OtherCell />
4
+ }
@@ -0,0 +1,3 @@
1
+ // A pure-logic .ts file inside a bio folder (cells/) — no-ts-in-bio-folders must
2
+ // FIRE. Pure logic belongs in lib/.
3
+ export const computeThing = (n: number) => n * 2
@@ -0,0 +1,6 @@
1
+ // A stateless props-in/JSX-out display with no hook, no organelle, no store, and
2
+ // no state-bearing sibling. It is "inert chemistry" filed in the cell tier —
3
+ // cell-must-be-stateful must fire and push it down to compounds/.
4
+ export function BadStatelessDisplay({ label }: { label: string }) {
5
+ return <div className="badge">{label}</div>
6
+ }
@@ -0,0 +1,5 @@
1
+ // Breaches cell-no-tissues: a cell must not import a higher-level tissue.
2
+ // Direction is wrong — tissues compose cells, not the reverse.
3
+ import { SiteHeader } from '../tissues/site-header'
4
+
5
+ export const BadTissueImport = SiteHeader
@@ -0,0 +1,9 @@
1
+ export function BadTriplicate() {
2
+ return (
3
+ <div>
4
+ <p className="mb-[4px] text-[12px] leading-[16px] font-bold">a</p>
5
+ <p className="mb-[4px] text-[12px] leading-[16px] font-bold">b</p>
6
+ <p className="mb-[4px] text-[12px] leading-[16px] font-bold">c</p>
7
+ </div>
8
+ )
9
+ }
@@ -0,0 +1,6 @@
1
+ export function FirstWidget() {
2
+ return <div data-testid="1"><span>a</span><span>b</span></div>
3
+ }
4
+ export function SecondWidget() {
5
+ return <section data-testid="2"><p>x</p><p>y</p></section>
6
+ }
@@ -0,0 +1,8 @@
1
+ // Breaches no-type-definitions-in-components: a component .tsx must not declare a
2
+ // type/interface — inline the props in the signature, or move shared types to
3
+ // features/*/lib/types.ts.
4
+ type Label = string
5
+
6
+ export function BadTypeDef({ label }: { label: Label }) {
7
+ return <button type="button">{label}</button>
8
+ }
@@ -0,0 +1 @@
1
+ export const LazyBadge = () => <span data-testid="lazy">lazy</span>
@@ -0,0 +1,9 @@
1
+ export function GoodIterated({ items }: { items: string[] }) {
2
+ return (
3
+ <ul>
4
+ {items.map((i) => (
5
+ <li key={i} className="font-primary text-[12px] leading-[16px] font-bold">{i}</li>
6
+ ))}
7
+ </ul>
8
+ )
9
+ }
@@ -0,0 +1,10 @@
1
+ const COLOR_MAP = { a: 'x', b: 'y' }
2
+ function privateHelper(n: number) {
3
+ return n * 2
4
+ }
5
+ export function GoodOneExport() {
6
+ return <div data-testid="g">{privateHelper(COLOR_MAP.a.length)}</div>
7
+ }
8
+ export function GoodOneExportRed() {
9
+ return <GoodOneExport />
10
+ }
@@ -0,0 +1,3 @@
1
+ export function GoodOwnSiblingSection() {
2
+ return <div data-testid="section">section</div>
3
+ }
@@ -0,0 +1,4 @@
1
+ import { GoodOwnSiblingSection } from './good-own-sibling.sections'
2
+ export function GoodOwnSibling() {
3
+ return <GoodOwnSiblingSection />
4
+ }
@@ -0,0 +1,3 @@
1
+ // Co-located <component>.types.ts — the kit's allowed convention.
2
+ // no-ts-in-bio-folders must NOT fire.
3
+ export type GoodShape = { id: string }
@@ -0,0 +1,12 @@
1
+ import { useState } from 'react'
2
+
3
+ // A genuine cell: it OWNS state (a useState hook). cell-must-be-stateful must
4
+ // stay silent here — this is the living unit the cell tier is for.
5
+ export function GoodStateful() {
6
+ const [open, setOpen] = useState(false)
7
+ return (
8
+ <button onClick={() => setOpen(!open)} type="button">
9
+ {open ? 'on' : 'off'}
10
+ </button>
11
+ )
12
+ }
@@ -0,0 +1,2 @@
1
+ export { GoodStateful } from './good-stateful'
2
+ export { OtherCell } from './other-cell'
@@ -0,0 +1,3 @@
1
+ export function OtherCell() {
2
+ return <div data-testid="other">other</div>
3
+ }
@@ -0,0 +1,5 @@
1
+ // this file is mostly commentary
2
+ // which is the thing the rule measures
3
+ // four lines of it, in a row
4
+ // so the block ceiling is passed too
5
+ export const one = 1
@@ -0,0 +1,3 @@
1
+ export function BadCardDiv() {
2
+ return <div className="border bg-white">card</div>
3
+ }
@@ -0,0 +1,5 @@
1
+ import { Picker } from './good-compound'
2
+
3
+ export function BadInlineData() {
4
+ return <Picker options={[{ id: 'a' }, { id: 'b' }, { id: 'c' }]} />
5
+ }
@@ -0,0 +1,8 @@
1
+ // Breaches compound-must-be-stateless: a compound is pure props-in / JSX-out —
2
+ // no hooks. If you need state, this is a cell or an organelle.
3
+ import { useState } from 'react'
4
+
5
+ export function BadStateful() {
6
+ const [open] = useState(false)
7
+ return open ? 'a' : 'b'
8
+ }
@@ -0,0 +1,5 @@
1
+ // Breaches compound-no-stores: a compound (level 3) may import only atoms /
2
+ // molecules / other compounds — never a store (or any higher-level tier).
3
+ import { useCartStore } from '../stores/cart'
4
+
5
+ export const BadStoreImport = useCartStore
@@ -0,0 +1,6 @@
1
+ import { Button } from '../atoms/button'
2
+
3
+ // A clean compound: composes a lower-level atom, no store / higher-level import.
4
+ export function GoodCompound() {
5
+ return <Button />
6
+ }
@@ -0,0 +1 @@
1
+ export const apiUrl = process.env.API_URL ?? ''
@@ -0,0 +1,5 @@
1
+ export const move = async (db: { transaction: (fn: () => Promise<void>) => Promise<void> }) => {
2
+ await db.transaction(async () => {
3
+ return undefined
4
+ })
5
+ }
@@ -0,0 +1,3 @@
1
+ import { pgTable } from 'drizzle-orm/pg-core'
2
+
3
+ export const table = pgTable
@@ -0,0 +1,3 @@
1
+ import { eq } from 'drizzle-orm'
2
+
3
+ export const where = eq
@@ -0,0 +1,3 @@
1
+ import { sql } from './fake-sql'
2
+
3
+ export const all = sql`select id from documents`
@@ -0,0 +1,6 @@
1
+ import { useBillingStore } from '@/features/billing/stores/billing-store'
2
+
3
+ export function BadCrossFeatureStore() {
4
+ const total = useBillingStore((state) => state.total)
5
+ return <span>{total}</span>
6
+ }
@@ -0,0 +1 @@
1
+ export const widget = 'widgets/ is not a tier'
@@ -0,0 +1,5 @@
1
+ import { step } from 'sagaflow-js'
2
+
3
+ // ACCEPTED: outside the configured `within`. An inline run shares the request's connection and
4
+ // never hibernates, so nothing here needs a per-step handle.
5
+ export const price = step('price', async () => ({ ok: true }))
@@ -0,0 +1,59 @@
1
+ {
2
+ "plugin": "biological-architecture",
3
+ "rules": [
4
+ "biological-architecture/atom-no-deps",
5
+ "biological-architecture/cell-must-be-stateful",
6
+ "biological-architecture/cell-must-not-compose-cell",
7
+ "biological-architecture/cell-no-tissues",
8
+ "biological-architecture/cells-folder-index-is-barrel",
9
+ "biological-architecture/compound-must-be-stateless",
10
+ "biological-architecture/compound-no-stores",
11
+ "biological-architecture/constants-in-constants-file",
12
+ "biological-architecture/dialect-through-the-seam",
13
+ "biological-architecture/document-sagas-are-generic",
14
+ "biological-architecture/documents-share-one-table",
15
+ "biological-architecture/effect-hook-naming",
16
+ "biological-architecture/max-comment-density",
17
+ "biological-architecture/molecule-atoms-only",
18
+ "biological-architecture/molecule-must-compose",
19
+ "biological-architecture/next-route-segment-is-thin-delegate",
20
+ "biological-architecture/no-brand-names",
21
+ "biological-architecture/no-card-shaped-div",
22
+ "biological-architecture/no-cross-feature-stores",
23
+ "biological-architecture/no-d1-transaction",
24
+ "biological-architecture/no-duplicate-jsx-patterns",
25
+ "biological-architecture/no-hook-in-component-disguise",
26
+ "biological-architecture/no-inert-hidden-jsx",
27
+ "biological-architecture/no-inline-data-in-jsx",
28
+ "biological-architecture/no-invalid-feature-folders",
29
+ "biological-architecture/no-logic-in-component-files",
30
+ "biological-architecture/no-orm-outside-db",
31
+ "biological-architecture/no-raw-html-atoms",
32
+ "biological-architecture/no-raw-sql-outside-allowed",
33
+ "biological-architecture/no-react-namespace",
34
+ "biological-architecture/no-renamed-html-props",
35
+ "biological-architecture/no-render-prop-reader",
36
+ "biological-architecture/no-trivial-wrapper-component",
37
+ "biological-architecture/no-ts-in-bio-folders",
38
+ "biological-architecture/no-type-definitions-in-components",
39
+ "biological-architecture/no-void-port",
40
+ "biological-architecture/organelle-dependency",
41
+ "biological-architecture/organelle-single-source",
42
+ "biological-architecture/queries-require-org-scope",
43
+ "biological-architecture/queue-loop-is-the-library",
44
+ "biological-architecture/ssot-no-inline-facts",
45
+ "biological-architecture/ssot-no-process-env",
46
+ "biological-architecture/step-opens-its-own-cell",
47
+ "biological-architecture/store-route-scopes-tenant-data",
48
+ "biological-architecture/tables-declare-their-plane",
49
+ "biological-architecture/tenant-tables-carry-org-id",
50
+ "biological-architecture/time-through-the-door",
51
+ "biological-architecture/tissue-must-compose",
52
+ "biological-architecture/tissue-no-data-props",
53
+ "biological-architecture/tissue-no-hooks",
54
+ "biological-architecture/tissue-no-organelles",
55
+ "biological-architecture/tissue-no-stores",
56
+ "biological-architecture/worker-handles-are-scoped",
57
+ "biological-architecture/zustand-v5-best-practices"
58
+ ]
59
+ }
@@ -0,0 +1,7 @@
1
+ // Breaches molecule-atoms-only: a molecule is an inert combination of ATOMS —
2
+ // importing a higher-level cell makes it stateful (that's a cell, not a molecule).
3
+ import { ProductCell } from '../cells/product'
4
+
5
+ export function BadCellImport() {
6
+ return <ProductCell />
7
+ }
@@ -0,0 +1,8 @@
1
+ import { Button } from '../atoms/button'
2
+
3
+ // Breaches molecule-must-compose: imports an atom but NEVER renders it (fake
4
+ // composition / lint-theater) — renders only a raw <div>. A molecule is by
5
+ // definition a combination; it must actually render an atom/molecule.
6
+ export function BadPassthrough() {
7
+ return <div>nothing composed</div>
8
+ }
@@ -0,0 +1,9 @@
1
+ import { Button } from '../atoms/good-atom'
2
+
3
+ interface BadRenamedProps {
4
+ ariaLabel: string
5
+ }
6
+
7
+ export function BadRenamed({ ariaLabel }: BadRenamedProps) {
8
+ return <Button aria-label={ariaLabel} />
9
+ }
@@ -0,0 +1,6 @@
1
+ import { Button } from '../atoms/button'
2
+
3
+ // A clean molecule: composes only lower-level atoms.
4
+ export function GoodMolecule() {
5
+ return <Button />
6
+ }
@@ -0,0 +1,7 @@
1
+ // An organelle importing a CELL — direction is inverted (cells contain
2
+ // organelles, not the reverse). organelle-dependency must FIRE on the flat path.
3
+ import { SomeCell } from '../cells/some-cell'
4
+
5
+ export function BadOrganelle() {
6
+ return <SomeCell />
7
+ }
@@ -0,0 +1,9 @@
1
+ import { useForm } from 'react-hook-form'
2
+
3
+ import { useInvoiceStore } from '../stores/invoice-store'
4
+
5
+ export function BadTwoSources() {
6
+ const total = useInvoiceStore((state) => state.total)
7
+ const { register } = useForm()
8
+ return <input {...register('total')} defaultValue={total} />
9
+ }
@@ -0,0 +1,6 @@
1
+ // An organelle importing an ATOM — allowed. organelle-dependency must NOT fire.
2
+ import { Button } from '../atoms/button'
3
+
4
+ export function GoodOrganelle() {
5
+ return <Button />
6
+ }