@deslop/coding-standards 0.1.912 → 0.1.1014
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 -0
- package/dist/{MutableRef-NhSl8G9M.mjs → MutableRef-CzOzKHjT.mjs} +249 -30
- package/dist/install.js +925 -377
- package/dist/oxlint.js +20 -7
- package/package.json +3 -3
- package/skills/design/SKILL.md +100 -99
- package/skills/engineering/SKILL.md +22 -21
- package/skills/testing/SKILL.md +15 -15
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: engineering
|
|
3
|
-
description: 'TypeScript and Effect code conventions paired with shared lint. Use when designing, implementing or reviewing product
|
|
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
6
|
**Scope.** Apply these rules and repository-specific CODING_STANDARDS.md. Write each line right the first time: the examples exist so lint and review find nothing, and lint is only the fallback.
|
|
7
7
|
|
|
8
|
-
**Dependency APIs.** Match the manifest and lockfile versions. Before adding an API wrapper or workaround, read the upstream source per
|
|
8
|
+
**Dependency APIs.** Match the manifest and lockfile versions. Before adding an API wrapper or workaround, read the upstream source per explore's source checkouts. Library APIs do not define coding policy.
|
|
9
9
|
|
|
10
10
|
**Diagnostics.** Use the shown idiom, not casts, weakened rules or a rewrite trading one diagnostic for another.
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ description: 'TypeScript and Effect code conventions paired with shared lint. Us
|
|
|
13
13
|
|
|
14
14
|
**Clarity.** Plain, explicit, idiomatic code, names and files; read top to bottom without commentary or cleverness. Match the surrounding code's naming, idiom and comment density.
|
|
15
15
|
|
|
16
|
-
**Experiments.** Write with these idioms; reserve the full refactor
|
|
16
|
+
**Experiments.** Write with these idioms; reserve the full refactor and cleanup pass for retained delivery. Keep disposable source isolated and shared rules intact.
|
|
17
17
|
|
|
18
18
|
- **Reuse first.** Check existing code, installed modules and platform capabilities before adding code. Implement only the missing behavior, without dropping required behavior, safety or accessibility. Workflow owns the scope. A refactor or cleanup request applies every rule to every line of the owned files.
|
|
19
19
|
- **Inward layers.** Domain and service code never import HTTP, RPC, or other transport types, and domain schemas carry no transport annotation such as an HTTP status.
|
|
@@ -21,7 +21,7 @@ description: 'TypeScript and Effect code conventions paired with shared lint. Us
|
|
|
21
21
|
- **No machinery.** Add hooks, guards or generators only for a required contract or a recurring defect.
|
|
22
22
|
- **Behavior kept.** A refactor keeps observable behavior at every consumer. Report accepted differences only when they exist: a change no consumer observes, or one a rule here causes, such as an error keeping its cause. Printed diagnostics are not a contract. Any other behavior change follows workflow's scope.
|
|
23
23
|
- **Reachable bugs.** Fix a bug that real input reaches when it lies inside workflow's scope, and check every consumer of the changed output. For a hard bug, use one reproduction and one prediction per probe. Remove edits based on disproven hypotheses, then rerun the original scenario. Delete handling only for input the contract excludes; keep and report code that looks intentional.
|
|
24
|
-
- **Performance.** Optimize only when the request or a required contract needs it. Measure one real action on realistic input before and after the change
|
|
24
|
+
- **Performance.** Optimize only when the request or a required contract needs it. Measure one real action on realistic input before and after the change. Measure at the layer that owns the symptom: spans for backend calls, React commits for rerenders, a browser profile for main-thread work. Report run count and variation; a difference within noise is unproved.
|
|
25
25
|
- **Type safety.** A fix never weakens types to make a symptom go away: no widened or erased type, cast, dropped generic, or loosened exported type.
|
|
26
26
|
- **Domain.** Implement the definition the domain uses, such as a cycle for recursion, never the nearest syntactic proxy.
|
|
27
27
|
|
|
@@ -72,7 +72,7 @@ _input => onSubmit(_input.value) // or input2: an outer input is shadowed
|
|
|
72
72
|
|
|
73
73
|
**Nearest sibling.** Extend the nearest implementation; match permissions, errors, refresh and tests. Reuse its helpers and change only affected state.
|
|
74
74
|
|
|
75
|
-
**Once.** Extract repeated logic once; one fact has one owner. A domain enum, such as a status, has one schema that every other schema reuses
|
|
75
|
+
**Once.** Extract repeated logic once; one fact has one owner. A domain enum, such as a status, has one schema that every other schema reuses. A cache key or atom family key carries the full identity of what it caches.
|
|
76
76
|
|
|
77
77
|
```ts
|
|
78
78
|
// good
|
|
@@ -88,11 +88,11 @@ pipe(
|
|
|
88
88
|
)
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
**Canonical data.** Store
|
|
91
|
+
**Canonical data.** Store and send canonical data, derive display values, expose each state once and remove states no caller produces.
|
|
92
92
|
|
|
93
|
-
**One way.** Replace superseded paths; no duplicate
|
|
93
|
+
**One way.** Replace superseded paths; no duplicate, subset or superset fields, methods, options or exports.
|
|
94
94
|
|
|
95
|
-
**No compatibility.** Rename the shape and every caller together. No alias, fallback, deprecated path or flag unless a documented
|
|
95
|
+
**No compatibility.** Rename the shape and every caller together. No alias, fallback, deprecated path or flag unless a documented or user-named contract requires it.
|
|
96
96
|
|
|
97
97
|
**Present needs.** No speculative option, abstraction, layer, export, file or check. Configure only what callers vary.
|
|
98
98
|
|
|
@@ -122,7 +122,7 @@ if (enabled === true) return // enabled is a boolean
|
|
|
122
122
|
if (props.selected) return // props.selected can be undefined
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
**Flat.** Use early-return statements in Effect.fn. No nested ternaries or combinators deeper than equivalent statements. Match
|
|
125
|
+
**Flat.** Use early-return statements in Effect.fn. No nested ternaries or combinators deeper than equivalent statements. Match and combinators serve single expressions; Boolean.match selects a value, except one-line JSX-attribute ternaries.
|
|
126
126
|
|
|
127
127
|
```ts
|
|
128
128
|
// good
|
|
@@ -185,7 +185,7 @@ const [first] = teamId
|
|
|
185
185
|
items.filter(item => supports(item)).map(item => item.value)
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
**Natives.** Use Effect modules instead of globals
|
|
188
|
+
**Natives.** Use Effect modules instead of globals and native methods. Third-party-owned methods stay, with an inline diagnostic disable and reason when needed.
|
|
189
189
|
|
|
190
190
|
```ts
|
|
191
191
|
// good
|
|
@@ -230,7 +230,7 @@ const text = Effect.fn('Ai.text')(function* (response: AiResponse) {
|
|
|
230
230
|
})
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
**Casts.** No `as`, `any` or erasing a known shape into `unknown`; `satisfies` and `as const` are the only assertions.
|
|
233
|
+
**Casts.** No `as`, `any` or erasing a known shape into `unknown`; `satisfies` and `as const` are the only assertions. Opaque SDK and framework inputs remain `unknown` until narrowed or decoded at their boundary; never fabricate a shape to avoid that type.
|
|
234
234
|
|
|
235
235
|
```ts
|
|
236
236
|
// good
|
|
@@ -247,7 +247,7 @@ const params: unknown = part.params // erases an already-known shape
|
|
|
247
247
|
// good
|
|
248
248
|
const load = Effect.fn('Plugin.load')(function* (path: string) {
|
|
249
249
|
const endpoint = yield* Schema.decodeEffect(Endpoint)(config.endpoint)
|
|
250
|
-
const manifest = yield* Schema.decodeEffect(PackageManifest)(yield* fs.readFileString(path))
|
|
250
|
+
const manifest = yield* Schema.decodeEffect(Schema.fromJsonString(PackageManifest))(yield* fs.readFileString(path))
|
|
251
251
|
return {endpoint, manifest}
|
|
252
252
|
})
|
|
253
253
|
// bad
|
|
@@ -260,12 +260,13 @@ JSON.parse(text)
|
|
|
260
260
|
```ts
|
|
261
261
|
// good
|
|
262
262
|
import {Effect, Schema} from 'effect'
|
|
263
|
-
import type {Plugin} from 'vite'
|
|
264
|
-
|
|
265
|
-
import type {PortfolioState} from '#rpcs/contracts.ts'
|
|
266
263
|
// bad — each line is a diagnostic
|
|
267
264
|
import {Schema as S} from 'effect'
|
|
265
|
+
|
|
266
|
+
import type {Plugin} from 'vite'
|
|
268
267
|
import {type Plugin, createServer} from 'vite'
|
|
268
|
+
|
|
269
|
+
import type {PortfolioState} from '#rpcs/contracts.ts'
|
|
269
270
|
export const EntryKind = Schema.Literals(['income', 'expense']) // no importer
|
|
270
271
|
```
|
|
271
272
|
|
|
@@ -282,7 +283,7 @@ export const EntryKind = Schema.Literals(['income', 'expense']) // no importer
|
|
|
282
283
|
// oxlint-disable-next-line typescript/consistent-type-assertions
|
|
283
284
|
// oxlint-disable-next-line sort-keys -- keeps the printed order
|
|
284
285
|
// bad
|
|
285
|
-
files: ['packages/components/src/
|
|
286
|
+
files: ['packages/components-web/src/form.tsx', 'packages/components-web/src/rich-text-area.tsx'],
|
|
286
287
|
```
|
|
287
288
|
|
|
288
289
|
## Effect
|
|
@@ -367,7 +368,7 @@ static readonly layer = Layer.effect(Ledger, makeLedger) // a make with one call
|
|
|
367
368
|
query(sql: string): Effect.Effect<Rows> // a method signature instead of a readonly property
|
|
368
369
|
```
|
|
369
370
|
|
|
370
|
-
**Primitives.** Use Effect's primitives directly; rpcs and services already trace, and one scope owns each lifetime. Compose established independent
|
|
371
|
+
**Primitives.** Use Effect's primitives directly; rpcs and services already trace, and one scope owns each lifetime. Compose established independent input and output with Effect.all and explicit appropriate concurrency; keep order-dependent, transactional or resource-conflicting work sequential.
|
|
371
372
|
|
|
372
373
|
```ts
|
|
373
374
|
// good
|
|
@@ -412,7 +413,7 @@ entries.sort((left, right) => left.tokens - right.tokens)
|
|
|
412
413
|
|
|
413
414
|
## Types and Schema
|
|
414
415
|
|
|
415
|
-
**Inference.** Infer types. Annotate recursion or branches TypeScript cannot unite; use schema types
|
|
416
|
+
**Inference.** Infer types. Annotate recursion or branches TypeScript cannot unite; use schema types and indexed members in signatures. Keep intentional widening, not repeated inferred generics.
|
|
416
417
|
|
|
417
418
|
```ts
|
|
418
419
|
// good
|
|
@@ -494,7 +495,7 @@ function isPathNotFound(error: unknown) {
|
|
|
494
495
|
|
|
495
496
|
## Shape
|
|
496
497
|
|
|
497
|
-
**Inline.** Inline forwarding wrappers and single-use helpers
|
|
498
|
+
**Inline.** Inline forwarding wrappers and single-use helpers, temporaries and constants. Argument-free functions are named values.
|
|
498
499
|
|
|
499
500
|
```ts
|
|
500
501
|
// good
|
|
@@ -573,7 +574,7 @@ onClick={() => Effect.runPromise(saveApp(draft))}
|
|
|
573
574
|
useAtomValue(Atom.make(loadApp(appId))) // a new atom on every render
|
|
574
575
|
```
|
|
575
576
|
|
|
576
|
-
**Outcomes.** Read data with `useAtomSuspense`. The route or section that owns a screen wraps it in one `Suspense` boundary for loading
|
|
577
|
+
**Outcomes.** Read data with `useAtomSuspense`. The route or section that owns a screen wraps it in one `Suspense` boundary for loading. One error boundary there shows the failure with a retry. Components never branch on loading or failure, and never turn a failure into an empty value. Take a write's pending and failure state from its atom's result, never from copied React state.
|
|
577
578
|
|
|
578
579
|
```tsx
|
|
579
580
|
// good
|
|
@@ -593,7 +594,7 @@ const [pending, setPending] = useState(false) // copies the write atom's waiting
|
|
|
593
594
|
```
|
|
594
595
|
|
|
595
596
|
- **Ownership.** Give a component that owns its own atoms, writes or lifetime its own file; keep small presentational helpers beside their one user. Reset an editable draft through a `key` on its identity, never an Effect that copies props.
|
|
596
|
-
- **Pure render.** Derive display values from props and subscribed state during render; never mutate them or perform an RPC, write or subscription there. Event handlers own user-triggered actions; Effects synchronize external systems, not copied state or calculations. Reuse the existing router
|
|
597
|
+
- **Pure render.** Derive display values from props and subscribed state during render; never mutate them or perform an RPC, write or subscription there. Event handlers own user-triggered actions; Effects synchronize external systems, not copied state or calculations. Reuse the existing router and atom integration rather than fetching in a new Effect.
|
|
597
598
|
- **State identity.** Keep one owner for each value, stable domain keys for list items and deliberate reset boundaries. For an observed rerender problem, derive the needed value in the existing atom graph instead of subscribing to frequently changing unrelated state. Compiler memoization is a performance optimization, never a correctness or lifetime guarantee. Do not add manual memoization, compiler escape directives or weakened hook rules to hide an ownership problem.
|
|
598
599
|
|
|
599
600
|
Testing judgment, fixtures, assertions and verification belong to the testing skill; these engineering rules still apply to test code.
|
package/skills/testing/SKILL.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: testing
|
|
3
|
-
description: 'Select and run black-box behavior proof for Effect apps
|
|
3
|
+
description: 'Select and run black-box behavior proof for Effect apps and tools. Use when choosing, writing, reviewing or running tests; apply engineering to test code and keep exploratory proof question-sized.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Testing
|
|
7
7
|
|
|
8
8
|
**Scope.** Test behavior the changed code owns; a coverage count proves nothing.
|
|
9
9
|
|
|
10
|
-
- **Worth it.** Test owned branching, computation, parsing
|
|
10
|
+
- **Worth it.** Test owned branching, computation, parsing and state transitions that a plausible regression breaks. Add missing valuable coverage before refactoring.
|
|
11
11
|
- **Public interface.** Test exported Layers, services and functions through observable behavior, so a rewrite that keeps the contract keeps the tests green. The browser proves UI components.
|
|
12
12
|
- **Redundant.** Delete touched cases that only repeat dependency guarantees, constants, wording, removed behavior or an already covered input. Keep boundary tests that prove the application's own validation, encoding or integration.
|
|
13
|
-
- **Independent.** Expected values come from the contract
|
|
13
|
+
- **Independent.** Expected values come from the contract or a worked example, not from recomputing the implementation. Use the smallest reachable fixture that tells the cases apart.
|
|
14
14
|
- **Bugs.** Reproduce a bug before fixing it. Keep the failing case when it protects owned behavior, in the existing test that covers the fixed code, or a new case when none fits.
|
|
15
15
|
- **Throwaway.** Settle a worry with a throwaway test or prototype and delete it once answered, unless it becomes a regression test under Bugs.
|
|
16
16
|
- **Honest.** No wrong expectations or rule workarounds. Change assertions only for recorded behavior changes; preserve every still-relevant value.
|
|
@@ -38,26 +38,26 @@ assert.strictEqual(Array.flatMap(attioProvider.groups, group => group.actions).l
|
|
|
38
38
|
|
|
39
39
|
## Choose cases
|
|
40
40
|
|
|
41
|
-
- **Contract.** Connect a reachable input to an observable output, so a plausible wrong implementation fails. Use the nearest public interface, not private helpers or new harnesses. When behavior is uncertain, settle one reachable case with a focused check and minimal implementation
|
|
42
|
-
- **Branches.** Exercise reachable empty
|
|
43
|
-
- **Failure.** Effect.flip for expected failure
|
|
41
|
+
- **Contract.** Connect a reachable input to an observable output, so a plausible wrong implementation fails. Use the nearest public interface, not private helpers or new harnesses. When behavior is uncertain, settle one reachable case with a focused check and a minimal implementation. Then choose the next case from the findings; never batch tests for an imagined implementation or require universal TDD.
|
|
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
|
+
- **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 first.
|
|
45
45
|
|
|
46
46
|
## Effect and fixtures
|
|
47
47
|
|
|
48
|
-
- **Entrypoint.** Plain it
|
|
49
|
-
- **Lifetime.** it.effect
|
|
50
|
-
- **Isolation.** Shared layers share mutable services
|
|
51
|
-
- **Clock.** TestClock
|
|
52
|
-
- **Concurrency.**
|
|
53
|
-
- **Boundary.** Double only dependencies outside tested logic,
|
|
54
|
-
- **Artifacts.**
|
|
55
|
-
- **Platform.** FileSystem.layerNoop is not memory storage
|
|
48
|
+
- **Entrypoint.** Plain `it` tests sync and Promise public functions. `it.effect(name, () => effect)` runs with virtual services and `it.live` with real ones. Return the Effect from the callback, with no runner wrapper. `it.layer` caches its block context and exposes `effect`, not `live`; shared live layers use `{excludeTestServices: true}`. Check APIs and imports against the cloned source.
|
|
49
|
+
- **Lifetime.** `it.effect` and `it.live` supply a Scope per case; `it.layer` acquisition belongs to its block. Use scoped platform resources and no redundant whole-case `Effect.scoped`. To assert cleanup, close the smaller operation scope before you inspect.
|
|
50
|
+
- **Isolation.** Shared layers share mutable services, `TestClock` and `TestConsole`, and `Layer.fresh` does not reset cached context. Use distinct keys, a public reset or case-local fixtures. Cases pass alone and together, and never depend on earlier output.
|
|
51
|
+
- **Clock.** Take `TestClock` and `TestConsole` from `effect/testing`. Fork, await a `Deferred` at the dependency boundary that carries the actual key or path, adjust time, then join; forking alone is not readiness. JavaScript timers, sockets and processes need live services, not `TestClock`.
|
|
52
|
+
- **Concurrency.** Give the dependency an entered and release pair: await entry, start the competing action, release, then assert the contract. Set `Effect.all` concurrency for overlap. For cancellation, await acquisition, interrupt and await completion; an unrelated outer Scope stays open. Never test primitives or incidental ordering.
|
|
53
|
+
- **Boundary.** Double only dependencies outside the tested logic, through a Layer or a public parameter. A claim about an SDK, process, filesystem, HTTP, browser or package integration needs the real boundary, not matching mocks or spies.
|
|
54
|
+
- **Artifacts.** Prove a public tool with real packing and declared dependencies, not source imports or stubs. Generate fixtures with the real generator after one representative input settles; never alter expected output to fit a bug.
|
|
55
|
+
- **Platform.** `FileSystem.layerNoop` is not memory storage, and `HttpServer.layerServices` supplies no filesystem; use a platform Layer and scoped paths. `RpcTest` bypasses serialization, and `HttpApiTest` bypasses sockets while it tests routing, encoding, middleware and decoding. Neither proves deployment or an external integration.
|
|
56
56
|
|
|
57
57
|
## Run and reconcile
|
|
58
58
|
|
|
59
59
|
- **Run.** Use the documented test selectors and confirm that they collect and execute the intended cases. Zero collected tests, setup alone or exit code 0 alone prove nothing.
|
|
60
|
-
- **Inventory.** Collect independent failures once instead of cancelling the suite at the first failure. Distinguish separate cases from repeated observations. Rerun a timed-out test once in isolation; a pass proves that run, not the cause of the timeout.
|
|
60
|
+
- **Inventory.** Collect independent failures once instead of cancelling the suite at the first failure. Distinguish separate cases from repeated observations. Rerun a timed-out test once in isolation; a pass proves that run, not the cause of the timeout. Never call a bypassed selector or a skipped operation flaky.
|
|
61
61
|
- **Evidence.** Preserve the first failure, exact command, execution count and exit status. Separate cause from hypothesis, product defect from setup failure, and a skipped criterion from a passing one. A source read or overall green command never substitutes for a missing behavior check.
|
|
62
62
|
- **Stable inputs.** A check proves only the source, dependencies and configuration it ran on; an edit to them invalidates its result.
|
|
63
63
|
- **Final proof.** Reconcile every required behavior on the final code. Screenshots and video prove visible outcomes; execution through the public interface proves the rest.
|