@afokapu/atdd-bun 0.1.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -1
- package/conventions/coder.bun/coder.bun.design-connected.convention.yaml +34 -0
- package/conventions/coder.bun/coder.bun.design-dependency-flow.convention.yaml +37 -0
- package/conventions/coder.bun/coder.bun.design-orphan-export.convention.yaml +36 -0
- package/conventions/coder.bun/coder.bun.design-primitives.convention.yaml +35 -0
- package/conventions/coder.bun/coder.bun.design-token-color.convention.yaml +38 -0
- package/conventions/coder.bun/coder.bun.design-token-hardcoded.convention.yaml +40 -0
- package/conventions/coder.bun/coder.bun.design-tokens-pure.convention.yaml +35 -0
- package/conventions/coder.bun/coder.bun.design-wagons-import.convention.yaml +35 -0
- package/detectors/bun_design_system_detector/atdd.implementation.yaml +28 -0
- package/detectors/bun_design_system_detector/checks/_design.mjs +112 -0
- package/detectors/bun_design_system_detector/checks/_map.json +10 -0
- package/detectors/bun_design_system_detector/checks/design_connected.mjs +19 -0
- package/detectors/bun_design_system_detector/checks/design_dependency_flow.mjs +20 -0
- package/detectors/bun_design_system_detector/checks/design_orphan_export.mjs +33 -0
- package/detectors/bun_design_system_detector/checks/design_primitives.mjs +16 -0
- package/detectors/bun_design_system_detector/checks/design_token_color.mjs +22 -0
- package/detectors/bun_design_system_detector/checks/design_token_hardcoded.mjs +29 -0
- package/detectors/bun_design_system_detector/checks/design_tokens_pure.mjs +23 -0
- package/detectors/bun_design_system_detector/checks/design_wagons_import.mjs +18 -0
- package/detectors/bun_design_system_detector/detect.mjs +46 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/public/index.html +6 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/public/site.css +9 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/design/components/Card.tsx +6 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/design/primitives/Button.tsx +6 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/design/primitives/Text.tsx +5 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/design/primitives/index.ts +2 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/design/templates/Page.tsx +5 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/design/tokens/colors.ts +1 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/design/tokens/spacing.ts +1 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/design/tokens/theme.css +8 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/wagons/orders/presentation/orders-view.tsx +6 -0
- package/detectors/bun_design_system_detector/fixtures/clean/app/src/wagons/orders/presentation/orders.css +8 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/connected/src/design/tokens/colors.ts +1 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/connected/src/views/plain.tsx +3 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/dependency_flow/src/design/components/Card.tsx +3 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/dependency_flow/src/design/primitives/Badge.tsx +5 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/orphan_export/src/design/primitives/Unused.tsx +3 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/primitives/src/design/primitives/Button.tsx +3 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/primitives/src/views/form.tsx +5 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/token_color/src/app.css +4 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/token_color/src/design/tokens/colors.css +1 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/token_color/src/views/alert.tsx +3 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/token_hardcoded/public/list.html +1 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/token_hardcoded/src/design/tokens/spacing.css +1 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/token_hardcoded/src/views/list.tsx +3 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/tokens_pure/src/design/tokens/swatch.tsx +9 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/wagons_import/src/design/components/OrderBadge.tsx +5 -0
- package/detectors/bun_design_system_detector/fixtures/dirty/wagons_import/src/wagons/orders/order-status.ts +1 -0
- package/integrity.json +933 -0
- package/package.json +3 -2
- package/src/cli.ts +9 -0
- package/src/enforce.ts +3 -2
- package/src/index.ts +2 -0
- package/src/integrity.ts +165 -0
- package/src/register.ts +9 -0
- package/src/setup.ts +7 -2
- package/templates/agents/AGENTS.block.md +1 -1
- package/templates/agents/atdd/SKILL.md +3 -1
- package/templates/agents/atdd-bun.integrity.test.ts +5 -0
- package/templates/github/atdd-bun.yml +1 -0
package/README.md
CHANGED
|
@@ -90,6 +90,7 @@ name.
|
|
|
90
90
|
| `coder`, `tester`, `security`, `architecture`, `metrics`, `runtime` | Bun source and test conventions for that concern |
|
|
91
91
|
| `interlocking` | declared train/interlocking binding, infrastructure, and coverage |
|
|
92
92
|
| `htmx` | htmx-specific source and test conventions |
|
|
93
|
+
| `design` | the design system: tokens ← primitives ← components ← templates, token-only colors, spacing, radii, and motion (also part of `coder`) |
|
|
93
94
|
| `all` | the complete package policy, normally used by CI |
|
|
94
95
|
|
|
95
96
|
The package ships the canonical planner-node corpus as planning reference, but
|
|
@@ -110,6 +111,29 @@ carry Station Master actions; the Bun interlocking family checks those actions r
|
|
|
110
111
|
Hooks, direct CLI use, and CI invoke this same profile and therefore share the
|
|
111
112
|
same schema source.
|
|
112
113
|
|
|
114
|
+
### Design system
|
|
115
|
+
|
|
116
|
+
The `design` profile (also part of `coder`) is the Bun realization of the core
|
|
117
|
+
`coder.design.*` obligations. It recognizes a design system by its directory:
|
|
118
|
+
a folder named `design`, `design_system`, or `design-system`, whose first
|
|
119
|
+
subfolder names the layer:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
design/
|
|
123
|
+
tokens/ or foundations/ values only: palette, spacing, radii, motion
|
|
124
|
+
primitives/ Button, Text, Stack … built from tokens
|
|
125
|
+
components/ composed from primitives
|
|
126
|
+
templates/ page structure composed from components
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Imports flow downward only, and the design system never imports app code.
|
|
130
|
+
Outside the tokens layer, `.tsx`, `.html`, and `.css` files take colors,
|
|
131
|
+
spacing, radii, and durations from tokens (`var(--…)`); app components render
|
|
132
|
+
controls through primitives and import at least one design-system element; and
|
|
133
|
+
every exported component has a consumer. Defining a custom property
|
|
134
|
+
(`--accent: #0ea5e9`) is defining a token and is allowed anywhere. A repository
|
|
135
|
+
without a design directory is not judged by these rules.
|
|
136
|
+
|
|
113
137
|
### Theme and contract registry
|
|
114
138
|
|
|
115
139
|
Theme vocabulary belongs to the repository, not the package. When a plan uses
|
|
@@ -142,12 +166,13 @@ when they are absent. It never overwrites another hook path, an existing
|
|
|
142
166
|
generated workflow, or an existing skill unless you explicitly pass `--replace`. Installing the dependency alone deliberately does
|
|
143
167
|
neither: package installation must not mutate a repository through postinstall.
|
|
144
168
|
|
|
145
|
-
Use `hooks install`, `ci init`, or `
|
|
169
|
+
Use `hooks install`, `ci init`, `agent init`, or `integrity init` when only one surface is wanted:
|
|
146
170
|
|
|
147
171
|
```sh
|
|
148
172
|
bun run atdd-bun hooks install
|
|
149
173
|
bun run atdd-bun ci init
|
|
150
174
|
bun run atdd-bun agent init
|
|
175
|
+
bun run atdd-bun integrity init
|
|
151
176
|
```
|
|
152
177
|
|
|
153
178
|
The hooks enforce protected-branch blocking, micro-commit limits, mass-delete
|
|
@@ -206,6 +231,31 @@ it stays correct as they change; after upgrading, refresh it with
|
|
|
206
231
|
`bun run atdd-bun agent init --replace`. The skill steers the agent; the
|
|
207
232
|
profiles, hooks, and CI remain the enforcement.
|
|
208
233
|
|
|
234
|
+
## Integrity: files agents must not change
|
|
235
|
+
|
|
236
|
+
Coding agents can edit anything on the machine they run on, including
|
|
237
|
+
`node_modules/@afokapu/atdd-bun`, the files this package generates, and
|
|
238
|
+
`atdd-bun.yaml`. `init` therefore writes `atdd-bun.integrity.test.ts` (into the
|
|
239
|
+
`[test] root` from `bunfig.toml`, if one is set), and the generated CI workflow
|
|
240
|
+
runs `bun run atdd-bun integrity` before enforcement. Both run the same check:
|
|
241
|
+
|
|
242
|
+
| Checked | Canonical source | Restore |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| Every file of the installed package | `integrity.json`, the hashes published with the package | `bun install --force` |
|
|
245
|
+
| The dependency is an npm version range, locked to the registry with an integrity hash | npm | `bun add -d @afokapu/atdd-bun` |
|
|
246
|
+
| The CI workflow, both skills, the `AGENTS.md` block, and the integrity test | what the installed version generates (its version stamp is ignored) | `bun run atdd-bun init --replace` |
|
|
247
|
+
| `atdd-bun.yaml` is not looser than on the branch being merged into | the merge base; for a push to the base branch, the previous commit | `git checkout <base> -- atdd-bun.yaml` |
|
|
248
|
+
|
|
249
|
+
A failure is addressed to the agent: it lists every changed file with its
|
|
250
|
+
restore command and tells it to stop and ask a human instead of working around
|
|
251
|
+
the check. Locally, this is a reminder an agent can still ignore, because
|
|
252
|
+
anything on its machine can be edited. In CI the check runs on a clean install,
|
|
253
|
+
so its verdict cannot be faked. Loosening the policy remains possible, as a
|
|
254
|
+
separate change a human approves.
|
|
255
|
+
|
|
256
|
+
After upgrading to a version that changes generated files, run
|
|
257
|
+
`bun run atdd-bun init --replace` and commit the result.
|
|
258
|
+
|
|
209
259
|
## CI: the merge gate
|
|
210
260
|
|
|
211
261
|
Generate the repository-owned workflow with:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: coder.bun.design-connected
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: Every rendering app component is connected to the design system
|
|
6
|
+
statement: An app component (`.tsx`/`.jsx` outside the design root) that renders markup MUST import at least one
|
|
7
|
+
design-system element — token, primitive, component, or template — so no UI surface is disconnected from the shared
|
|
8
|
+
system. Bun realization of `coder.design.orphan-ui`.
|
|
9
|
+
terms:
|
|
10
|
+
- term_id: design_root
|
|
11
|
+
text: 'a directory named `design`, `design_system`, or `design-system`. The first directory beneath it names the
|
|
12
|
+
layer: `tokens` or `foundations`, `primitives`, `components`, `templates`. A repository with no design root
|
|
13
|
+
is not judged (self-scoping): there is no system to connect to yet.'
|
|
14
|
+
- term_id: rendering_component
|
|
15
|
+
text: a component source file whose code (literals and comments masked) contains a closing tag or a self-closing
|
|
16
|
+
element.
|
|
17
|
+
content:
|
|
18
|
+
summary: Flags rendering component files outside the design root that import nothing from it.
|
|
19
|
+
normative_text: A screen built without the system is where the next one-off style lands. Requiring one design
|
|
20
|
+
import makes the connection explicit and reviewable.
|
|
21
|
+
fix_hint: Compose the screen from a template, component, or primitive — at minimum wrap it in the page template.
|
|
22
|
+
exceptions:
|
|
23
|
+
- Test files (`*.test.*`, `*.spec.*`) and vendored/build directories are never scanned.
|
|
24
|
+
- Non-rendering modules (routes, handlers, data) are not judged.
|
|
25
|
+
metadata:
|
|
26
|
+
aliases:
|
|
27
|
+
- BUN-DESIGN-CONNECTED-001
|
|
28
|
+
core_obligation: coder.design.orphan-ui
|
|
29
|
+
severity: 2
|
|
30
|
+
disposition: strict
|
|
31
|
+
introduced_in: 0.2.0
|
|
32
|
+
implementation:
|
|
33
|
+
type: validator
|
|
34
|
+
ref: bun_design_system_detector
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: coder.bun.design-dependency-flow
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: Design-system layers import only their own layer or a lower one
|
|
6
|
+
statement: A file under a design root MUST import only from its own layer or a lower one in the hierarchy tokens/foundations
|
|
7
|
+
(0) <- primitives (1) <- components (2) <- templates (3). Upward edges — tokens importing primitives, primitives
|
|
8
|
+
importing components, components importing templates — are forbidden. Bun realization of the core obligation `coder.design.hierarchy-import`,
|
|
9
|
+
mirroring `coder.vite.design-dependency-flow`.
|
|
10
|
+
terms:
|
|
11
|
+
- term_id: design_root
|
|
12
|
+
text: 'a directory named `design`, `design_system`, or `design-system`. The first directory beneath it names the
|
|
13
|
+
layer: `tokens` or `foundations`, `primitives`, `components`, `templates`. A repository with no design root
|
|
14
|
+
is not judged (self-scoping): there is no system to connect to yet.'
|
|
15
|
+
- term_id: upward_import
|
|
16
|
+
text: an import from a design-system file into a HIGHER layer, resolved from a relative path or from an aliased
|
|
17
|
+
specifier that names the design directory (`@/design/components/Card`).
|
|
18
|
+
content:
|
|
19
|
+
summary: Resolves each import of a design-system file to its target layer and flags any edge that points upward.
|
|
20
|
+
normative_text: A hierarchy is only a hierarchy if dependencies flow one way. A primitive that imports a component
|
|
21
|
+
can no longer be reused without dragging that component along, and the layers collapse into one tangle.
|
|
22
|
+
fix_hint: 'Move the shared piece down to the lowest layer that needs it, or compose upward: the component imports
|
|
23
|
+
the primitive, never the reverse.'
|
|
24
|
+
exceptions:
|
|
25
|
+
- Test files (`*.test.*`, `*.spec.*`) and vendored/build directories are never scanned.
|
|
26
|
+
- Imports within the same layer are allowed.
|
|
27
|
+
- Files directly under the design root, outside any named layer, are not layered.
|
|
28
|
+
metadata:
|
|
29
|
+
aliases:
|
|
30
|
+
- BUN-DESIGN-DEPENDENCY-FLOW-001
|
|
31
|
+
core_obligation: coder.design.hierarchy-import
|
|
32
|
+
severity: 3
|
|
33
|
+
disposition: strict
|
|
34
|
+
introduced_in: 0.2.0
|
|
35
|
+
implementation:
|
|
36
|
+
type: validator
|
|
37
|
+
ref: bun_design_system_detector
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: coder.bun.design-orphan-export
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: Design-system components have a consumer
|
|
6
|
+
statement: Every component a `primitives`, `components`, or `templates` file exports MUST be imported by at least
|
|
7
|
+
one other file — a higher design layer or app code — so the system stays lean. Re-exports through an `index.*`
|
|
8
|
+
barrel are not consumption. Bun realization of `coder.design.orphan-export`, counting consumers the way `coder.vite.design-orphan-ui`
|
|
9
|
+
does, so composition inside the system (a template using a component) is legitimate.
|
|
10
|
+
terms:
|
|
11
|
+
- term_id: design_root
|
|
12
|
+
text: 'a directory named `design`, `design_system`, or `design-system`. The first directory beneath it names the
|
|
13
|
+
layer: `tokens` or `foundations`, `primitives`, `components`, `templates`. A repository with no design root
|
|
14
|
+
is not judged (self-scoping): there is no system to connect to yet.'
|
|
15
|
+
- term_id: consumer
|
|
16
|
+
text: a non-barrel file whose import clause names the export, or imports it as a default under that name.
|
|
17
|
+
content:
|
|
18
|
+
summary: Collects capitalized exports of primitives/components/templates and flags those no other file imports.
|
|
19
|
+
normative_text: Unused components still have to be maintained, themed, and migrated. A design system grows by
|
|
20
|
+
adjudication, not by accretion of pieces nobody uses.
|
|
21
|
+
fix_hint: 'Use the component where it was meant to be used, or delete it. Unused chains surface one layer at a
|
|
22
|
+
time: remove the unused template and its component is reported next.'
|
|
23
|
+
exceptions:
|
|
24
|
+
- Test files (`*.test.*`, `*.spec.*`) and vendored/build directories are never scanned.
|
|
25
|
+
- Token/foundation values and `index.*` barrels are not judged.
|
|
26
|
+
- Only capitalized (component) exports are judged.
|
|
27
|
+
metadata:
|
|
28
|
+
aliases:
|
|
29
|
+
- BUN-DESIGN-ORPHAN-EXPORT-001
|
|
30
|
+
core_obligation: coder.design.orphan-export
|
|
31
|
+
severity: 2
|
|
32
|
+
disposition: strict
|
|
33
|
+
introduced_in: 0.2.0
|
|
34
|
+
implementation:
|
|
35
|
+
type: validator
|
|
36
|
+
ref: bun_design_system_detector
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: coder.bun.design-primitives
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: App components compose design-system primitives for interactive controls
|
|
6
|
+
statement: An app component (`.tsx`/`.jsx` outside the design root) MUST render interactive controls through a design-system
|
|
7
|
+
primitive rather than a raw `<button>`, `<input>`, `<select>`, or `<textarea>`, so controls stay themed, accessible,
|
|
8
|
+
and consistent. Bun realization of `coder.design.primitives`, mirroring `coder.vite.design-primitives`.
|
|
9
|
+
terms:
|
|
10
|
+
- term_id: design_root
|
|
11
|
+
text: 'a directory named `design`, `design_system`, or `design-system`. The first directory beneath it names the
|
|
12
|
+
layer: `tokens` or `foundations`, `primitives`, `components`, `templates`. A repository with no design root
|
|
13
|
+
is not judged (self-scoping): there is no system to connect to yet.'
|
|
14
|
+
- term_id: raw_control
|
|
15
|
+
text: a lowercase `<button`, `<input`, `<select`, or `<textarea` element written in app code.
|
|
16
|
+
content:
|
|
17
|
+
summary: Flags raw interactive platform elements in component source outside the design root.
|
|
18
|
+
normative_text: Controls are where focus, disabled, error, and loading states live. Each raw control re-implements
|
|
19
|
+
them, and each re-implementation diverges.
|
|
20
|
+
fix_hint: 'Render the primitive and pass htmx attributes through it: `<Button hx-post="/orders">Place</Button>`.
|
|
21
|
+
Add the primitive to `design/primitives/` if it does not exist yet.'
|
|
22
|
+
exceptions:
|
|
23
|
+
- Test files (`*.test.*`, `*.spec.*`) and vendored/build directories are never scanned.
|
|
24
|
+
- The design system's own primitives render the raw elements and are not scanned.
|
|
25
|
+
- Plain `.html` templates cannot import components and are out of scope.
|
|
26
|
+
metadata:
|
|
27
|
+
aliases:
|
|
28
|
+
- BUN-DESIGN-PRIMITIVES-001
|
|
29
|
+
core_obligation: coder.design.primitives
|
|
30
|
+
severity: 2
|
|
31
|
+
disposition: strict
|
|
32
|
+
introduced_in: 0.2.0
|
|
33
|
+
implementation:
|
|
34
|
+
type: validator
|
|
35
|
+
ref: bun_design_system_detector
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: coder.bun.design-token-color
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: UI files use color tokens, not raw color literals
|
|
6
|
+
statement: Outside the tokens layer, a UI file (`.tsx`, `.jsx`, `.html`, `.htm`, `.css`) MUST NOT write a raw color
|
|
7
|
+
literal — `#rgb`/`#rrggbb`/`#rrggbbaa`, `rgb()`/`rgba()`, `hsl()`/`hsla()` — in a CSS declaration, a `style` attribute,
|
|
8
|
+
a `<style>` block, a JSX `style={{…}}` object, or a `fill`/`stroke`/`bgcolor` attribute. Colors come from tokens
|
|
9
|
+
(`var(--…)`). Bun realization of `coder.design.token-color`, mirroring `coder.vite.design-token-color`.
|
|
10
|
+
terms:
|
|
11
|
+
- term_id: design_root
|
|
12
|
+
text: 'a directory named `design`, `design_system`, or `design-system`. The first directory beneath it names the
|
|
13
|
+
layer: `tokens` or `foundations`, `primitives`, `components`, `templates`. A repository with no design root
|
|
14
|
+
is not judged (self-scoping): there is no system to connect to yet.'
|
|
15
|
+
- term_id: raw_color_literal
|
|
16
|
+
text: 'a hex, rgb(a), or hsl(a) color written in a declaration value. Selectors are not values: `#fade:hover {`
|
|
17
|
+
is never read as a color.'
|
|
18
|
+
content:
|
|
19
|
+
summary: Reads declarations from stylesheets, style attributes, style blocks, and JSX style objects, and flags
|
|
20
|
+
raw color literals in their values.
|
|
21
|
+
normative_text: A raw color is a theme decision made in one place. It does not follow dark mode, it drifts from
|
|
22
|
+
the palette, and nobody can find it when the palette changes.
|
|
23
|
+
fix_hint: 'Define the color once as a custom property in the tokens layer and reference it: `color: var(--danger)`.'
|
|
24
|
+
exceptions:
|
|
25
|
+
- Test files (`*.test.*`, `*.spec.*`) and vendored/build directories are never scanned.
|
|
26
|
+
- Custom property definitions (`--danger: #b91c1c`) define tokens wherever they live and are not flagged.
|
|
27
|
+
- The tokens/foundations layer is where raw colors are defined and is not scanned.
|
|
28
|
+
- Named colors (`red`, `white`) and `currentColor`/`transparent` are not flagged.
|
|
29
|
+
metadata:
|
|
30
|
+
aliases:
|
|
31
|
+
- BUN-DESIGN-TOKEN-COLOR-001
|
|
32
|
+
core_obligation: coder.design.token-color
|
|
33
|
+
severity: 2
|
|
34
|
+
disposition: strict
|
|
35
|
+
introduced_in: 0.2.0
|
|
36
|
+
implementation:
|
|
37
|
+
type: validator
|
|
38
|
+
ref: bun_design_system_detector
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: coder.bun.design-token-hardcoded
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: UI files take spacing, radii, and durations from foundations
|
|
6
|
+
statement: Outside the tokens layer, a UI file MUST NOT hardcode a spacing, inset, or border-radius length greater
|
|
7
|
+
than 1px (`margin`, `padding`, `gap`, `inset`, `top`/`right`/`bottom`/`left`, `border-radius`), nor a non-zero
|
|
8
|
+
duration in `transition`/`animation`. A bare number in a JSX style object for those properties counts as a length.
|
|
9
|
+
These values come from foundations tokens. Bun realization of `coder.design.token-hardcoded` and of the raw-pixel
|
|
10
|
+
part of `coder.design.foundations`.
|
|
11
|
+
terms:
|
|
12
|
+
- term_id: design_root
|
|
13
|
+
text: 'a directory named `design`, `design_system`, or `design-system`. The first directory beneath it names the
|
|
14
|
+
layer: `tokens` or `foundations`, `primitives`, `components`, `templates`. A repository with no design root
|
|
15
|
+
is not judged (self-scoping): there is no system to connect to yet.'
|
|
16
|
+
- term_id: visual_token
|
|
17
|
+
text: a spacing, radius, or motion value defined once in foundations and referenced as `var(--…)`.
|
|
18
|
+
content:
|
|
19
|
+
summary: Flags hardcoded lengths and durations in spacing, radius, and motion declarations across UI files, including
|
|
20
|
+
the design system's own primitives and components.
|
|
21
|
+
normative_text: Spacing and motion are the rhythm of an interface. Each hardcoded `13px` is a new step on a scale
|
|
22
|
+
nobody chose, and the rhythm erodes one component at a time.
|
|
23
|
+
fix_hint: 'Use the foundations scale: `padding: var(--space-3)`, `border-radius: var(--radius)`, `transition:
|
|
24
|
+
opacity var(--fast)`. Add a step to the scale in the tokens layer if none fits.'
|
|
25
|
+
exceptions:
|
|
26
|
+
- Test files (`*.test.*`, `*.spec.*`) and vendored/build directories are never scanned.
|
|
27
|
+
- 0 and 1px (hairline borders) are not design decisions and are allowed.
|
|
28
|
+
- Custom property definitions (`--space-3: 12px`) define tokens wherever they live and are not flagged.
|
|
29
|
+
- The tokens/foundations layer defines these values and is not scanned.
|
|
30
|
+
- Widths, heights, and font sizes are out of scope for this rule.
|
|
31
|
+
metadata:
|
|
32
|
+
aliases:
|
|
33
|
+
- BUN-DESIGN-TOKEN-HARDCODED-001
|
|
34
|
+
core_obligation: coder.design.token-hardcoded
|
|
35
|
+
severity: 2
|
|
36
|
+
disposition: strict
|
|
37
|
+
introduced_in: 0.2.0
|
|
38
|
+
implementation:
|
|
39
|
+
type: validator
|
|
40
|
+
ref: bun_design_system_detector
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: coder.bun.design-tokens-pure
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: Token and foundation files hold values, not behaviour
|
|
6
|
+
statement: A code file in the `tokens` or `foundations` layer MUST contain only values — palette, spacing, radii,
|
|
7
|
+
motion, scales — and MUST NOT import a renderer (`hono/jsx`, `react`, `preact`, `@kitajs/html`), render markup,
|
|
8
|
+
or branch with `if`/`for`/`while`/`switch`. Bun realization of `coder.design.foundations` (the bottom of the hierarchy
|
|
9
|
+
is pure), mirroring `coder.vite.design-tokens-pure`.
|
|
10
|
+
terms:
|
|
11
|
+
- term_id: design_root
|
|
12
|
+
text: 'a directory named `design`, `design_system`, or `design-system`. The first directory beneath it names the
|
|
13
|
+
layer: `tokens` or `foundations`, `primitives`, `components`, `templates`. A repository with no design root
|
|
14
|
+
is not judged (self-scoping): there is no system to connect to yet.'
|
|
15
|
+
- term_id: pure_token
|
|
16
|
+
text: 'a declaration whose value is data: a string, number, or object of them, including `var(--…)` references.'
|
|
17
|
+
content:
|
|
18
|
+
summary: Scans token/foundation code files for renderer imports, rendered markup, and control flow.
|
|
19
|
+
normative_text: Tokens are what every other layer depends on. Once they render or decide, the bottom of the hierarchy
|
|
20
|
+
has behaviour, and every primitive inherits it.
|
|
21
|
+
fix_hint: Keep the value in the token file and move the markup to `primitives/` or the logic to the component
|
|
22
|
+
that needs it.
|
|
23
|
+
exceptions:
|
|
24
|
+
- Test files (`*.test.*`, `*.spec.*`) and vendored/build directories are never scanned.
|
|
25
|
+
- '`.css` token files are values by construction and are not scanned by this rule.'
|
|
26
|
+
metadata:
|
|
27
|
+
aliases:
|
|
28
|
+
- BUN-DESIGN-TOKENS-PURE-001
|
|
29
|
+
core_obligation: coder.design.foundations
|
|
30
|
+
severity: 2
|
|
31
|
+
disposition: strict
|
|
32
|
+
introduced_in: 0.2.0
|
|
33
|
+
implementation:
|
|
34
|
+
type: validator
|
|
35
|
+
ref: bun_design_system_detector
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: coder.bun.design-wagons-import
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: The design system never imports app or wagon code
|
|
6
|
+
statement: A file under a design root MUST NOT import a relative path that resolves outside the design root. Wagons
|
|
7
|
+
and app code import FROM the design system, never the reverse, so the system stays wagon-agnostic. Bun realization
|
|
8
|
+
of the outward half of `coder.design.hierarchy-import`, mirroring `coder.vite.design-wagons-import`.
|
|
9
|
+
terms:
|
|
10
|
+
- term_id: design_root
|
|
11
|
+
text: 'a directory named `design`, `design_system`, or `design-system`. The first directory beneath it names the
|
|
12
|
+
layer: `tokens` or `foundations`, `primitives`, `components`, `templates`. A repository with no design root
|
|
13
|
+
is not judged (self-scoping): there is no system to connect to yet.'
|
|
14
|
+
- term_id: outward_import
|
|
15
|
+
text: a relative import from a design-system file whose target lies outside every design root.
|
|
16
|
+
content:
|
|
17
|
+
summary: Flags relative imports from a design-system file that leave the design root.
|
|
18
|
+
normative_text: 'A design system that reaches into a wagon has become part of that wagon: it cannot be reused
|
|
19
|
+
by the next one, and a change to the wagon breaks the system.'
|
|
20
|
+
fix_hint: Pass the value in as a prop, or move the shared type or constant into the design system (or a shared
|
|
21
|
+
commons module) so the dependency points inward.
|
|
22
|
+
exceptions:
|
|
23
|
+
- Test files (`*.test.*`, `*.spec.*`) and vendored/build directories are never scanned.
|
|
24
|
+
- Bare package specifiers (`hono/jsx`) are outside the project graph and are not judged.
|
|
25
|
+
- Aliased specifiers are judged only when they name a design directory.
|
|
26
|
+
metadata:
|
|
27
|
+
aliases:
|
|
28
|
+
- BUN-DESIGN-WAGONS-IMPORT-001
|
|
29
|
+
core_obligation: coder.design.hierarchy-import
|
|
30
|
+
severity: 3
|
|
31
|
+
disposition: strict
|
|
32
|
+
introduced_in: 0.2.0
|
|
33
|
+
implementation:
|
|
34
|
+
type: validator
|
|
35
|
+
ref: bun_design_system_detector
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
kind: implementation
|
|
3
|
+
subtype: validator
|
|
4
|
+
implementation_id: bun_design_system_detector
|
|
5
|
+
targets_workspace: atdd.workspace.bun
|
|
6
|
+
contract_version: 1.1.0
|
|
7
|
+
emits_rule_ids:
|
|
8
|
+
- coder.bun.design-dependency-flow
|
|
9
|
+
- coder.bun.design-wagons-import
|
|
10
|
+
- coder.bun.design-tokens-pure
|
|
11
|
+
- coder.bun.design-token-color
|
|
12
|
+
- coder.bun.design-token-hardcoded
|
|
13
|
+
- coder.bun.design-primitives
|
|
14
|
+
- coder.bun.design-connected
|
|
15
|
+
- coder.bun.design-orphan-export
|
|
16
|
+
entrypoint: detect.mjs
|
|
17
|
+
report: detect.mjs
|
|
18
|
+
realizes_convention:
|
|
19
|
+
# A family detector owns every rule it emits: the Bun realization of the core
|
|
20
|
+
# coder.design.* obligations (tokens <- primitives <- components <- templates).
|
|
21
|
+
- coder.bun.design-dependency-flow
|
|
22
|
+
- coder.bun.design-wagons-import
|
|
23
|
+
- coder.bun.design-tokens-pure
|
|
24
|
+
- coder.bun.design-token-color
|
|
25
|
+
- coder.bun.design-token-hardcoded
|
|
26
|
+
- coder.bun.design-primitives
|
|
27
|
+
- coder.bun.design-connected
|
|
28
|
+
- coder.bun.design-orphan-export
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Shared design-system model for the bun_design_system_detector family.
|
|
2
|
+
//
|
|
3
|
+
// A DESIGN ROOT is a directory named `design`, `design_system`, or `design-system`.
|
|
4
|
+
// The first directory beneath it names the file's LAYER, bottom to top:
|
|
5
|
+
//
|
|
6
|
+
// tokens | foundations (0) ← primitives (1) ← components (2) ← templates (3)
|
|
7
|
+
//
|
|
8
|
+
// Imports may point to the same layer or a lower one. Every rule self-scopes: a
|
|
9
|
+
// repository with no design root is not judged, because there is no system to
|
|
10
|
+
// connect to yet.
|
|
11
|
+
import { dirname, extname, normalize, resolve, sep } from "node:path";
|
|
12
|
+
import { walk, readText, maskLiteralsAndComments } from "../../../lib/scan.mjs";
|
|
13
|
+
|
|
14
|
+
export const DESIGN_DIRS = new Set(["design", "design_system", "design-system"]);
|
|
15
|
+
export const LAYERS = { tokens: 0, foundations: 0, primitives: 1, components: 2, templates: 3 };
|
|
16
|
+
export const UI_EXT = new Set([".tsx", ".jsx", ".html", ".htm", ".css"]);
|
|
17
|
+
export const CODE_EXT = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".mts"]);
|
|
18
|
+
const ALL_EXT = new Set([...UI_EXT, ...CODE_EXT]);
|
|
19
|
+
|
|
20
|
+
/** The design root containing `file` and the file's layer, or null when outside any design root. */
|
|
21
|
+
export function designOf(file) {
|
|
22
|
+
const parts = normalize(file).split(sep);
|
|
23
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
24
|
+
if (!DESIGN_DIRS.has(parts[i])) continue;
|
|
25
|
+
const layerName = parts[i + 1];
|
|
26
|
+
const layer = i + 1 < parts.length - 1 && layerName in LAYERS ? LAYERS[layerName] : null;
|
|
27
|
+
return { root: parts.slice(0, i + 1).join(sep) || sep, layerName: layer === null ? null : layerName, layer };
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Every source/UI file under the scan roots, once. */
|
|
33
|
+
export function collect(roots, excludes) {
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
for (const root of roots) for (const file of walk(root, excludes, ALL_EXT)) seen.add(resolve(file));
|
|
36
|
+
return [...seen];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const IMPORT_RE = /(?:^|[\n;])\s*(?:import|export)\s+(?:type\s+)?([\s\S]*?)\s*from\s*(['"])([^'"]+)\2|(?:^|[\n;])\s*import\s*(['"])([^'"]+)\4|\bimport\(\s*(['"])([^'"]+)\6\s*\)/g;
|
|
40
|
+
|
|
41
|
+
/** Import statements of a code file: specifier, the clause text, and its offset. */
|
|
42
|
+
export function importsOf(text) {
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const m of text.matchAll(IMPORT_RE)) {
|
|
45
|
+
const specifier = m[3] ?? m[5] ?? m[7];
|
|
46
|
+
out.push({ specifier, clause: m[1] ?? "", index: m.index + m[0].indexOf(specifier) });
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Where an import points: a path for relative specifiers, or — for aliased/absolute
|
|
53
|
+
* specifiers — the design segment it names (`@/design/primitives/Text`). Bare
|
|
54
|
+
* package names resolve to null.
|
|
55
|
+
*/
|
|
56
|
+
export function target(file, specifier) {
|
|
57
|
+
if (specifier.startsWith(".")) return { path: resolve(dirname(file), specifier) };
|
|
58
|
+
const segments = specifier.split("/");
|
|
59
|
+
const at = segments.findIndex((s) => DESIGN_DIRS.has(s));
|
|
60
|
+
if (at === -1) return null;
|
|
61
|
+
const layerName = segments[at + 1];
|
|
62
|
+
return { alias: true, layerName: layerName in LAYERS ? layerName : null, layer: layerName in LAYERS ? LAYERS[layerName] : null };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The design location an import resolves into, or null when it leaves every design root. */
|
|
66
|
+
export function designTarget(file, specifier) {
|
|
67
|
+
const t = target(file, specifier);
|
|
68
|
+
if (!t) return null;
|
|
69
|
+
if (t.alias) return { layer: t.layer, layerName: t.layerName };
|
|
70
|
+
const d = designOf(t.path + (extname(t.path) ? "" : sep + "_"));
|
|
71
|
+
return d ? { layer: d.layer, layerName: d.layerName, root: d.root, path: t.path } : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function masked(file) {
|
|
75
|
+
const text = readText(file);
|
|
76
|
+
if (text === null) return null;
|
|
77
|
+
return { text, code: extname(file) === ".css" ? text.replace(/\/\*[\s\S]*?\*\//g, (c) => c.replace(/[^\n]/g, " ")) : maskLiteralsAndComments(text) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const isUi = (file) => UI_EXT.has(extname(file));
|
|
81
|
+
export const isComponentSource = (file) => extname(file) === ".tsx" || extname(file) === ".jsx";
|
|
82
|
+
export const hasDesignRoot = (files) => files.some((file) => designOf(file));
|
|
83
|
+
|
|
84
|
+
const kebab = (prop) => prop.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
|
|
85
|
+
const CSS_DECL_RE = /([-a-zA-Z]+)\s*:\s*([^;{}]+)(?=[;}])/g;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Property/value pairs written in a UI file, with absolute offsets. CSS declarations
|
|
89
|
+
* must end in `;` or `}` so selectors (`a:hover {`) are never read as values; JSX
|
|
90
|
+
* style-object keys are converted to kebab-case, and a bare number is kept as-is.
|
|
91
|
+
*/
|
|
92
|
+
export function declarations(file, text) {
|
|
93
|
+
const out = [];
|
|
94
|
+
const css = (body, offset) => { for (const m of body.matchAll(CSS_DECL_RE)) out.push({ prop: m[1].toLowerCase(), value: m[2].trim(), index: offset + m.index }); };
|
|
95
|
+
if (extname(file) === ".css") { css(text.replace(/\/\*[\s\S]*?\*\//g, (c) => c.replace(/[^\n]/g, " ")), 0); return out; }
|
|
96
|
+
for (const m of text.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi)) css(m[1], m.index + m[0].indexOf(m[1]));
|
|
97
|
+
for (const m of text.matchAll(/\bstyle\s*=\s*(["'])([\s\S]*?)\1/g)) css(m[2] + ";", m.index + m[0].indexOf(m[2]));
|
|
98
|
+
for (const m of text.matchAll(/\bstyle\s*=\s*\{\{/g)) {
|
|
99
|
+
let depth = 0, j = m.index + m[0].length - 2;
|
|
100
|
+
for (; j < text.length; j++) { if (text[j] === "{") depth++; else if (text[j] === "}" && --depth === 0) break; }
|
|
101
|
+
const body = text.slice(m.index, j + 1);
|
|
102
|
+
for (const d of body.matchAll(/([A-Za-z]+)\s*:\s*(?:(["'`])([^"'`]*)\2|(-?\d+(?:\.\d+)?)\b)/g)) out.push({ prop: kebab(d[1]), value: d[3] ?? d[4], numeric: d[4] !== undefined, index: m.index + d.index });
|
|
103
|
+
}
|
|
104
|
+
for (const m of text.matchAll(/\b(fill|stroke|bgcolor)\s*=\s*(["'])([^"']*)\2/g)) out.push({ prop: m[1], value: m[3], index: m.index });
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Run a check over the scan roots, self-scoped to repositories that have a design root. */
|
|
109
|
+
export function scope(roots, excludes) {
|
|
110
|
+
const files = collect(roots, excludes);
|
|
111
|
+
return hasDesignRoot(files) ? files : [];
|
|
112
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"design_dependency_flow": "coder.bun.design-dependency-flow",
|
|
3
|
+
"design_wagons_import": "coder.bun.design-wagons-import",
|
|
4
|
+
"design_tokens_pure": "coder.bun.design-tokens-pure",
|
|
5
|
+
"design_token_color": "coder.bun.design-token-color",
|
|
6
|
+
"design_token_hardcoded": "coder.bun.design-token-hardcoded",
|
|
7
|
+
"design_primitives": "coder.bun.design-primitives",
|
|
8
|
+
"design_connected": "coder.bun.design-connected",
|
|
9
|
+
"design_orphan_export": "coder.bun.design-orphan-export"
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Detector: coder.bun.design-connected
|
|
3
|
+
// Every app component that renders markup imports at least one design-system
|
|
4
|
+
// element, so no UI surface is disconnected from the shared system.
|
|
5
|
+
import { readRoots, readExcludes, readText, emit, locate } from "../../../lib/scan.mjs";
|
|
6
|
+
import { scope, designOf, isComponentSource, masked, importsOf, designTarget } from "./_design.mjs";
|
|
7
|
+
|
|
8
|
+
const RULE_ID = "coder.bun.design-connected";
|
|
9
|
+
const violations = [];
|
|
10
|
+
for (const file of scope(readRoots(), readExcludes())) {
|
|
11
|
+
if (!isComponentSource(file) || designOf(file)) continue;
|
|
12
|
+
const m = masked(file);
|
|
13
|
+
if (!m) continue;
|
|
14
|
+
const renders = m.code.search(/<\/[A-Za-z]|\/>/);
|
|
15
|
+
if (renders === -1 || importsOf(m.text).some((imp) => designTarget(file, imp.specifier))) continue;
|
|
16
|
+
violations.push({ rule_id: RULE_ID, file, ...locate(m.text, renders), evidence: "component renders markup but imports nothing from the design system" });
|
|
17
|
+
}
|
|
18
|
+
process.stderr.write(`bun-detector[design-connected]: ${violations.length} violation(s)\n`);
|
|
19
|
+
emit(violations);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Detector: coder.bun.design-dependency-flow
|
|
3
|
+
// A design-system file imports only its own layer or a lower one:
|
|
4
|
+
// tokens/foundations ← primitives ← components ← templates.
|
|
5
|
+
import { readRoots, readExcludes, readText, emit, locate } from "../../../lib/scan.mjs";
|
|
6
|
+
import { scope, designOf, importsOf, designTarget } from "./_design.mjs";
|
|
7
|
+
|
|
8
|
+
const RULE_ID = "coder.bun.design-dependency-flow";
|
|
9
|
+
const violations = [];
|
|
10
|
+
for (const file of scope(readRoots(), readExcludes())) {
|
|
11
|
+
const here = designOf(file);
|
|
12
|
+
if (!here || here.layer === null) continue;
|
|
13
|
+
const text = readText(file);
|
|
14
|
+
for (const imp of importsOf(text ?? "")) {
|
|
15
|
+
const to = designTarget(file, imp.specifier);
|
|
16
|
+
if (to && to.layer !== null && to.layer > here.layer) violations.push({ rule_id: RULE_ID, file, ...locate(text, imp.index), evidence: `${here.layerName} imports upward into ${to.layerName} ("${imp.specifier}"); depend on the same or a lower layer` });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
process.stderr.write(`bun-detector[design-dependency-flow]: ${violations.length} violation(s)\n`);
|
|
20
|
+
emit(violations);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Detector: coder.bun.design-orphan-export
|
|
3
|
+
// Every component a primitives/components/templates file exports is imported by at
|
|
4
|
+
// least one other file: a higher design layer or app code. Re-exports through an
|
|
5
|
+
// index.* barrel are not consumption. Dead exports are removed, not kept.
|
|
6
|
+
import { readRoots, readExcludes, readText, emit, locate } from "../../../lib/scan.mjs";
|
|
7
|
+
import { basename, extname } from "node:path";
|
|
8
|
+
import { scope, designOf, importsOf, designTarget, masked, CODE_EXT } from "./_design.mjs";
|
|
9
|
+
|
|
10
|
+
const RULE_ID = "coder.bun.design-orphan-export";
|
|
11
|
+
const files = scope(readRoots(), readExcludes());
|
|
12
|
+
const consumed = new Set();
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
if (basename(file).startsWith("index.")) continue;
|
|
15
|
+
for (const imp of importsOf(readText(file) ?? "")) {
|
|
16
|
+
if (!designTarget(file, imp.specifier)) continue;
|
|
17
|
+
const named = imp.clause.match(/\{([^}]*)\}/)?.[1] ?? "";
|
|
18
|
+
for (const n of named.split(",")) { const name = n.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0]; if (name) consumed.add(name); }
|
|
19
|
+
const fallback = imp.clause.replace(/\{[^}]*\}/, "").replace(/\*\s+as\s+\w+/, "").split(",")[0].trim();
|
|
20
|
+
if (/^[A-Z]\w*$/.test(fallback)) consumed.add(fallback);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const violations = [];
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
const here = designOf(file);
|
|
26
|
+
if (!here || !(here.layer >= 1) || !CODE_EXT.has(extname(file)) || basename(file).startsWith("index.")) continue;
|
|
27
|
+
const m = masked(file);
|
|
28
|
+
for (const e of (m?.code ?? "").matchAll(/export\s+(?:default\s+)?(?:async\s+)?(?:function|const|let|class)\s+([A-Z]\w*)/g)) {
|
|
29
|
+
if (!consumed.has(e[1])) violations.push({ rule_id: RULE_ID, file, ...locate(m.text, e.index), evidence: `${here.layerName} export ${e[1]} is imported by no other file` });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
process.stderr.write(`bun-detector[design-orphan-export]: ${violations.length} violation(s)\n`);
|
|
33
|
+
emit(violations);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Detector: coder.bun.design-primitives
|
|
3
|
+
// App components (outside the design root) render interactive controls through the
|
|
4
|
+
// design-system primitive, not raw <button>/<input>/<select>/<textarea>.
|
|
5
|
+
import { readRoots, readExcludes, readText, emit, locate } from "../../../lib/scan.mjs";
|
|
6
|
+
import { scope, designOf, isComponentSource, masked } from "./_design.mjs";
|
|
7
|
+
|
|
8
|
+
const RULE_ID = "coder.bun.design-primitives";
|
|
9
|
+
const violations = [];
|
|
10
|
+
for (const file of scope(readRoots(), readExcludes())) {
|
|
11
|
+
if (!isComponentSource(file) || designOf(file)) continue;
|
|
12
|
+
const m = masked(file);
|
|
13
|
+
for (const c of (m?.code ?? "").matchAll(/<(button|input|select|textarea)\b/g)) violations.push({ rule_id: RULE_ID, file, ...locate(m.text, c.index), evidence: `raw <${c[1]}> in app code; compose the design-system primitive instead` });
|
|
14
|
+
}
|
|
15
|
+
process.stderr.write(`bun-detector[design-primitives]: ${violations.length} violation(s)\n`);
|
|
16
|
+
emit(violations);
|