@canonical/code-standards 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.
@@ -15,18 +15,20 @@ cs:ApplicationPackage a cs:CodeStandard ;
15
15
  cs:name "packaging/application/structure" ;
16
16
  cs:hasCategory cs:PackagingCategory ;
17
17
  cs:description "Application packages (CLIs, servers, MCP tools, build scripts, workers) execute behavior through entry points. They are not imported as dependencies by other packages. Their internal code must be organized around execution domains (commands, routes, tools, operations) rather than library conventions like `src/lib/` or barrel re-exports. Each domain folder must have its own barrel file (`index.ts`) that defines the domain's internal API — sibling domains import through the barrel, never from individual files." ;
18
- cs:dos """
19
- (Do) Recognise an application package by its `package.json` signals — `bin` field, `private: true`, no `main`/`exports`:
20
- ```json
18
+ cs:do [
19
+ cs:description "Recognise an application package by its `package.json` signals — `bin` field, `private: true`, no `main`/`exports`" ;
20
+ cs:language "json" ;
21
+ cs:code """
21
22
  {
22
23
  "name": "@scope/my-cli",
23
24
  "private": true,
24
25
  "bin": { "my-cli": "./dist/index.js" }
25
26
  }
26
- ```
27
-
28
- (Do) Organize code around execution domains. Each domain folder gets its own barrel (`index.ts`) that exposes the domain's internal API to sibling domains:
29
- ```
27
+ """
28
+ ] ;
29
+ cs:do [
30
+ cs:description "Organize code around execution domains. Each domain folder gets its own barrel (`index.ts`) that exposes the domain's internal API to sibling domains" ;
31
+ cs:code """
30
32
  packages/my-cli/
31
33
  ├── src/
32
34
  │ ├── commands/ # CLI command handlers
@@ -42,17 +44,20 @@ packages/my-cli/
42
44
  │ │ └── index.ts # Barrel: exports utilities
43
45
  │ └── index.ts # Entry point (bootstraps CLI)
44
46
  └── package.json
45
- ```
46
-
47
- (Do) Import from sibling domains through their barrel, not from individual files:
48
- ```typescript
47
+ """
48
+ ] ;
49
+ cs:do [
50
+ cs:description "Import from sibling domains through their barrel, not from individual files" ;
51
+ cs:language "typescript" ;
52
+ cs:code """
49
53
  // src/commands/deploy.ts
50
54
  import { resolveConfig, runValidation } from "../operations/index.js";
51
55
  import { formatOutput } from "../utils/index.js";
52
- ```
53
-
54
- (Do) Use domain-appropriate folder names that reflect what the code does — `commands/`, `tools/`, `routes/`, `operations/`, `handlers/`:
55
- ```
56
+ """
57
+ ] ;
58
+ cs:do [
59
+ cs:description "Use domain-appropriate folder names that reflect what the code does — `commands/`, `tools/`, `routes/`, `operations/`, `handlers/`" ;
60
+ cs:code """
56
61
  packages/my-mcp/
57
62
  ├── src/
58
63
  │ ├── tools/ # MCP tool handlers
@@ -64,10 +69,11 @@ packages/my-mcp/
64
69
  │ │ └── index.ts # Barrel: exports shared operations
65
70
  │ └── index.ts # MCP server entry point
66
71
  └── package.json
67
- ```
68
-
69
- (Do) For **hybrid** packages (primarily application but exporting a small reusable surface), isolate the exported surface in `src/lib/` following library rules while keeping the rest as application structure:
70
- ```
72
+ """
73
+ ] ;
74
+ cs:do [
75
+ cs:description "For **hybrid** packages (primarily application but exporting a small reusable surface), isolate the exported surface in `src/lib/` following library rules while keeping the rest as application structure" ;
76
+ cs:code """
71
77
  packages/my-mcp/
72
78
  ├── src/
73
79
  │ ├── lib/ # Only the reusable exported surface
@@ -82,11 +88,11 @@ packages/my-mcp/
82
88
  │ │ └── index.ts # Barrel: exports shared operations
83
89
  │ └── index.ts # MCP server entry point
84
90
  └── package.json
85
- ```
86
- """ ;
87
- cs:donts """
88
- (Don't) Apply library structure (`src/lib/`, barrel re-exports) to application packages:
89
- ```
91
+ """
92
+ ] ;
93
+ cs:dont [
94
+ cs:description "Apply library structure (`src/lib/`, barrel re-exports) to application packages" ;
95
+ cs:code """
90
96
  // Bad: CLI tool forced into library layout
91
97
  packages/my-cli/
92
98
  ├── src/
@@ -94,10 +100,11 @@ packages/my-cli/
94
100
  │ │ ├── commands/
95
101
  │ │ └── index.ts # Barrel re-export — nobody imports this
96
102
  │ └── index.ts
97
- ```
98
-
99
- (Don't) Create a `src/lib/` folder for internal shared operations. Use domain-appropriate names instead:
100
- ```
103
+ """
104
+ ] ;
105
+ cs:dont [
106
+ cs:description "Create a `src/lib/` folder for internal shared operations. Use domain-appropriate names instead" ;
107
+ cs:code """
101
108
  // Bad: "lib" implies external consumption
102
109
  packages/my-cli/
103
110
  ├── src/
@@ -109,10 +116,12 @@ packages/my-cli/
109
116
  ├── src/
110
117
  │ ├── operations/
111
118
  │ │ └── resolveConfig.ts # Internal shared logic
112
- ```
113
-
114
- (Don't) Import directly from files inside a sibling domain — always go through the domain barrel:
115
- ```typescript
119
+ """
120
+ ] ;
121
+ cs:dont [
122
+ cs:description "Import directly from files inside a sibling domain — always go through the domain barrel" ;
123
+ cs:language "typescript" ;
124
+ cs:code """
116
125
  // Bad: reaching into a sibling domain's internals
117
126
  import { resolveConfig } from "../operations/resolveConfig.js";
118
127
  import { formatOutput } from "../utils/formatOutput.js";
@@ -120,10 +129,11 @@ import { formatOutput } from "../utils/formatOutput.js";
120
129
  // Good: import through the domain barrel
121
130
  import { resolveConfig } from "../operations/index.js";
122
131
  import { formatOutput } from "../utils/index.js";
123
- ```
124
-
125
- (Don't) Omit barrels from domain folders:
126
- ```
132
+ """
133
+ ] ;
134
+ cs:dont [
135
+ cs:description "Omit barrels from domain folders" ;
136
+ cs:code """
127
137
  // Bad: no barrels — every consumer imports individual files
128
138
  packages/my-cli/
129
139
  ├── src/
@@ -133,36 +143,39 @@ packages/my-cli/
133
143
  │ ├── operations/
134
144
  │ │ ├── resolveConfig.ts
135
145
  │ │ └── runValidation.ts # No index.ts — fragile cross-domain imports
136
- ```
137
-
138
- (Don't) Default to library structure just because the package lives in a monorepo:
139
- ```
146
+ """
147
+ ] ;
148
+ cs:dont [
149
+ cs:description "Default to library structure just because the package lives in a monorepo" ;
150
+ cs:code """
140
151
  // Bad: reflexively adding lib/ to a build script package
141
152
  packages/build-tools/
142
153
  ├── src/
143
154
  │ ├── lib/ # This is a build script, not a library
144
155
  │ │ └── runBuild.ts
145
156
  │ └── index.ts
146
- ```
147
- """ .
157
+ """
158
+ ] .
148
159
 
149
160
  # Library Package Structure
150
161
  cs:LibraryPackage a cs:CodeStandard ;
151
162
  cs:name "packaging/library/structure" ;
152
163
  cs:hasCategory cs:PackagingCategory ;
153
164
  cs:description "Library packages export reusable code (components, utilities, hooks, types) for consumption by other packages. They must organize their exportable code in a `src/lib/` folder and re-export through a root barrel. This convention ensures consistency across the monorepo and clearly separates public API code from non-exported concerns like storybook configuration or test utilities. Each domain folder within `lib/` must have its own barrel (`index.ts`)." ;
154
- cs:dos """
155
- (Do) Recognise a library package by its `package.json` signals — `main`/`exports` field, publishable, no `bin`:
156
- ```json
165
+ cs:do [
166
+ cs:description "Recognise a library package by its `package.json` signals — `main`/`exports` field, publishable, no `bin`" ;
167
+ cs:language "json" ;
168
+ cs:code """
157
169
  {
158
170
  "name": "@scope/design-system",
159
171
  "main": "./dist/index.js",
160
172
  "exports": { ".": "./dist/index.js" }
161
173
  }
162
- ```
163
-
164
- (Do) Place all reusable/exportable code in `src/lib/`, with a barrel in each domain folder:
165
- ```
174
+ """
175
+ ] ;
176
+ cs:do [
177
+ cs:description "Place all reusable/exportable code in `src/lib/`, with a barrel in each domain folder" ;
178
+ cs:code """
166
179
  packages/my-package/
167
180
  ├── src/
168
181
  │ ├── lib/
@@ -180,54 +193,61 @@ packages/my-package/
180
193
  │ ├── storybook/ # Storybook-specific files (not exported)
181
194
  │ └── index.ts # Re-exports from lib
182
195
  └── package.json
183
- ```
184
-
185
- (Do) Re-export from the lib folder in the package entry point:
186
- ```typescript
196
+ """
197
+ ] ;
198
+ cs:do [
199
+ cs:description "Re-export from the lib folder in the package entry point" ;
200
+ cs:language "typescript" ;
201
+ cs:code """
187
202
  // src/index.ts
188
203
  export * from "./lib/index.js";
189
- ```
190
-
191
- (Do) Use the lib barrel to compose the package's public API from domain barrels:
192
- ```typescript
204
+ """
205
+ ] ;
206
+ cs:do [
207
+ cs:description "Use the lib barrel to compose the package's public API from domain barrels" ;
208
+ cs:language "typescript" ;
209
+ cs:code """
193
210
  // src/lib/index.ts
194
211
  export * from "./Button/index.js";
195
212
  export * from "./hooks/index.js";
196
213
  export type * from "./types/index.js";
197
- ```
198
- """ ;
199
- cs:donts """
200
- (Don't) Use alternative folder names like `ui`, `components`, or `utils` for exportable code at the package level:
201
- ```
214
+ """
215
+ ] ;
216
+ cs:dont [
217
+ cs:description "Use alternative folder names like `ui`, `components`, or `utils` for exportable code at the package level" ;
218
+ cs:code """
202
219
  // Bad: Using 'ui' instead of 'lib'
203
220
  packages/my-package/
204
221
  ├── src/
205
222
  │ ├── ui/ # Wrong: should be 'lib'
206
223
  │ │ └── Button/
207
224
  │ └── index.ts
208
- ```
209
-
210
- (Don't) Mix exportable and non-exportable code at the same level:
211
- ```
225
+ """
226
+ ] ;
227
+ cs:dont [
228
+ cs:description "Mix exportable and non-exportable code at the same level" ;
229
+ cs:code """
212
230
  // Bad: Mixing concerns
213
231
  packages/my-package/
214
232
  ├── src/
215
233
  │ ├── Button/ # Component mixed with...
216
234
  │ ├── storybook/ # ...non-exportable storybook config
217
235
  │ └── test-utils/ # ...and test utilities
218
- ```
219
-
220
- (Don't) Create deeply nested lib-like structures; keep lib at the top level of src:
221
- ```
236
+ """
237
+ ] ;
238
+ cs:dont [
239
+ cs:description "Create deeply nested lib-like structures; keep lib at the top level of src" ;
240
+ cs:code """
222
241
  // Bad: Nested lib folders
223
242
  packages/my-package/
224
243
  ├── src/
225
244
  │ ├── features/
226
245
  │ │ └── lib/ # Wrong: lib should be at src level
227
- ```
228
-
229
- (Don't) Omit barrels from domain folders within lib:
230
- ```
246
+ """
247
+ ] ;
248
+ cs:dont [
249
+ cs:description "Omit barrels from domain folders within lib" ;
250
+ cs:code """
231
251
  // Bad: no barrels — consumers must know individual file paths
232
252
  packages/my-package/
233
253
  ├── src/
@@ -238,23 +258,26 @@ packages/my-package/
238
258
  │ │ ├── hooks/
239
259
  │ │ │ └── useToggle.ts # No index.ts
240
260
  │ │ └── index.ts
241
- ```
242
- """ .
261
+ """
262
+ ] .
243
263
 
244
264
  # Export Shape Standard
245
265
  cs:ExportShape a cs:CodeStandard ;
246
266
  cs:name "packaging/export/shape" ;
247
267
  cs:hasCategory cs:PackagingCategory ;
248
268
  cs:description "Files must use either a single default export or multiple named exports. When using multiple named exports, all exports must have the same type or shape." ;
249
- cs:dos """
250
- (Do) Use a single default export for files implementing a single component or function:
251
- ```typescript
269
+ cs:do [
270
+ cs:description "Use a single default export for files implementing a single component or function" ;
271
+ cs:language "typescript" ;
272
+ cs:code """
252
273
  // ComponentName.tsx
253
274
  export default ComponentName;
254
- ```
255
-
256
- (Do) Name atomic function files after the function they export:
257
- ```typescript
275
+ """
276
+ ] ;
277
+ cs:do [
278
+ cs:description "Name atomic function files after the function they export" ;
279
+ cs:language "typescript" ;
280
+ cs:code """
258
281
  // assignElement.ts
259
282
  export default function assignElement(target: Element, source: Partial<Element>) {
260
283
  // ...
@@ -264,10 +287,12 @@ export default function assignElement(target: Element, source: Partial<Element>)
264
287
  export default function formatCurrency(value: number) {
265
288
  // ...
266
289
  }
267
- ```
268
-
269
- (Do) Use multiple named exports for files providing a public API or a collection of related items, and ensure all exports have the same type:
270
- ```typescript
290
+ """
291
+ ] ;
292
+ cs:do [
293
+ cs:description "Use multiple named exports for files providing a public API or a collection of related items, and ensure all exports have the same type" ;
294
+ cs:language "typescript" ;
295
+ cs:code """
271
296
  // index.ts
272
297
  export { ComponentA, ComponentB };
273
298
 
@@ -279,17 +304,20 @@ export type TypeB = { ... };
279
304
  export const myFuncA = (value: string) => {};
280
305
  export const myFuncB = (value: string) => {};
281
306
  export const myFuncC = (value: string) => {};
282
- ```
283
- """ ;
284
- cs:donts """
285
- (Don't) Mix default and unrelated named exports in a way that confuses the file's purpose:
286
- ```typescript
307
+ """
308
+ ] ;
309
+ cs:dont [
310
+ cs:description "Mix default and unrelated named exports in a way that confuses the file's purpose" ;
311
+ cs:language "typescript" ;
312
+ cs:code """
287
313
  export default ComponentName;
288
314
  export const helper = () => {};
289
- ```
290
-
291
- (Don't) Name an atomic function file with a name that doesn't match its exported function:
292
- ```typescript
315
+ """
316
+ ] ;
317
+ cs:dont [
318
+ cs:description "Name an atomic function file with a name that doesn't match its exported function" ;
319
+ cs:language "typescript" ;
320
+ cs:code """
293
321
  // helpers.ts — wrong: generic name instead of function name
294
322
  export default function assignElement(target: Element, source: Partial<Element>) {
295
323
  // ...
@@ -299,104 +327,209 @@ export default function assignElement(target: Element, source: Partial<Element>)
299
327
  export default function formatCurrency(value: number) {
300
328
  // ...
301
329
  }
302
- ```
303
-
304
- (Don't) Provide multiple unrelated exports from a file meant for a single domain:
305
- ```typescript
330
+ """
331
+ ] ;
332
+ cs:dont [
333
+ cs:description "Provide multiple unrelated exports from a file meant for a single domain" ;
334
+ cs:language "typescript" ;
335
+ cs:code """
306
336
  export default debounce;
307
337
  export const throttle = () => {};
308
338
  export const logger = () => {};
309
- ```
310
-
311
- (Don't) Export objects of different types or shapes from the same file:
312
- ```typescript
339
+ """
340
+ ] ;
341
+ cs:dont [
342
+ cs:description "Export objects of different types or shapes from the same file" ;
343
+ cs:language "typescript" ;
344
+ cs:code """
313
345
  export const transformer = (value: string) => {};
314
346
  export const reducer = (map: string[]) => {};
315
347
  class ABC {}
316
348
  export { ABC };
317
- ```
318
- """ .
349
+ """
350
+ ] .
319
351
 
320
352
  # Barrel File Standard
321
353
  cs:BarrelPublicApi a cs:CodeStandard ;
322
354
  cs:name "packaging/export/barrel" ;
323
355
  cs:hasCategory cs:PackagingCategory ;
324
356
  cs:description "Every domain folder must have a barrel file (`index.ts`) that defines its API surface. In library packages, barrels define the public API for external consumers. In application packages, barrels define the internal API between sibling domains. Within a domain, implementation files import from siblings directly; cross-domain imports always go through the barrel." ;
325
- cs:dos """
326
- (Do) Use a barrel at the package entry point to define the public API (library packages):
327
- ```typescript
357
+ cs:do [
358
+ cs:description "Use a barrel at the package entry point to define the public API (library packages)" ;
359
+ cs:language "typescript" ;
360
+ cs:code """
328
361
  // src/index.ts
329
362
  export { Button } from "./components/Button.js";
330
363
  export type { ButtonProps } from "./components/Button.types.js";
331
- ```
332
-
333
- (Do) Use a barrel at each domain folder to define the domain's API (application packages):
334
- ```typescript
364
+ """
365
+ ] ;
366
+ cs:do [
367
+ cs:description "Use a barrel at each domain folder to define the domain's API (application packages)" ;
368
+ cs:language "typescript" ;
369
+ cs:code """
335
370
  // src/operations/index.ts
336
371
  export { resolveConfig } from "./resolveConfig.js";
337
372
  export { runValidation } from "./runValidation.js";
338
- ```
339
-
340
- (Do) Import internal implementation code within a domain from the owning file directly:
341
- ```typescript
373
+ """
374
+ ] ;
375
+ cs:do [
376
+ cs:description "Import internal implementation code within a domain from the owning file directly" ;
377
+ cs:language "typescript" ;
378
+ cs:code """
342
379
  // src/build/buildTheme.ts — same domain, import the file
343
380
  import { computeDeltas } from "./computeDeltas.js";
344
381
  import { recoverPrimitiveRef } from "./recoverPrimitiveRef.js";
345
- ```
346
-
347
- (Do) Import cross-domain code through the sibling domain's barrel:
348
- ```typescript
382
+ """
383
+ ] ;
384
+ cs:do [
385
+ cs:description "Import cross-domain code through the sibling domain's barrel" ;
386
+ cs:language "typescript" ;
387
+ cs:code """
349
388
  // src/commands/deploy.ts — different domain, import through barrel
350
389
  import { resolveConfig } from "../operations/index.js";
351
- ```
352
-
353
- (Do) Keep compatibility barrels thin and documented as public facades:
354
- ```typescript
390
+ """
391
+ ] ;
392
+ cs:do [
393
+ cs:description "Keep compatibility barrels thin and documented as public facades" ;
394
+ cs:language "typescript" ;
395
+ cs:code """
355
396
  /** Public compatibility surface. */
356
397
  export { computeDeltas } from "./computeDeltas.js";
357
398
  export { formatDelta } from "./formatDelta.js";
358
- ```
359
- """ ;
360
- cs:donts """
361
- (Don't) Route internal same-domain code through its own barrel:
362
- ```typescript
399
+ """
400
+ ] ;
401
+ cs:dont [
402
+ cs:description "Route internal same-domain code through its own barrel" ;
403
+ cs:language "typescript" ;
404
+ cs:code """
363
405
  // Bad: internal implementation depends on its own barrel
364
406
  // src/build/applyTheme.ts
365
407
  import { computeDeltas, recoverPrimitiveRef } from "./index.js";
366
- ```
367
-
368
- (Don't) Import cross-domain code by reaching into individual files:
369
- ```typescript
408
+ """
409
+ ] ;
410
+ cs:dont [
411
+ cs:description "Import cross-domain code by reaching into individual files" ;
412
+ cs:language "typescript" ;
413
+ cs:code """
370
414
  // Bad: bypassing the domain barrel
371
415
  import { resolveConfig } from "../operations/resolveConfig.js";
372
416
 
373
417
  // Good: through the barrel
374
418
  import { resolveConfig } from "../operations/index.js";
375
- ```
376
-
377
- (Don't) Hide domain ownership behind convenience barrels:
378
- ```typescript
419
+ """
420
+ ] ;
421
+ cs:dont [
422
+ cs:description "Hide domain ownership behind convenience barrels" ;
423
+ cs:language "typescript" ;
424
+ cs:code """
379
425
  // Bad: types appear to belong to context.ts instead of their owning domains
380
426
  import type { Artifact, CSSNode, OverlayToken } from "./context.js";
381
- ```
382
-
383
- (Don't) Omit the barrel from a domain folder:
384
- ```
427
+ """
428
+ ] ;
429
+ cs:dont [
430
+ cs:description "Omit the barrel from a domain folder" ;
431
+ cs:code """
385
432
  // Bad: no index.ts — consumers must know internal file names
386
433
  src/operations/
387
434
  ├── resolveConfig.ts
388
435
  └── runValidation.ts
389
- ```
390
- """ .
436
+ """
437
+ ] .
438
+
439
+ # Export Declaration Location
440
+ cs:ExportDeclarationLocation a cs:CodeStandard ;
441
+ cs:name "packaging/export/declaration" ;
442
+ cs:hasCategory cs:PackagingCategory ;
443
+ cs:description "Exports must be declared on the definition itself (e.g. `export default function` or `export const`), not gathered at the end of the file. When a wrapper (HOC, decorator, middleware) is applied, declare the unwrapped binding with `const`, then export the wrapped result as default on a separate line." ;
444
+ cs:do [
445
+ cs:description "Declare the export directly on the function or class definition" ;
446
+ cs:language "typescript" ;
447
+ cs:code """
448
+ // formatCurrency.ts
449
+ export default function formatCurrency(value: number): string {
450
+ return `$${value.toFixed(2)}`;
451
+ }
452
+ """
453
+ ] ;
454
+ cs:do [
455
+ cs:description "Declare the export directly on a named constant" ;
456
+ cs:language "typescript" ;
457
+ cs:code """
458
+ // MAX_RETRIES.ts
459
+ export const MAX_RETRIES = 3;
460
+ """
461
+ ] ;
462
+ cs:do [
463
+ cs:description "When a wrapper is involved, declare the raw binding with `const`, then export the wrapped result as default" ;
464
+ cs:language "typescript" ;
465
+ cs:code """
466
+ // UserProfile.tsx
467
+ const UserProfile = ({ user }: UserProfileProps) => {
468
+ return <div>{user.name}</div>;
469
+ };
470
+
471
+ export default memo(UserProfile);
472
+ """
473
+ ] ;
474
+ cs:do [
475
+ cs:description "Apply the same pattern for higher-order functions and middleware" ;
476
+ cs:language "typescript" ;
477
+ cs:code """
478
+ // connectDatabase.ts
479
+ const connectDatabase = (config: DbConfig): Connection => {
480
+ return new Connection(config);
481
+ };
482
+
483
+ export default withRetry(connectDatabase);
484
+ """
485
+ ] ;
486
+ cs:dont [
487
+ cs:description "Define a symbol and then export it at the end of the file" ;
488
+ cs:language "typescript" ;
489
+ cs:code """
490
+ // Bad: export is separated from definition
491
+ function formatCurrency(value: number): string {
492
+ return `$${value.toFixed(2)}`;
493
+ }
494
+
495
+ export default formatCurrency;
496
+ """
497
+ ] ;
498
+ cs:dont [
499
+ cs:description "Gather multiple exports at the bottom of the file" ;
500
+ cs:language "typescript" ;
501
+ cs:code """
502
+ // Bad: exports collected at the end
503
+ const helperA = () => {};
504
+ const helperB = () => {};
505
+
506
+ export { helperA, helperB };
507
+ """
508
+ ] ;
509
+ cs:dont [
510
+ cs:description "Use `export default` on the raw binding when a wrapper is needed" ;
511
+ cs:language "typescript" ;
512
+ cs:code """
513
+ // Bad: exports the unwrapped version, then wraps elsewhere
514
+ export default function UserProfile({ user }: UserProfileProps) {
515
+ return <div>{user.name}</div>;
516
+ }
517
+
518
+ // consumer.ts — has to wrap it themselves
519
+ import UserProfile from "./UserProfile.js";
520
+ const Memoized = memo(UserProfile);
521
+ """
522
+ ] .
391
523
 
392
524
  # Single Export File Naming Standard
393
525
  cs:SingleExportFileNaming a cs:CodeStandard ;
394
526
  cs:name "packaging/naming/single-export-file" ;
395
527
  cs:hasCategory cs:PackagingCategory ;
396
528
  cs:description "Files that contain a single export must be named after that export. This applies to functions, classes, types, constants, and any other single-export module. The file name must match the exported identifier exactly, preserving its casing (camelCase for functions/variables, PascalCase for classes/types/components)." ;
397
- cs:dos """
398
- (Do) Name files after the single function they export:
399
- ```typescript
529
+ cs:do [
530
+ cs:description "Name files after the single function they export" ;
531
+ cs:language "typescript" ;
532
+ cs:code """
400
533
  // isAccepted.ts
401
534
  export const isAccepted = (status: string): boolean => {
402
535
  return status === "accepted";
@@ -406,10 +539,12 @@ export const isAccepted = (status: string): boolean => {
406
539
  export default function formatCurrency(value: number): string {
407
540
  return `$${value.toFixed(2)}`;
408
541
  }
409
- ```
410
-
411
- (Do) Name files after the single type or interface they export:
412
- ```typescript
542
+ """
543
+ ] ;
544
+ cs:do [
545
+ cs:description "Name files after the single type or interface they export" ;
546
+ cs:language "typescript" ;
547
+ cs:code """
413
548
  // ConnectionConfig.ts
414
549
  export interface ConnectionConfig {
415
550
  host: string;
@@ -418,25 +553,30 @@ export interface ConnectionConfig {
418
553
 
419
554
  // UserRole.ts
420
555
  export type UserRole = "admin" | "editor" | "viewer";
421
- ```
422
-
423
- (Do) Name files after the single class they export:
424
- ```typescript
556
+ """
557
+ ] ;
558
+ cs:do [
559
+ cs:description "Name files after the single class they export" ;
560
+ cs:language "typescript" ;
561
+ cs:code """
425
562
  // EventEmitter.ts
426
563
  export class EventEmitter {
427
564
  // ...
428
565
  }
429
- ```
430
-
431
- (Do) Name files after the single constant they export:
432
- ```typescript
566
+ """
567
+ ] ;
568
+ cs:do [
569
+ cs:description "Name files after the single constant they export" ;
570
+ cs:language "typescript" ;
571
+ cs:code """
433
572
  // DEFAULT_TIMEOUT.ts
434
573
  export const DEFAULT_TIMEOUT = 5000;
435
- ```
436
- """ ;
437
- cs:donts """
438
- (Don't) Use generic names like `helpers.ts`, `utils.ts`, or `types.ts` for files with a single export:
439
- ```typescript
574
+ """
575
+ ] ;
576
+ cs:dont [
577
+ cs:description "Use generic names like `helpers.ts`, `utils.ts`, or `types.ts` for files with a single export" ;
578
+ cs:language "typescript" ;
579
+ cs:code """
440
580
  // helpers.ts — wrong: should be isAccepted.ts
441
581
  export const isAccepted = (status: string): boolean => {
442
582
  return status === "accepted";
@@ -446,10 +586,12 @@ export const isAccepted = (status: string): boolean => {
446
586
  export default function formatCurrency(value: number): string {
447
587
  return `$${value.toFixed(2)}`;
448
588
  }
449
- ```
450
-
451
- (Don't) Use names that describe the domain instead of the export:
452
- ```typescript
589
+ """
590
+ ] ;
591
+ cs:dont [
592
+ cs:description "Use names that describe the domain instead of the export" ;
593
+ cs:language "typescript" ;
594
+ cs:code """
453
595
  // validation.ts — wrong: should be isAccepted.ts
454
596
  export const isAccepted = (status: string): boolean => {
455
597
  return status === "accepted";
@@ -460,5 +602,121 @@ export interface ConnectionConfig {
460
602
  host: string;
461
603
  port: number;
462
604
  }
463
- ```
464
- """ .
605
+ """
606
+ ] .
607
+
608
+ # Intra-Package Cross-Domain Imports
609
+ cs:IntraPackageCrossDomainImports a cs:CodeStandard ;
610
+ cs:name "packaging/import/cross-domain" ;
611
+ cs:hasCategory cs:PackagingCategory ;
612
+ cs:description """Intra-package cross-domain imports must use Node.js subpath imports (the `imports` field in `package.json`) with the `#` prefix convention. Each `#` alias points directly to the domain's barrel file — no wildcard splats, no path suffixes, no extension mapping. With `moduleResolution: "nodenext"` (or `"node16"`), bare specifiers like `error/index.js` are treated as package names, not path-relative imports. The `paths` compiler option is a manual alias table — brittle and verbose. Subpath imports are the official Node.js mechanism, work natively with Node, Bun, vitest, and tsc, and require no plugins. TypeScript resolves them when `resolvePackageJsonImports` is enabled (on by default with `moduleResolution` set to `node16`, `nodenext`, or `bundler`).""" ;
613
+ cs:do [
614
+ cs:description "Map each `#` alias directly to the domain barrel in `package.json` — no wildcards, no path suffixes" ;
615
+ cs:language "json" ;
616
+ cs:code """
617
+ {
618
+ "imports": {
619
+ "#config": "./src/config/index.ts",
620
+ "#error": "./src/error/index.ts",
621
+ "#pipeline": "./src/pipeline/index.ts",
622
+ "#package-manager": "./src/package-manager/index.ts",
623
+ "#constants": "./src/constants.ts"
624
+ }
625
+ }
626
+ """
627
+ ] ;
628
+ cs:do [
629
+ cs:description "Import cross-domain modules using the `#` alias — clean, no path suffixes" ;
630
+ cs:language "typescript" ;
631
+ cs:code """
632
+ // src/pipeline/runPipeline.ts
633
+ import { PragmaError } from "#error";
634
+ import { resolveConfig } from "#config";
635
+ """
636
+ ] ;
637
+ cs:do [
638
+ cs:description """Use `moduleResolution: "nodenext"` (or `"node16"` / `"bundler"`) in `tsconfig.json` so TypeScript resolves subpath imports via `resolvePackageJsonImports` (enabled by default with these settings)""" ;
639
+ cs:language "json" ;
640
+ cs:code """
641
+ {
642
+ "compilerOptions": {
643
+ "moduleResolution": "nodenext"
644
+ }
645
+ }
646
+ """
647
+ ] ;
648
+ cs:do [
649
+ cs:description "Use conditional subpath imports when you need different resolutions for different environments" ;
650
+ cs:language "json" ;
651
+ cs:code """
652
+ {
653
+ "imports": {
654
+ "#db": {
655
+ "development": "./src/db/mock.ts",
656
+ "default": "./src/db/real.ts"
657
+ }
658
+ }
659
+ }
660
+ """
661
+ ] ;
662
+ cs:dont [
663
+ cs:description "Use wildcard splats or path suffixes in `#` aliases — point directly to the barrel instead" ;
664
+ cs:language "json" ;
665
+ cs:code """
666
+ // Bad: wildcard mapping forces consumers to know internal file structure
667
+ {
668
+ "imports": {
669
+ "#config/*": "./src/config/*",
670
+ "#error/*": "./src/error/*"
671
+ }
672
+ }
673
+
674
+ // Bad: path suffix leaks barrel structure into every import site
675
+ import { PragmaError } from "#error/index.js";
676
+ """
677
+ ] ;
678
+ cs:dont [
679
+ cs:description "Use bare specifiers without the `#` prefix for intra-package imports — they resolve as package names under `nodenext`" ;
680
+ cs:language "typescript" ;
681
+ cs:code """
682
+ // Bad: "error/index.js" resolves as the npm package "error", not ./src/error/
683
+ import { PragmaError } from "error/index.js";
684
+ """
685
+ ] ;
686
+ cs:dont [
687
+ cs:description "Use `paths` in `tsconfig.json` as a substitute for subpath imports — it's a manual alias table that doesn't participate in Node.js resolution" ;
688
+ cs:language "json" ;
689
+ cs:code """
690
+ // Bad: brittle, verbose, requires a bundler or ts-patch to work at runtime
691
+ {
692
+ "compilerOptions": {
693
+ "baseUrl": ".",
694
+ "paths": {
695
+ "@error/*": ["src/error/*"],
696
+ "@config/*": ["src/config/*"],
697
+ "@pipeline/*": ["src/pipeline/*"]
698
+ }
699
+ }
700
+ }
701
+ """
702
+ ] ;
703
+ cs:dont [
704
+ cs:description "Use deep relative paths to reach across domains — they break when folders move and obscure the dependency graph" ;
705
+ cs:language "typescript" ;
706
+ cs:code """
707
+ // Bad: fragile, hard to refactor
708
+ import { PragmaError } from "../../error/PragmaError.js";
709
+ import { resolveConfig } from "../../../config/resolveConfig.js";
710
+ """
711
+ ] ;
712
+ cs:dont [
713
+ cs:description "Import specific files through the `#` alias — go through the barrel instead" ;
714
+ cs:language "typescript" ;
715
+ cs:code """
716
+ // Bad: bypasses the barrel, couples to internal file structure
717
+ import { readConfig } from "#config/readConfig.js";
718
+
719
+ // Good: import through the barrel
720
+ import { readConfig } from "#config";
721
+ """
722
+ ] .