@deslop/coding-standards 0.1.1076 → 0.1.1079
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 +1 -1
- package/dist/{MutableRef-KfyWrSvW.mjs → MutableList-BCF17jPH.mjs} +536 -65
- package/dist/install.js +1611 -1752
- package/dist/oxlint.d.ts +12 -18
- package/dist/oxlint.js +631 -226
- package/package.json +1 -1
- package/skills/architecture/SKILL.md +1 -1
- package/skills/design/SKILL.md +4 -4
- package/skills/engineering/SKILL.md +39 -10
- package/skills/testing/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@ Before code, settle these facts in the current plan, without creating another do
|
|
|
19
19
|
- The boundary where untrusted input becomes trusted data.
|
|
20
20
|
- What remains durable after interruption, and how the caller knows.
|
|
21
21
|
|
|
22
|
-
For a user-visible direction that remains open, decide it with the user before implementation; use orchestrator when available. Settle technical mechanisms with source, a disposable inline probe or a small executable prototype. Do not ask the user to choose between implementation details that evidence can rank.
|
|
22
|
+
For a user-visible direction that remains open, decide it with the user before implementation; use orchestrator when available. File layout, module boundaries, public interfaces and names are the user's taste: when the Patterns and Taste sections of `CODING_STANDARDS.md` do not settle them, show the shape before code, per orchestrator's Taste gate when available. Settle technical mechanisms with source, a disposable inline probe or a small executable prototype. Do not ask the user to choose between implementation details that evidence can rank.
|
|
23
23
|
|
|
24
24
|
## Choose the smallest mechanism
|
|
25
25
|
|
package/skills/design/SKILL.md
CHANGED
|
@@ -13,11 +13,11 @@ Follow the engineering skill and the repository's `CODING_STANDARDS.md` when pre
|
|
|
13
13
|
|
|
14
14
|
## Before the first draft
|
|
15
15
|
|
|
16
|
-
For
|
|
16
|
+
For a screen that the recorded taste does not settle, agree on a realistic mock with the user before product code. Use orchestrator's Taste gate when available. Approval is provisional: let the user try a small real journey before expensive expansion. Own routine refinement, but expect the real experience to change the user's direction.
|
|
17
17
|
|
|
18
18
|
1. **Task and feeling.** Name the person, their task, their context and the feeling the product gives, such as calm, fast or trustworthy. Name three qualities the person notices, each with what "excellent" looks like.
|
|
19
19
|
2. **Baseline.** Name the category and platform, and the reference screen for each screen you build: in product mode the canonical screen per engineering's Patterns, otherwise one of Apple's apps on iOS or a reference the user named. Capture the reference in the states you need. Start from the system or established component defaults.
|
|
20
|
-
3. **Range.**
|
|
20
|
+
3. **Range.** When the reference screen and the Taste section of `CODING_STANDARDS.md` do not settle the structure, compare 2–3 structurally different solutions on the same data. Include a simpler solution that removes unnecessary controls. Develop only the chosen direction.
|
|
21
21
|
4. **States.** List every screen, its reachable states (empty, loading, partial, error, success, long content) and the events between them, before any code. Group states by concern instead of multiplying every combination.
|
|
22
22
|
5. **Settled choices.** Carry forward what the user approved and what he removed. Never re-add a removed element, rename a visible term or change an approved detail without a request.
|
|
23
23
|
|
|
@@ -98,9 +98,9 @@ For an open direction, show a question-sized prototype before polishing it. Use
|
|
|
98
98
|
|
|
99
99
|
## Prototype and tune
|
|
100
100
|
|
|
101
|
-
**
|
|
101
|
+
**Mocks.** Start the mock from the real screen: copy its rendered markup, theme tokens and fonts from the running app or its source, then change only what the decision needs. Keep its components, copy, spacing, realistic data and main states. For a native app, imitate the platform's system look; the mock settles structure, flow and the rough look. Add interaction only when manipulating the subject exposes a consequential difference; never add tabs or a state dashboard merely to navigate the explanation. Revise the mock until the user accepts the direction, then expand states and polish in code.
|
|
102
102
|
|
|
103
|
-
**Real host.**
|
|
103
|
+
**Real host.** After the mock, build one complete representative journey with realistic data in the real host: the browser preview for the web, the simulator for a native app. Settle the final native appearance there. Let the user try it before broader implementation and polish. Complete specified fixes and approved-pattern work with focused proof, without a user checkpoint. Carry unaffected choices forward when feedback changes the journey; revise the affected design instead of defending the approved mock.
|
|
104
104
|
|
|
105
105
|
**Tune.** Settle values yourself. For values you cannot settle by reasoning, such as spacing, timing, springs or blur, capture candidates side by side, including both extremes, and judge them against the reference. Write the chosen values into the source and recapture.
|
|
106
106
|
|
|
@@ -3,7 +3,7 @@ name: engineering
|
|
|
3
3
|
description: 'TypeScript and Effect code conventions paired with shared lint. Use when designing, implementing or reviewing product and test code, including prototypes.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
**Scope.** Apply these rules and the repository's `CODING_STANDARDS.md` when present. Aim for a structurally correct first pass with few lint errors, not a promise of zero diagnostics. Oxlint remains the authoritative check; instructions do not replace it. Oxfmt owns whitespace, indentation and other mechanical formatting. Use automatic formatting and available fixes instead of spending reasoning on those details; fix remaining structural diagnostics at their source without a full rewrite or weakened rules. A refactor or cleanup request applies every rule to every line of the owned files. Scratch code outside the repository follows no style rule. Create a missing standards file only to hold a [Patterns](#patterns) row.
|
|
6
|
+
**Scope.** Apply these rules and the repository's `CODING_STANDARDS.md` when present. Aim for a structurally correct first pass with few lint errors, not a promise of zero diagnostics. Oxlint remains the authoritative check; instructions do not replace it. Oxfmt owns whitespace, indentation and other mechanical formatting. Use automatic formatting and available fixes instead of spending reasoning on those details; fix remaining structural diagnostics at their source without a full rewrite or weakened rules. A refactor or cleanup request applies every rule to every line of the owned files. Scratch code outside the repository follows no style rule. Create a missing standards file only to hold a [Patterns](#patterns) or Taste row.
|
|
7
7
|
|
|
8
8
|
**Before checking.** Run Oxfmt and the repository's deterministic lint autofix before manual code analysis, independent review or a check-only lint pass. Use the owned scope and preserve unrelated changes. Run both even when generated code looks correct; automatic fixes need no model iteration. Analyze the remaining diagnostics and behavior only after normalization. For a read-only review, the author prepares the normalized diff; the reviewer never writes the reviewed checkout.
|
|
9
9
|
|
|
@@ -112,6 +112,8 @@ Keep the repository consistent by default, without a reminder, and change a stan
|
|
|
112
112
|
|
|
113
113
|
**Register.** When a task settles a shape that two or more consumers share and the Patterns section has no row for it, add one: the pattern, its owner and canonical example, a search that lists its consumers, and what enforces it.
|
|
114
114
|
|
|
115
|
+
**Taste.** Read the Taste section of `CODING_STANDARDS.md` before choosing a layout, file organization, interface or name, and follow its rows like Patterns rows. When the user picks a variant, rejects one or corrects a taste choice that no rule covers, add a row in the same change: the area, the choice, the rejected alternative and his reason in his words. A row that recurs across repositories moves into the owning skill.
|
|
116
|
+
|
|
115
117
|
**Pattern change.** A user change to a pattern, style, name or convention changes the repository standard, unless he limits its scope. In the same change, update its owner: the shared component, template, generator, lint rule and Patterns row. List every consumer with the row's search, migrate each one, delete the superseded variant and its exemptions, then run the search for the old form again. Report each consumer kept on purpose with its reason. A migration that crosses another repository or a published contract needs the user's approval first.
|
|
116
118
|
|
|
117
119
|
**Enforce.** When the same correction happens twice, make the mistake impossible at the highest level that works: an owner component, export or type that removes the wrong form, then a lint rule whose message names the replacement, then a test. Keep a prose rule only for a judgment that no check can make. Prove a new check against the real past mistake and an accepted control.
|
|
@@ -134,13 +136,13 @@ const root = options?.root !== undefined ? options.root : '.'
|
|
|
134
136
|
```tsx
|
|
135
137
|
// good
|
|
136
138
|
if (enabled) return
|
|
137
|
-
aria-current={props.selected === true
|
|
139
|
+
aria-current={Boolean.match(props.selected === true, {onFalse: () => undefined, onTrue: () => 'page'})}
|
|
138
140
|
// bad
|
|
139
141
|
if (enabled === true) return // enabled is a boolean
|
|
140
142
|
if (props.selected) return // props.selected can be undefined
|
|
141
143
|
```
|
|
142
144
|
|
|
143
|
-
**Flat.** Use early-return statements in Effect.fn. No
|
|
145
|
+
**Flat.** Use early-return statements in Effect.fn. No ternaries, in JSX too, and no combinators deeper than equivalent statements. An early return, `Boolean.match`, `Option.match`, `Array.match`, `Match` or a lookup record selects a value.
|
|
144
146
|
|
|
145
147
|
```ts
|
|
146
148
|
// good
|
|
@@ -153,6 +155,7 @@ Boolean.match(ignoreCase, {onFalse: () => undefined, onTrue: () => 'i'})
|
|
|
153
155
|
// bad
|
|
154
156
|
const type = event.type === 'text-delta' ? 'text' : event.type === 'reasoning-delta' ? 'reasoning' : 'unknown'
|
|
155
157
|
const flags = ignoreCase ? 'i' : undefined // "instead of a ternary the Boolean.match would be cleaner"
|
|
158
|
+
{props.children ? props.children(entry) : <Label entry={entry} />} // "the ternaries are impossible to read"
|
|
156
159
|
// bad
|
|
157
160
|
Array.appendAll(
|
|
158
161
|
base,
|
|
@@ -180,6 +183,17 @@ if (Option.isSome(open)) open.value.append(event.delta)
|
|
|
180
183
|
const parsed = Option.getOrUndefined(Number.parse(event.target.value))
|
|
181
184
|
```
|
|
182
185
|
|
|
186
|
+
**Absence.** `undefined` marks an absent value. Use `null` only where an outside contract, such as a stored schema or a library callback, demands it, with an inline disable that names the contract.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// good
|
|
190
|
+
const last = Option.getOrUndefined(Array.last(sessions))
|
|
191
|
+
// oxlint-disable-next-line no-restricted-properties -- The schema stores a missing link as null.
|
|
192
|
+
url: Option.getOrNull(url)
|
|
193
|
+
// bad
|
|
194
|
+
failed: Option.getOrNull(Option.none()) // "i don't like null, i prefer undefined"
|
|
195
|
+
```
|
|
196
|
+
|
|
183
197
|
**Handlers.** A handler used once is an arrow in place; a handler used twice is a `function`.
|
|
184
198
|
|
|
185
199
|
**Accumulators.** No `let`: a fold or an Effect collection operation that says what it selects.
|
|
@@ -207,6 +221,7 @@ items.filter(item => supports(item)).map(item => item.value)
|
|
|
207
221
|
// good
|
|
208
222
|
Number.parse(input.count)
|
|
209
223
|
Predicate.isString(value)
|
|
224
|
+
Predicate.hasProperty(node, 'tagName')
|
|
210
225
|
Predicate.isNotNullish(annotation)
|
|
211
226
|
Array.isReadonlyArrayEmpty(node.arguments)
|
|
212
227
|
String.isNonEmpty(event.delta)
|
|
@@ -221,6 +236,7 @@ const serve = Effect.gen(function* () {
|
|
|
221
236
|
// bad
|
|
222
237
|
parseInt(input.count)
|
|
223
238
|
typeof value === 'string'
|
|
239
|
+
'tagName' in node // "typeof and in shouldn't be allowed, probably this should use effect schema"
|
|
224
240
|
annotation !== null && annotation !== undefined
|
|
225
241
|
node.arguments.length === 0
|
|
226
242
|
Effect.promise(() => new Promise(resolve => setTimeout(resolve, delay)))
|
|
@@ -304,11 +320,11 @@ files: ['packages/components-web/src/form.tsx', 'packages/components-web/src/ric
|
|
|
304
320
|
|
|
305
321
|
## Effect
|
|
306
322
|
|
|
307
|
-
**Functions.** Use Effect.fn('Name.method') for a traced function with arguments and Effect.fnUntraced for an internal helper. Use Effect.gen for an effect with several statements and no arguments, and a single effect directly. Use Effect.fn.Return for
|
|
323
|
+
**Functions.** Use Effect.fn('Name.method') for a traced function with arguments and Effect.fnUntraced for an internal helper. Use Effect.gen for an effect with several statements and no arguments, and a single effect directly. Use Effect.fn.Return only for a generator that calls itself.
|
|
308
324
|
|
|
309
325
|
```ts
|
|
310
326
|
// good
|
|
311
|
-
const prompt = Effect.fn('Ai.prompt')(function* (message: Prompt.UserMessage)
|
|
327
|
+
const prompt = Effect.fn('Ai.prompt')(function* (message: Prompt.UserMessage) {
|
|
312
328
|
const input = yield* piUserMessage(message)
|
|
313
329
|
yield* send(input)
|
|
314
330
|
})
|
|
@@ -403,15 +419,17 @@ const traced = Effect.withSpan('Notes.create')(notes.create(input))
|
|
|
403
419
|
Effect.addFinalizer(() => closeSocket(socket)) // acquisition and release with different owners
|
|
404
420
|
```
|
|
405
421
|
|
|
406
|
-
**Runtime.** The
|
|
422
|
+
**Runtime.** The entry point runs the program with `NodeRuntime.runMain`. Bridge to a Promise once, where an external contract demands it, with `Effect.runPromise` and an inline disable that names the contract. React is no such contract: atoms run its Effects.
|
|
407
423
|
|
|
408
424
|
```ts
|
|
409
425
|
// good
|
|
410
426
|
NodeRuntime.runMain(program)
|
|
411
|
-
|
|
427
|
+
// oxlint-disable-next-line no-restricted-properties -- Bingo's produce contract demands a Promise.
|
|
428
|
+
produce: flow(generate, Effect.runPromise),
|
|
412
429
|
// bad
|
|
413
430
|
const runtime = ManagedRuntime.make(AppLayer)
|
|
414
431
|
const run = <A, E>(effect: Effect.Effect<A, E>) => Effect.runPromise(effect)
|
|
432
|
+
Effect.runPromiseWith(Context.empty()) // "what's the point of using the runPromiseWith if i pass an empty context?"
|
|
415
433
|
```
|
|
416
434
|
|
|
417
435
|
**Sorting.** `Order` for sorting, `Result` for a fallible pure value.
|
|
@@ -429,7 +447,7 @@ entries.sort((left, right) => left.tokens - right.tokens)
|
|
|
429
447
|
|
|
430
448
|
## Types and Schema
|
|
431
449
|
|
|
432
|
-
**Inference.** Infer types. Annotate
|
|
450
|
+
**Inference.** Infer types. Annotate a return type only on a type guard, an assertion function, a recursive function or the thunk of a recursive `Schema.suspend`. Check a returned object with `satisfies`, and type an empty or one-element array with `Array.empty<T>()` or `Array.of<T>(...)` instead of its callback. Use schema types and indexed members in signatures. Keep intentional widening, not repeated inferred generics.
|
|
433
451
|
|
|
434
452
|
```ts
|
|
435
453
|
// good
|
|
@@ -442,8 +460,14 @@ function visit(node: Node): Result {
|
|
|
442
460
|
function settle(amount: LedgerEntry['amount']) {
|
|
443
461
|
return BigDecimal.format(amount)
|
|
444
462
|
}
|
|
463
|
+
function still(bytes: Uint8Array) {
|
|
464
|
+
return {bytes, type: 'image/jpeg'} satisfies Image
|
|
465
|
+
}
|
|
445
466
|
export type Sandbox = ReturnType<typeof fromSdk>
|
|
446
467
|
// bad
|
|
468
|
+
function still(bytes: Uint8Array): Image {
|
|
469
|
+
return {bytes, type: 'image/jpeg'}
|
|
470
|
+
} // "not a fan of assigning the return type, i would have preferred a satisfies"
|
|
447
471
|
const Input = Schema.Struct({value: Schema.String}) satisfies Schema.Schema<Input>
|
|
448
472
|
const label = MutableRef.make<string>('') // repeats the inferred type
|
|
449
473
|
export type SandboxLike = {id: string; renew: (seconds: number) => Promise<unknown>} // a copy of an inferred shape
|
|
@@ -511,7 +535,7 @@ function isPathNotFound(error: unknown) {
|
|
|
511
535
|
|
|
512
536
|
## Shape
|
|
513
537
|
|
|
514
|
-
**Inline.** Inline forwarding wrappers, temporaries and
|
|
538
|
+
**Inline.** Inline forwarding wrappers, temporaries, constants and property aliases; keep an alias only where a callback needs its narrowed type. Inline a small helper with one caller. A helper that several files share lives in the package's `lib/utils`. Argument-free functions are named values.
|
|
515
539
|
|
|
516
540
|
```ts
|
|
517
541
|
// good
|
|
@@ -528,6 +552,10 @@ function isBackendRequest(request: IncomingMessage) {
|
|
|
528
552
|
} // "this is just a wrapper / factory function"
|
|
529
553
|
Ref.set(entries, Array.copy(decoded)) // decoded is already an array
|
|
530
554
|
const runPromise = Effect.runPromiseWith(Context.empty()) // "wrapper function, tmp variable ... that can be inlined"
|
|
555
|
+
const parent = read.parent // "inline the property access since it add no value"
|
|
556
|
+
function isVideo(type: FileType) {
|
|
557
|
+
return String.startsWith('video/')(type)
|
|
558
|
+
} // String.startsWith('video/') is already the function
|
|
531
559
|
```
|
|
532
560
|
|
|
533
561
|
**Linked state.** Linked state moves in one pure updater, never a setter that calls another.
|
|
@@ -563,7 +591,7 @@ const warned = ['sort-keys', 'typescript/no-restricted-types'] // a package rule
|
|
|
563
591
|
|
|
564
592
|
## React
|
|
565
593
|
|
|
566
|
-
**Components.** React Compiler memoizes. Destructure state at its declaration, receive component refs through props, and keep only a component's own interaction state in React.
|
|
594
|
+
**Components.** React Compiler memoizes. Import hooks by name from `react`. Destructure state at its declaration, receive component refs through props, and keep only a component's own interaction state in React.
|
|
567
595
|
|
|
568
596
|
```tsx
|
|
569
597
|
// good
|
|
@@ -573,6 +601,7 @@ const moveRpc = useAtomSet(RpcClient.mutation('portfolio.move'))
|
|
|
573
601
|
// bad
|
|
574
602
|
const handle = useCallback(() => setOpen(true), [])
|
|
575
603
|
const state = useState(false)
|
|
604
|
+
const [open, setOpen] = React.useState(false) // "why are you using the React.useState and not importing useState"
|
|
576
605
|
```
|
|
577
606
|
|
|
578
607
|
**Atoms.** Data, shared state and every Effect a component needs live in atoms; a component reads them with hooks and never runs an Effect. A task that changes a React Query hook, a fetching Effect or an Effect runner in React code moves that code to an atom. Declare atoms at module level or in `Atom.family`, never during render. Import `Atom` and `AsyncResult` from `effect/reactivity` and the hooks from `@effect/atom-react`. Supply services through `Atom.runtime(layer)`, and build every read and write with that runtime's `atom` and `fn`. Tie each write to the reads it changes with reactivity keys; only a runtime's `fn` honors them, so never pass them to `Atom.fn`.
|
package/skills/testing/SKILL.md
CHANGED
|
@@ -42,7 +42,7 @@ assert.strictEqual(Array.flatMap(attioProvider.groups, group => group.actions).l
|
|
|
42
42
|
- **Branches.** Exercise the reachable distinctions: empty, duplicate, concurrent and dependency failure. Write no Cartesian product, and no random or repeated happy paths without a hypothesis.
|
|
43
43
|
- **Failure.** Use `Effect.flip` for an expected failure and `Effect.exit` for both outcomes. Assert domain identity and preserved state and resources: no partial write, lost value, extra retry or leak. Wording or rejection alone is not enough, and incidental order is no assertion.
|
|
44
44
|
- **Control.** Run the regression test against the unfixed implementation, and confirm that the intended assertion fails, not setup, imports or compilation. Before a refactor, cover the old behavior.
|
|
45
|
-
- **Audit.** Remove duplicate and tautological cases in the touched scope, but retain independent protocol, package, validation, authorization and persistence contracts. Mock call counts do not prove an external boundary. Do not replace a useful test with a disposable script just to reduce the count.
|
|
45
|
+
- **Audit.** Delete a case that no plausible wrong implementation fails, such as an assertion on a constant placeholder. Remove duplicate and tautological cases in the touched scope, but retain independent protocol, package, validation, authorization and persistence contracts. Mock call counts do not prove an external boundary. Do not replace a useful test with a disposable script just to reduce the count.
|
|
46
46
|
|
|
47
47
|
## Effect and fixtures
|
|
48
48
|
|