@deslop/coding-standards 0.1.785 → 0.1.828
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 +27 -26
- package/dist/{MutableRef-DT4DAKeI.mjs → MutableRef-NhSl8G9M.mjs} +145 -4
- package/dist/install.js +2 -2
- package/dist/oxlint.d.ts +7 -0
- package/dist/oxlint.js +201 -31
- package/package.json +1 -1
- package/skills/design/SKILL.md +45 -4
- package/skills/engineering/SKILL.md +3 -1
- package/skills/testing/SKILL.md +2 -1
package/README.md
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
# Coding standards
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A strict Oxlint preset and an installer for the engineering, design and testing agent skills. Requires Node 26+.
|
|
4
4
|
|
|
5
|
-
## Install skills
|
|
5
|
+
## Install the skills
|
|
6
|
+
|
|
7
|
+
Run inside a Git repository, including a linked worktree:
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
vpx @deslop/coding-standards@latest
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
| -------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
13
|
-
| Engineering, design and testing skills | Git root's existing `.agents`, `.claude` and `.codex` directories; creates `.agents` if none exist |
|
|
14
|
-
| Refresh | Replaces only those three skill names; preserves other skills and global configuration |
|
|
15
|
-
| Boundary | Rejects destinations escaping the repository or overlapping global configuration |
|
|
16
|
-
|
|
17
|
-
No repository dependency, manifest/lock change or lint configuration change. Treat installed skill copies as read-only; refresh through the CLI.
|
|
13
|
+
It copies the three skills into the repository's existing `.agents`, `.claude` and `.codex` directories, or creates `.agents` when none exists. A refresh replaces only those three skills, and destinations outside the repository or inside global agent configuration are rejected. Nothing is added to the manifest, lockfile or lint configuration. Installed copies are read-only; refresh them with the CLI.
|
|
18
14
|
|
|
19
15
|
## Use the lint preset
|
|
20
16
|
|
|
@@ -30,24 +26,19 @@ import {oxlint} from '@deslop/coding-standards'
|
|
|
30
26
|
export default defineConfig({lint: {extends: [oxlint]}})
|
|
31
27
|
```
|
|
32
28
|
|
|
33
|
-
The preset
|
|
34
|
-
|
|
35
|
-
## Working here
|
|
29
|
+
The preset needs Effect-tsgo's patched Oxlint, so keep the patch command in the project's `prepare` script.
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| `src/` | Flat package structure; rules in `src/rules`, no separate plugin or install-test harness |
|
|
40
|
-
| `skills/` | Maintained engineering/design/testing sources; refresh repository copies from the TypeScript CLI, no build needed |
|
|
41
|
-
| Lint rules | Prefer an equivalent maintained rule before custom enforcement; consumer code conforms to strict standards, not the reverse. Fix broken/imprecise rules, not inconvenient valid diagnostics |
|
|
42
|
-
| Rule proof | Existing public CLI regression cases, with accepted/rejected controls; no severity/exclusion workaround |
|
|
31
|
+
<details>
|
|
32
|
+
<summary>For agents</summary>
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
### Decisions
|
|
45
35
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
36
|
+
| Owner | Decision |
|
|
37
|
+
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| `src/` | Flat package structure; rules in `src/rules`, no separate plugin or install-test harness |
|
|
39
|
+
| `skills/` | Maintained engineering, design and testing sources; refresh repository copies from the TypeScript CLI, no build needed |
|
|
40
|
+
| Lint rules | Prefer an equivalent maintained rule before custom enforcement; consumer code conforms to strict standards, not the reverse. Fix broken or imprecise rules, not inconvenient valid diagnostics. A rule targets code the engineering skill and the other rules allow; an edge case that only unusual code reaches takes an inline disable with its reason, never a looser rule |
|
|
41
|
+
| Rule proof | Existing public CLI regression cases, with accepted and rejected controls; no severity or exclusion workaround |
|
|
51
42
|
|
|
52
43
|
With workspace dependencies installed, from the repository root:
|
|
53
44
|
|
|
@@ -56,9 +47,17 @@ vp test run tools/coding-standards/src/rules/rules.test.ts
|
|
|
56
47
|
node tools/coding-standards/src/install.ts
|
|
57
48
|
```
|
|
58
49
|
|
|
59
|
-
|
|
50
|
+
### Rejected shortcuts
|
|
60
51
|
|
|
61
|
-
|
|
|
52
|
+
| Candidate | Reason |
|
|
53
|
+
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| Blanket `unknown` type ban | Opaque SDK and framework contracts legitimately need it; narrow or decode before use instead of fabricating a shape |
|
|
55
|
+
| Functional preset or dependency just to replace `no-let` | The preset conflicts; a selective plugin adds dependencies for a tiny rule |
|
|
56
|
+
| Syntax-only generator-failure rule | Factory-call syntax does not establish yieldability; the existing type-aware Effect rule owns that check |
|
|
57
|
+
|
|
58
|
+
### Skill sources
|
|
59
|
+
|
|
60
|
+
| Source | Used and boundary |
|
|
62
61
|
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
63
62
|
| [Matt Pocock's engineering skills](https://github.com/mattpocock/skills/tree/d81f3a183412e71a5b1e84ca21bc1a35eea03a60/skills/engineering) | Caller-first contracts, causal debugging and one uncertain behavior at a time; no universal TDD |
|
|
64
63
|
| [Cursor / pstack](https://github.com/cursor/plugins/tree/e43c7ee26e0038c6c1fa8380dd34ce86ff94cb2a/pstack) | Interface rationale, focused bug replay and defensible benchmarks; no playbook-stage machinery |
|
|
@@ -70,3 +69,5 @@ node tools/coding-standards/src/install.ts
|
|
|
70
69
|
| [Awesome DESIGN.md](https://github.com/VoltAgent/awesome-design-md) and [Impeccable](https://github.com/pbakaus/impeccable) | Reviewed reference collections; community brand analyses are inspiration, not authoritative/current tokens |
|
|
71
70
|
|
|
72
71
|
These are selectively adapted ideas, not imported frameworks. Concrete behavior, existing stack and settled user decisions govern the maintained skills.
|
|
72
|
+
|
|
73
|
+
</details>
|
|
@@ -3444,7 +3444,7 @@ const isSome = isSome$1;
|
|
|
3444
3444
|
* @category pattern matching
|
|
3445
3445
|
* @since 2.0.0
|
|
3446
3446
|
*/
|
|
3447
|
-
const match$
|
|
3447
|
+
const match$3 = /*#__PURE__*/ dual(2, (self, { onNone, onSome }) => isNone(self) ? onNone() : onSome(self.value));
|
|
3448
3448
|
/**
|
|
3449
3449
|
* Extracts the value from a `Some`, or evaluates a fallback thunk on `None`.
|
|
3450
3450
|
*
|
|
@@ -3604,6 +3604,37 @@ const fromNullishOr = (a) => a == null ? none() : some$1(a);
|
|
|
3604
3604
|
*/
|
|
3605
3605
|
const fromUndefinedOr = (a) => a === void 0 ? none() : some$1(a);
|
|
3606
3606
|
/**
|
|
3607
|
+
* Converts a possibly `null` value into an `Option`, leaving `undefined`
|
|
3608
|
+
* as a valid `Some`.
|
|
3609
|
+
*
|
|
3610
|
+
* **When to use**
|
|
3611
|
+
*
|
|
3612
|
+
* Use when you want to treat only `null` as absent while preserving
|
|
3613
|
+
* `undefined` as a meaningful value.
|
|
3614
|
+
*
|
|
3615
|
+
* **Details**
|
|
3616
|
+
*
|
|
3617
|
+
* - `null` → `None`
|
|
3618
|
+
* - Any other value (including `undefined`) → `Some`
|
|
3619
|
+
*
|
|
3620
|
+
* **Example** (Converting possibly null values to an Option)
|
|
3621
|
+
*
|
|
3622
|
+
* ```ts import.meta.vitest
|
|
3623
|
+
* import { Option } from "effect"
|
|
3624
|
+
*
|
|
3625
|
+
* Option.fromNullOr(null) // => Option.none()
|
|
3626
|
+
* Option.fromNullOr(undefined) // => Option.some(undefined)
|
|
3627
|
+
* Option.fromNullOr(42) // => Option.some(42)
|
|
3628
|
+
* ```
|
|
3629
|
+
*
|
|
3630
|
+
* @see {@link fromNullishOr} to treat both `null` and `undefined` as absent
|
|
3631
|
+
* @see {@link fromUndefinedOr} to only treat `undefined` as absent
|
|
3632
|
+
*
|
|
3633
|
+
* @category converting
|
|
3634
|
+
* @since 4.0.0
|
|
3635
|
+
*/
|
|
3636
|
+
const fromNullOr = (a) => a === null ? none() : some$1(a);
|
|
3637
|
+
/**
|
|
3607
3638
|
* Extracts the value from a `Some`, or returns `undefined` for `None`.
|
|
3608
3639
|
*
|
|
3609
3640
|
* **When to use**
|
|
@@ -3741,6 +3772,34 @@ const map$2 = /*#__PURE__*/ dual(2, (self, f) => isNone(self) ? none() : some$1(
|
|
|
3741
3772
|
*/
|
|
3742
3773
|
const flatMap$1 = /*#__PURE__*/ dual(2, (self, f) => isNone(self) ? none() : f(self.value));
|
|
3743
3774
|
/**
|
|
3775
|
+
* Flattens a nested `Option<Option<A>>` into `Option<A>`.
|
|
3776
|
+
*
|
|
3777
|
+
* **When to use**
|
|
3778
|
+
*
|
|
3779
|
+
* Use when you need to remove one layer of nested `Option`.
|
|
3780
|
+
*
|
|
3781
|
+
* **Details**
|
|
3782
|
+
*
|
|
3783
|
+
* - `Some(Some(value))` → `Some(value)`
|
|
3784
|
+
* - `Some(None)` → `None`
|
|
3785
|
+
* - `None` → `None`
|
|
3786
|
+
*
|
|
3787
|
+
* **Example** (Flattening nested Options)
|
|
3788
|
+
*
|
|
3789
|
+
* ```ts import.meta.vitest
|
|
3790
|
+
* import { Option } from "effect"
|
|
3791
|
+
*
|
|
3792
|
+
* Option.flatten(Option.some(Option.some("value"))) // => Option.some("value")
|
|
3793
|
+
* Option.flatten(Option.some(Option.none())) // => Option.none()
|
|
3794
|
+
* ```
|
|
3795
|
+
*
|
|
3796
|
+
* @see {@link flatMap} which is `map` + `flatten`
|
|
3797
|
+
*
|
|
3798
|
+
* @category sequencing
|
|
3799
|
+
* @since 2.0.0
|
|
3800
|
+
*/
|
|
3801
|
+
const flatten$1 = /*#__PURE__*/ flatMap$1(identity);
|
|
3802
|
+
/**
|
|
3744
3803
|
* Combines two `Option`s into a `Some` containing a tuple `[A, B]` if both
|
|
3745
3804
|
* are `Some`.
|
|
3746
3805
|
*
|
|
@@ -4186,7 +4245,7 @@ const isSuccess = isSuccess$1;
|
|
|
4186
4245
|
* @category pattern matching
|
|
4187
4246
|
* @since 2.0.0
|
|
4188
4247
|
*/
|
|
4189
|
-
const match$
|
|
4248
|
+
const match$2 = /*#__PURE__*/ dual(2, (self, { onFailure, onSuccess }) => isFailure(self) ? onFailure(self.failure) : onSuccess(self.success));
|
|
4190
4249
|
/**
|
|
4191
4250
|
* Extracts the success value or throws the raw failure value `E`.
|
|
4192
4251
|
*
|
|
@@ -4557,7 +4616,7 @@ const ensure = (self) => Array$1.isArray(self) ? self : [self];
|
|
|
4557
4616
|
* @category pattern matching
|
|
4558
4617
|
* @since 2.0.0
|
|
4559
4618
|
*/
|
|
4560
|
-
const match = /*#__PURE__*/ dual(2, (self, { onEmpty, onNonEmpty }) => isReadonlyArrayNonEmpty(self) ? onNonEmpty(self) : onEmpty());
|
|
4619
|
+
const match$1 = /*#__PURE__*/ dual(2, (self, { onEmpty, onNonEmpty }) => isReadonlyArrayNonEmpty(self) ? onNonEmpty(self) : onEmpty());
|
|
4561
4620
|
/**
|
|
4562
4621
|
* Adds a single element to the front of an iterable, returning a `NonEmptyArray`.
|
|
4563
4622
|
*
|
|
@@ -4769,6 +4828,30 @@ const get$1 = /*#__PURE__*/ dual(2, (self, index) => {
|
|
|
4769
4828
|
return isOutOfBounds(i, self) ? none() : some$1(self[i]);
|
|
4770
4829
|
});
|
|
4771
4830
|
/**
|
|
4831
|
+
* Returns the first element of an array safely wrapped in `Option.some`, or
|
|
4832
|
+
* `Option.none` if the array is empty.
|
|
4833
|
+
*
|
|
4834
|
+
* **When to use**
|
|
4835
|
+
*
|
|
4836
|
+
* Use to safely get the first element of an array that may be empty.
|
|
4837
|
+
*
|
|
4838
|
+
* **Example** (Getting the first element)
|
|
4839
|
+
*
|
|
4840
|
+
* ```ts import.meta.vitest
|
|
4841
|
+
* import { Array, Option } from "effect"
|
|
4842
|
+
*
|
|
4843
|
+
* Array.head([1, 2, 3]) // => Option.some(1)
|
|
4844
|
+
* Array.head([]) // => Option.none()
|
|
4845
|
+
* ```
|
|
4846
|
+
*
|
|
4847
|
+
* @see {@link headNonEmpty} — direct access when array is known non-empty
|
|
4848
|
+
* @see {@link last} — get the last element
|
|
4849
|
+
*
|
|
4850
|
+
* @category getters
|
|
4851
|
+
* @since 2.0.0
|
|
4852
|
+
*/
|
|
4853
|
+
const head = /*#__PURE__*/ get$1(0);
|
|
4854
|
+
/**
|
|
4772
4855
|
* Returns the last element of an array safely wrapped in `Option.some`, or
|
|
4773
4856
|
* `Option.none` if the array is empty.
|
|
4774
4857
|
*
|
|
@@ -5635,6 +5718,43 @@ const Order = String$2;
|
|
|
5635
5718
|
*/
|
|
5636
5719
|
const trimEnd = (self) => self.trimEnd();
|
|
5637
5720
|
/**
|
|
5721
|
+
* Splits a string into an array of substrings using a separator.
|
|
5722
|
+
*
|
|
5723
|
+
* **Example** (Splitting strings)
|
|
5724
|
+
*
|
|
5725
|
+
* ```ts import.meta.vitest
|
|
5726
|
+
* import { pipe, String } from "effect"
|
|
5727
|
+
*
|
|
5728
|
+
* pipe("abc", String.split("")) // => ["a", "b", "c"]
|
|
5729
|
+
* pipe("", String.split("")) // => [""]
|
|
5730
|
+
* String.split("hello,world", ",") // => ["hello", "world"]
|
|
5731
|
+
* ```
|
|
5732
|
+
*
|
|
5733
|
+
* @category transforming
|
|
5734
|
+
* @since 2.0.0
|
|
5735
|
+
*/
|
|
5736
|
+
const split = /*#__PURE__*/ dual(2, (self, separator) => {
|
|
5737
|
+
const out = self.split(separator);
|
|
5738
|
+
return isArrayNonEmpty$1(out) ? out : [self];
|
|
5739
|
+
});
|
|
5740
|
+
/**
|
|
5741
|
+
* Returns `true` if `searchString` appears as a substring of `self`, at one or more positions that are
|
|
5742
|
+
* greater than or equal to `position`; otherwise, returns `false`.
|
|
5743
|
+
*
|
|
5744
|
+
* **Example** (Checking for substrings)
|
|
5745
|
+
*
|
|
5746
|
+
* ```ts import.meta.vitest
|
|
5747
|
+
* import { pipe, String } from "effect"
|
|
5748
|
+
*
|
|
5749
|
+
* pipe("hello world", String.includes("world")) // => true
|
|
5750
|
+
* pipe("hello world", String.includes("foo")) // => false
|
|
5751
|
+
* ```
|
|
5752
|
+
*
|
|
5753
|
+
* @category predicates
|
|
5754
|
+
* @since 2.0.0
|
|
5755
|
+
*/
|
|
5756
|
+
const includes = (searchString, position) => (self) => self.includes(searchString, position);
|
|
5757
|
+
/**
|
|
5638
5758
|
* Returns `true` if the string starts with the specified search string.
|
|
5639
5759
|
*
|
|
5640
5760
|
* **Example** (Checking string prefixes)
|
|
@@ -5650,6 +5770,27 @@ const trimEnd = (self) => self.trimEnd();
|
|
|
5650
5770
|
* @since 2.0.0
|
|
5651
5771
|
*/
|
|
5652
5772
|
const startsWith = (searchString, position) => (self) => self.startsWith(searchString, position);
|
|
5773
|
+
/**
|
|
5774
|
+
* Matches a string against a pattern safely and returns `Option.some` with the match
|
|
5775
|
+
* array, or `Option.none` when the pattern does not match.
|
|
5776
|
+
*
|
|
5777
|
+
* **Example** (Matching regular expressions)
|
|
5778
|
+
*
|
|
5779
|
+
* ```ts import.meta.vitest
|
|
5780
|
+
* import { Option, pipe, String } from "effect"
|
|
5781
|
+
*
|
|
5782
|
+
* pipe(
|
|
5783
|
+
* "hello",
|
|
5784
|
+
* String.match(/l+/),
|
|
5785
|
+
* Option.map((match) => [match[0], match.index])
|
|
5786
|
+
* ) // => Option.some(["ll", 2])
|
|
5787
|
+
* pipe("hello", String.match(/x/)) // => Option.none()
|
|
5788
|
+
* ```
|
|
5789
|
+
*
|
|
5790
|
+
* @category searching
|
|
5791
|
+
* @since 2.0.0
|
|
5792
|
+
*/
|
|
5793
|
+
const match = (regExp) => (self) => fromNullOr(self.match(regExp));
|
|
5653
5794
|
//#endregion
|
|
5654
5795
|
//#region ../../node_modules/.pnpm/effect@4.0.0/node_modules/effect/dist/MutableRef.js
|
|
5655
5796
|
const TypeId = "~effect/MutableRef";
|
|
@@ -5848,4 +5989,4 @@ const set = /*#__PURE__*/ dual(2, (self, value) => {
|
|
|
5848
5989
|
*/
|
|
5849
5990
|
const update = /*#__PURE__*/ dual(2, (self, f) => set(self, f(get(self))));
|
|
5850
5991
|
//#endregion
|
|
5851
|
-
export {
|
|
5992
|
+
export { fromOption as $, isNullish as $n, causeFail as $t, getSomes as A, formatPath as An, isGreaterThanOrEqualTo as At, match$1 as B, optimize as Bn, Error$2 as Bt, findFirst as C, setStackTraceLimit as Cn, some$1 as Ct, flatten as D, format as Dn, Number$1 as Dt, flatMap as E, toStringUnknown as En, Date$1 as Et, isReadonlyArrayNonEmpty as F, symbol as Fn, Tuple as Ft, sort as G, hasProperty as Gn, ReasonBase as Gt, prepend as H, string as Hn, Fail as Ht, join as I, array as In, make$2 as It, get$2 as J, isFunction as Jn, Yield as Jt, take as K, isBoolean as Kn, StackTraceKey as Kt, last as L, combine as Ln, strictEqual as Lt, isArrayEmpty as M, currentFiberTypeId as Mn, isLessThanOrEqualTo as Mt, isArrayNonEmpty as N, byReferenceUnsafe as Nn, make$1 as Nt, fromIterable as O, formatDate as On, String$2 as Ot, isCanonicalArrayIndex as P, equals as Pn, mapInput as Pt, fail as Q, isNotUndefined as Qn, causeEmpty as Qt, makeEquivalence as R, hash as Rn, CauseImpl as Rt, filterMap as S, getStackTraceLimit as Sn, orElse as St, findLast as T, NodeInspectSymbol as Tn, BigInt as Tt, reduce as U, structure as Un, InterruptorStackTrace as Ut, of as V, random as Vn, ExitTypeId as Vt, some as W, symbol$1 as Wn, PipeInspectableProto as Wt, remove as X, isNotNull as Xn, causeAnnotate as Xt, map$1 as Y, isIterable as Yn, args as Yt, values as Z, isNotNullish as Zn, causeDie as Zt, dropRight as _, makePrimitive as _n, memoizeIdempotent as _r, isSome as _t, includes as a, done as an, isString as ar, all as at, every as b, assignProperty as bn, Prototype as br, match$3 as bt, startsWith as c, exitFail as cn, isUnknown as cr, filter$1 as ct, TaggedError as d, isCause as dn, constUndefined as dr, flatten$1 as dt, causeFromReasons as en, isNumber as er, getOrThrow as et, taggedEnum as f, isDieReason as fn, constVoid as fr, fromNullishOr as ft, dedupe as g, isInterruptReason as gn, memoize as gr, isNone as gt, contains as h, isFailReason as hn, identity as hr, getOrUndefined as ht, Order as i, contE as in, isReadonlyObject as ir, normalize as it, head as j, formatPropertyKey as jn, isLessThan as jt, get$1 as k, formatJson as kn, isGreaterThan as kt, trimEnd as l, exitFailCause as ln, constFalse as lr, firstSomeOf as lt, appendAll as m, isEffect as mn, dual as mr, getOrElse as mt, make as n, contA as nn, isObjectKeyword as nr, match$2 as nt, match as o, evaluate as on, isTagged as or, contains$1 as ot, append as p, isDone as pn, constant as pr, fromUndefinedOr as pt, fromEntries as q, isError as qn, TaggedError$1 as qt, update as r, contAll as rn, isPropertyKey as rr, succeed as rt, split as s, exitDie as sn, isUndefined as sr, exists as st, get as t, constEmptyAnnotations as tn, isObject as tr, isFailure as tt, Error$1 as u, exitSucceed as un, constTrue as ur, flatMap$1 as ut, empty as v, makePrimitiveProto as vn, pipe as vr, liftThrowable as vt, findFirstIndex as w, BaseProto as wn, zipWith as wt, filter as x, internalCall as xn, pipeArguments as xr, none as xt, ensure as y, withFiber as yn, Class$1 as yr, map$2 as yt, map as z, number as zn, Done as zt };
|
package/dist/install.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $n as isNullish, $t as causeFail, An as formatPath, B as match$5, Bt as Error$3, C as findFirst, Cn as setStackTraceLimit, Ct as some, Dn as format$1, Dt as Number$6, En as toStringUnknown, F as isReadonlyArrayNonEmpty, Fn as symbol, Gn as hasProperty, Gt as ReasonBase, Hn as string$1, Ht as Fail, I as join, Jn as isFunction, Jt as Yield, Kn as isBoolean$1, Kt as StackTraceKey, Ln as combine$1, M as isArrayEmpty, Mn as currentFiberTypeId, N as isArrayNonEmpty, O as fromIterable$1, Ot as String$6, P as isCanonicalArrayIndex, Pn as equals$1, Pt as mapInput, Q as fail$5, Qn as isNotUndefined, Qt as causeEmpty, Rn as hash, Rt as CauseImpl, Sn as getStackTraceLimit, T as findLast, Tn as NodeInspectSymbol, U as reduce, Un as structure, Ut as InterruptorStackTrace, V as of, Vn as random, Vt as ExitTypeId, W as some$1, Wn as symbol$1, Wt as PipeInspectableProto, Xn as isNotNull, Xt as causeAnnotate, Yn as isIterable, Yt as args, Zn as isNotNullish, Zt as causeDie, _n as makePrimitive$1, _r as memoizeIdempotent, _t as isSome, an as done$1, ar as isString, bn as assignProperty, br as Prototype$1, bt as match$4, c as startsWith, cn as exitFail, cr as isUnknown, d as TaggedError$1, dn as isCause, dr as constUndefined, en as causeFromReasons, er as isNumber, f as taggedEnum, fn as isDieReason, fr as constVoid, ft as fromNullishOr, g as dedupe, gn as isInterruptReason, gr as memoize, gt as isNone, h as contains, hn as isFailReason$1, hr as identity, ht as getOrUndefined$1, in as contE, it as normalize$1, jn as formatPropertyKey, kn as formatJson, kt as isGreaterThan$1, l as trimEnd, ln as exitFailCause, lr as constFalse, mn as isEffect$1, mr as dual, mt as getOrElse, n as make$29, nn as contA, nr as isObjectKeyword, nt as match$3, on as evaluate, or as isTagged, p as append$1, pn as isDone$2, pr as constant, pt as fromUndefinedOr, qn as isError, qt as TaggedError$2, rn as contAll, rt as succeed$6, sn as exitDie, sr as isUndefined, st as exists, tn as constEmptyAnnotations, tr as isObject, tt as isFailure$1, u as Error$2, un as exitSucceed, ur as constTrue, ut as flatMap$3, v as empty$3, vn as makePrimitiveProto, vr as pipe, w as findFirstIndex, wn as BaseProto, x as filter, xn as internalCall, xr as pipeArguments, xt as none, yn as withFiber$1, yr as Class, yt as map$10, z as map$9, zn as number$1, zt as Done$1 } from "./MutableRef-NhSl8G9M.mjs";
|
|
3
3
|
import * as NodeChildProcess from "node:child_process";
|
|
4
4
|
import { PassThrough } from "node:stream";
|
|
5
5
|
import * as NodeCrypto from "node:crypto";
|
|
@@ -24685,7 +24685,7 @@ const runWith = (command, config) => {
|
|
|
24685
24685
|
};
|
|
24686
24686
|
//#endregion
|
|
24687
24687
|
//#region package.json
|
|
24688
|
-
var version = "0.1.
|
|
24688
|
+
var version = "0.1.828";
|
|
24689
24689
|
//#endregion
|
|
24690
24690
|
//#region src/install.ts
|
|
24691
24691
|
const cli = pipe(make("deslop-coding-standards", {}, fnUntraced(function* () {
|
package/dist/oxlint.d.ts
CHANGED
|
@@ -4098,6 +4098,7 @@ export declare const oxlint: {
|
|
|
4098
4098
|
'@deslop/coding-standards/no-let': "error";
|
|
4099
4099
|
'@deslop/coding-standards/no-native-emptiness-check': "error";
|
|
4100
4100
|
'@deslop/coding-standards/no-native-method-call': "error";
|
|
4101
|
+
'@deslop/coding-standards/no-nullish-ternary': "error";
|
|
4101
4102
|
'@deslop/coding-standards/no-option-value-access': "error";
|
|
4102
4103
|
'@deslop/coding-standards/no-readonly-type-syntax': "error";
|
|
4103
4104
|
'@deslop/coding-standards/no-redundant-return-type': "error";
|
|
@@ -4106,6 +4107,7 @@ export declare const oxlint: {
|
|
|
4106
4107
|
'@deslop/coding-standards/no-redundant-variable-annotation': "error";
|
|
4107
4108
|
'@deslop/coding-standards/no-reinvented-schema': "error";
|
|
4108
4109
|
'@deslop/coding-standards/no-renamed-import': "error";
|
|
4110
|
+
'@deslop/coding-standards/no-single-use-value': "error";
|
|
4109
4111
|
'@deslop/coding-standards/no-stored-schema-operation': "error";
|
|
4110
4112
|
'@deslop/coding-standards/no-switch-statement': "error";
|
|
4111
4113
|
'@deslop/coding-standards/no-trivial-indirection': "error";
|
|
@@ -4509,6 +4511,11 @@ export declare const oxlint: {
|
|
|
4509
4511
|
disallowArithmeticOperators: true;
|
|
4510
4512
|
}];
|
|
4511
4513
|
'no-unused-expressions': "error";
|
|
4514
|
+
'no-unused-vars': ["error", {
|
|
4515
|
+
argsIgnorePattern: string;
|
|
4516
|
+
ignoreRestSiblings: true;
|
|
4517
|
+
reportUsedIgnorePattern: true;
|
|
4518
|
+
}];
|
|
4512
4519
|
'no-useless-assignment': "error";
|
|
4513
4520
|
'no-useless-backreference': "error";
|
|
4514
4521
|
'no-useless-call': "error";
|
package/dist/oxlint.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $
|
|
1
|
+
import { $ as fromOption, $n as isNullish$1, A as getSomes, Bn as optimize, C as findFirst, Ct as some, D as flatten, E as flatMap$1, Fn as symbol, G as sort, Gn as hasProperty, H as prepend, Hn as string, I as join, J as get$2, K as take, Kn as isBoolean, L as last, Ln as combine, M as isArrayEmpty, N as isArrayNonEmpty, Nn as byReferenceUnsafe, Pn as equals, Rn as hash, S as filterMap, St as orElse, Tn as NodeInspectSymbol, V as of, W as some$1, Wn as symbol$1, X as remove, Xn as isNotNull, Z as values, Zn as isNotNullish, _ as dropRight, _t as isSome, a as includes, ar as isString, at as all, b as every, bt as match$1, c as startsWith, ct as filter, d as TaggedError, dt as flatten$1, er as isNumber, ft as fromNullishOr, g as dedupe, h as contains$1, i as Order, ir as isReadonlyObject, j as head, k as get$1, lt as firstSomeOf, m as appendAll, mr as dual, mt as getOrElse, n as make$2, o as match, ot as contains, p as append, pt as fromUndefinedOr, q as fromEntries, r as update, s as split, st as exists, t as get$3, ut as flatMap, v as empty, vr as pipe, w as findFirstIndex, wt as zipWith, x as filter$1, xr as pipeArguments, xt as none, y as ensure, yt as map, z as map$1, zn as number } from "./MutableRef-NhSl8G9M.mjs";
|
|
2
2
|
//#region ../../node_modules/.pnpm/effect@4.0.0/node_modules/effect/dist/internal/graph.js
|
|
3
3
|
/** @internal */
|
|
4
4
|
const TypeId = "~effect/Graph";
|
|
@@ -668,8 +668,11 @@ function importedMember(input) {
|
|
|
668
668
|
source: "effect"
|
|
669
669
|
}) && (input.propertyName === void 0 || contains(memberName(input.node), input.propertyName));
|
|
670
670
|
}
|
|
671
|
+
function matches(regExp) {
|
|
672
|
+
return (text) => isSome(pipe(text, match(regExp)));
|
|
673
|
+
}
|
|
671
674
|
function isSchemaOperationName(name) {
|
|
672
|
-
return /^(?:(?:decode|encode)(?:Unknown)?(?:Effect|Exit|Option|Promise|Result|Sync)|asserts|is)$/u
|
|
675
|
+
return matches(/^(?:(?:decode|encode)(?:Unknown)?(?:Effect|Exit|Option|Promise|Result|Sync)|asserts|is)$/u)(name);
|
|
673
676
|
}
|
|
674
677
|
function isSchemaOperationCall(input) {
|
|
675
678
|
if (input.node.callee.type !== "MemberExpression" || !importedMember({
|
|
@@ -787,7 +790,7 @@ function suspendSeeds(input) {
|
|
|
787
790
|
return pipe(suspendedTypeName({
|
|
788
791
|
context: input.context,
|
|
789
792
|
node: input.node
|
|
790
|
-
}), match({
|
|
793
|
+
}), match$1({
|
|
791
794
|
onNone: () => seeds,
|
|
792
795
|
onSome: (name) => prepend(seeds, name)
|
|
793
796
|
}));
|
|
@@ -858,7 +861,7 @@ function testedValue(node) {
|
|
|
858
861
|
function isSingletonArray(input) {
|
|
859
862
|
if (input.node.type !== "ArrayExpression" || input.node.elements.length !== 1) return false;
|
|
860
863
|
const element = input.node.elements[0];
|
|
861
|
-
if (isNullish(element) || element.type === "SpreadElement") return false;
|
|
864
|
+
if (isNullish$1(element) || element.type === "SpreadElement") return false;
|
|
862
865
|
return input.context.sourceCode.getText(element) === input.context.sourceCode.getText(input.value);
|
|
863
866
|
}
|
|
864
867
|
function restatesArrayEnsure(input) {
|
|
@@ -937,8 +940,7 @@ function enclosingPipes(context, node) {
|
|
|
937
940
|
"FunctionDeclaration",
|
|
938
941
|
"FunctionExpression"
|
|
939
942
|
], node.type)) return 0;
|
|
940
|
-
|
|
941
|
-
return enclosingPipes(context, node.parent) + (nested ? 1 : 0);
|
|
943
|
+
return enclosingPipes(context, node.parent) + (isPipeCall(context, node.parent) && some$1(node.parent.arguments, (argument) => argument === node) ? 1 : 0);
|
|
942
944
|
}
|
|
943
945
|
const noDeepPipe = defineRule({
|
|
944
946
|
create: (context) => ({ CallExpression: (node) => {
|
|
@@ -970,10 +972,8 @@ function namedTuple(input) {
|
|
|
970
972
|
}))));
|
|
971
973
|
}
|
|
972
974
|
function component(node) {
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
const name = declared ?? assigned;
|
|
976
|
-
if (name === void 0 || !/^[A-Z]/u.test(name.name) || node.params.length !== 1 || node.body === null) return false;
|
|
975
|
+
const name = (node.type === "ArrowFunctionExpression" ? void 0 : node.id) ?? (node.parent.type === "VariableDeclarator" && node.parent.id.type === "Identifier" ? node.parent.id : void 0);
|
|
976
|
+
if (name === void 0 || !matches(/^[A-Z]/u)(name.name) || node.params.length !== 1 || node.body === null) return false;
|
|
977
977
|
if (node.body.type === "JSXElement" || node.body.type === "JSXFragment") return true;
|
|
978
978
|
return node.body.type === "BlockStatement" && some$1(node.body.body, (statement) => statement.type === "ReturnStatement" && (statement.argument?.type === "JSXElement" || statement.argument?.type === "JSXFragment"));
|
|
979
979
|
}
|
|
@@ -1102,7 +1102,7 @@ function receiverName(node) {
|
|
|
1102
1102
|
return node.type === "MemberExpression" ? memberName(node) : none();
|
|
1103
1103
|
}
|
|
1104
1104
|
function isErrorMessage(node) {
|
|
1105
|
-
return node?.type === "MemberExpression" && contains(memberName(node), "message") && exists(receiverName(node.object), (
|
|
1105
|
+
return node?.type === "MemberExpression" && contains(memberName(node), "message") && exists(receiverName(node.object), matches(/^(?:e|err|error|failure|cause|exception)$|Error$/iu));
|
|
1106
1106
|
}
|
|
1107
1107
|
function expectsMessage(node) {
|
|
1108
1108
|
const subject = node.object.type === "MemberExpression" && contains(memberName(node.object), "not") ? node.object.object : node.object;
|
|
@@ -1139,7 +1139,7 @@ function hasUnusedSetter(input) {
|
|
|
1139
1139
|
const declaration = input.node.parent;
|
|
1140
1140
|
if (declaration.type !== "VariableDeclarator" || declaration.id.type !== "ArrayPattern") return false;
|
|
1141
1141
|
const setter = declaration.id.elements[1];
|
|
1142
|
-
if (isNullish(setter)) return true;
|
|
1142
|
+
if (isNullish$1(setter)) return true;
|
|
1143
1143
|
if (setter.type !== "Identifier") return false;
|
|
1144
1144
|
return pipe(variableFromScope({
|
|
1145
1145
|
name: setter.name,
|
|
@@ -1212,10 +1212,10 @@ function check(input) {
|
|
|
1212
1212
|
subject: input.node.right
|
|
1213
1213
|
})]), map(of));
|
|
1214
1214
|
}
|
|
1215
|
-
function report(input) {
|
|
1215
|
+
function report$1(input) {
|
|
1216
1216
|
const predicate = input.node.returnType?.typeAnnotation;
|
|
1217
1217
|
const returned = returnedExpression(input.node);
|
|
1218
|
-
if (predicate?.type !== "TSTypePredicate" || predicate.asserts || predicate.parameterName.type !== "Identifier" || isNullish(returned)) return;
|
|
1218
|
+
if (predicate?.type !== "TSTypePredicate" || predicate.asserts || predicate.parameterName.type !== "Identifier" || isNullish$1(returned)) return;
|
|
1219
1219
|
const name = pipe(check({
|
|
1220
1220
|
name: predicate.parameterName.name,
|
|
1221
1221
|
node: returned
|
|
@@ -1252,19 +1252,19 @@ function report(input) {
|
|
|
1252
1252
|
const noHandWrittenGuard = defineRule({
|
|
1253
1253
|
create: (context) => ({
|
|
1254
1254
|
ArrowFunctionExpression: (node) => {
|
|
1255
|
-
report({
|
|
1255
|
+
report$1({
|
|
1256
1256
|
context,
|
|
1257
1257
|
node
|
|
1258
1258
|
});
|
|
1259
1259
|
},
|
|
1260
1260
|
FunctionDeclaration: (node) => {
|
|
1261
|
-
report({
|
|
1261
|
+
report$1({
|
|
1262
1262
|
context,
|
|
1263
1263
|
node
|
|
1264
1264
|
});
|
|
1265
1265
|
},
|
|
1266
1266
|
FunctionExpression: (node) => {
|
|
1267
|
-
report({
|
|
1267
|
+
report$1({
|
|
1268
1268
|
context,
|
|
1269
1269
|
node
|
|
1270
1270
|
});
|
|
@@ -1367,6 +1367,13 @@ const nativeMethods = [
|
|
|
1367
1367
|
"values",
|
|
1368
1368
|
"with"
|
|
1369
1369
|
];
|
|
1370
|
+
function freshRegExpFlags(input) {
|
|
1371
|
+
if (input.node.type === "Literal" && hasProperty(input.node, "regex")) return some(pipe(input.context.sourceCode.getText(input.node), split("/"), last));
|
|
1372
|
+
if (input.node.type !== "NewExpression" && input.node.type !== "CallExpression" || input.node.callee.type !== "Identifier" || input.node.callee.name !== "RegExp") return none();
|
|
1373
|
+
const flags = input.node.arguments[1];
|
|
1374
|
+
if (flags === void 0) return some(some(""));
|
|
1375
|
+
return some(flags.type === "Literal" && isString(flags.value) ? some(flags.value) : none());
|
|
1376
|
+
}
|
|
1370
1377
|
const noNativeMethodCall = defineRule({
|
|
1371
1378
|
create: (context) => ({ CallExpression: (node) => {
|
|
1372
1379
|
if (node.callee.type !== "MemberExpression" || node.callee.object.type === "Identifier" && (node.callee.object.name === "path" || isImportBinding({
|
|
@@ -1375,6 +1382,17 @@ const noNativeMethodCall = defineRule({
|
|
|
1375
1382
|
source: /^(?:effect(?:\/|$)|@effect\/)/u
|
|
1376
1383
|
}))) return;
|
|
1377
1384
|
const receiver = node.callee.object;
|
|
1385
|
+
const regExpFlags = freshRegExpFlags({
|
|
1386
|
+
context,
|
|
1387
|
+
node: receiver
|
|
1388
|
+
});
|
|
1389
|
+
if (isSome(regExpFlags) && exists(memberName(node.callee), (name) => name === "test" || name === "exec" && exists(flatten$1(regExpFlags), (flags) => !pipe(flags, includes("g"))))) {
|
|
1390
|
+
context.report({
|
|
1391
|
+
message: "Match with String.match, which returns an Option, instead of a native RegExp method.",
|
|
1392
|
+
node: node.callee
|
|
1393
|
+
});
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1378
1396
|
pipe(memberName(node.callee), filter((name) => contains$1(nativeMethods, name)), filter((name) => !contains$1([
|
|
1379
1397
|
"entries",
|
|
1380
1398
|
"keys",
|
|
@@ -1389,6 +1407,50 @@ const noNativeMethodCall = defineRule({
|
|
|
1389
1407
|
meta: { type: "problem" }
|
|
1390
1408
|
});
|
|
1391
1409
|
//#endregion
|
|
1410
|
+
//#region src/rules/no-nullish-ternary.ts
|
|
1411
|
+
const nullishPredicates = [
|
|
1412
|
+
"isNotNull",
|
|
1413
|
+
"isNotNullish",
|
|
1414
|
+
"isNotUndefined",
|
|
1415
|
+
"isNull",
|
|
1416
|
+
"isNullish",
|
|
1417
|
+
"isUndefined"
|
|
1418
|
+
];
|
|
1419
|
+
function isNullish(input) {
|
|
1420
|
+
if (input.node.type === "Literal") return input.node.value === null;
|
|
1421
|
+
return input.node.type === "Identifier" && input.node.name === "undefined";
|
|
1422
|
+
}
|
|
1423
|
+
function testsNullish(input) {
|
|
1424
|
+
if (input.node.type === "UnaryExpression" && input.node.operator === "!") return testsNullish({
|
|
1425
|
+
context: input.context,
|
|
1426
|
+
node: input.node.argument
|
|
1427
|
+
});
|
|
1428
|
+
if (input.node.type === "BinaryExpression" && (input.node.operator === "===" || input.node.operator === "!==")) return isNullish({
|
|
1429
|
+
context: input.context,
|
|
1430
|
+
node: input.node.left
|
|
1431
|
+
}) || isNullish({
|
|
1432
|
+
context: input.context,
|
|
1433
|
+
node: input.node.right
|
|
1434
|
+
});
|
|
1435
|
+
return input.node.type === "CallExpression" && input.node.callee.type === "MemberExpression" && importedMember({
|
|
1436
|
+
context: input.context,
|
|
1437
|
+
importedName: "Predicate",
|
|
1438
|
+
node: input.node.callee
|
|
1439
|
+
}) && exists(memberName(input.node.callee), (name) => contains$1(nullishPredicates, name));
|
|
1440
|
+
}
|
|
1441
|
+
const noNullishTernary = defineRule({
|
|
1442
|
+
create: (context) => ({ ConditionalExpression: (node) => {
|
|
1443
|
+
if (testsNullish({
|
|
1444
|
+
context,
|
|
1445
|
+
node: node.test
|
|
1446
|
+
})) context.report({
|
|
1447
|
+
message: "Use ?? for a default, or Option for a value that may be absent, instead of a nullish ternary.",
|
|
1448
|
+
node
|
|
1449
|
+
});
|
|
1450
|
+
} }),
|
|
1451
|
+
meta: { type: "problem" }
|
|
1452
|
+
});
|
|
1453
|
+
//#endregion
|
|
1392
1454
|
//#region src/rules/no-option-value-access.ts
|
|
1393
1455
|
function checks(input) {
|
|
1394
1456
|
if (input.test.type === "LogicalExpression" && input.test.operator === input.operator) return checks({
|
|
@@ -1415,8 +1477,7 @@ function exits(node) {
|
|
|
1415
1477
|
function earlyExit(input) {
|
|
1416
1478
|
const parent = input.node.parent;
|
|
1417
1479
|
if (parent?.type !== "BlockStatement" && parent?.type !== "Program") return false;
|
|
1418
|
-
|
|
1419
|
-
return some$1(take(parent.body, getOrElse(index, () => 0)), (statement) => statement.type === "IfStatement" && exits(statement.consequent) && checks({
|
|
1480
|
+
return some$1(take(parent.body, getOrElse(findFirstIndex(parent.body, (statement) => statement === input.node), () => 0)), (statement) => statement.type === "IfStatement" && exits(statement.consequent) && checks({
|
|
1420
1481
|
...input,
|
|
1421
1482
|
name: "isNone",
|
|
1422
1483
|
operator: "||",
|
|
@@ -1620,11 +1681,11 @@ const noRedundantReturnType = defineRule({
|
|
|
1620
1681
|
function exit(node) {
|
|
1621
1682
|
const frame = last(get$3(frames));
|
|
1622
1683
|
update(frames, dropRight(1));
|
|
1623
|
-
if (node.async || node.generator || isNullish(node.returnType)) return;
|
|
1684
|
+
if (node.async || node.generator || isNullish$1(node.returnType)) return;
|
|
1624
1685
|
if (inferredIdentically({
|
|
1625
1686
|
context,
|
|
1626
1687
|
node,
|
|
1627
|
-
returns: match(frame, {
|
|
1688
|
+
returns: match$1(frame, {
|
|
1628
1689
|
onNone: () => empty(),
|
|
1629
1690
|
onSome: (top) => top.returns
|
|
1630
1691
|
}),
|
|
@@ -1642,7 +1703,7 @@ const noRedundantReturnType = defineRule({
|
|
|
1642
1703
|
FunctionExpression: enter,
|
|
1643
1704
|
"FunctionExpression:exit": exit,
|
|
1644
1705
|
ReturnStatement: (node) => {
|
|
1645
|
-
update(frames, (stack) => pipe(last(stack), match({
|
|
1706
|
+
update(frames, (stack) => pipe(last(stack), match$1({
|
|
1646
1707
|
onNone: () => stack,
|
|
1647
1708
|
onSome: (top) => append(dropRight(stack, 1), { returns: append(top.returns, node) })
|
|
1648
1709
|
})));
|
|
@@ -1714,7 +1775,7 @@ const noRedundantUseRefNullType = defineRule({
|
|
|
1714
1775
|
const noRedundantVariableAnnotation = defineRule({
|
|
1715
1776
|
create: (context) => ({ VariableDeclarator: (node) => {
|
|
1716
1777
|
const annotation = node.id.typeAnnotation;
|
|
1717
|
-
if (node.init === null || isNullish(annotation)) return;
|
|
1778
|
+
if (node.init === null || isNullish$1(annotation)) return;
|
|
1718
1779
|
if (node.init.type === "ArrayExpression" && isArrayEmpty(node.init.elements)) {
|
|
1719
1780
|
context.report({
|
|
1720
1781
|
message: "Write Array.empty<T>() instead of annotating an empty array.",
|
|
@@ -1781,7 +1842,10 @@ function shippedSchema(input) {
|
|
|
1781
1842
|
node: input.filter.callee
|
|
1782
1843
|
});
|
|
1783
1844
|
const [value, ...rest] = input.filter.arguments;
|
|
1784
|
-
return pipe(shipped, findFirst((entry) => contains(base, entry.base) && contains(check, entry.check) && isArrayEmpty(rest) && (entry.only
|
|
1845
|
+
return pipe(shipped, findFirst((entry) => contains(base, entry.base) && contains(check, entry.check) && isArrayEmpty(rest) && match$1(fromUndefinedOr(entry.only), {
|
|
1846
|
+
onNone: () => value === void 0,
|
|
1847
|
+
onSome: (only) => value?.type === "Literal" && value.value === only
|
|
1848
|
+
})), map((entry) => entry.schema));
|
|
1785
1849
|
}
|
|
1786
1850
|
function pipedBase(node) {
|
|
1787
1851
|
const parent = node.parent;
|
|
@@ -1835,6 +1899,104 @@ const noRenamedImport = defineRule({
|
|
|
1835
1899
|
meta: { type: "problem" }
|
|
1836
1900
|
});
|
|
1837
1901
|
//#endregion
|
|
1902
|
+
//#region src/rules/no-single-use-value.ts
|
|
1903
|
+
const repeating = [
|
|
1904
|
+
"ArrowFunctionExpression",
|
|
1905
|
+
"DoWhileStatement",
|
|
1906
|
+
"ForInStatement",
|
|
1907
|
+
"ForOfStatement",
|
|
1908
|
+
"ForStatement",
|
|
1909
|
+
"FunctionDeclaration",
|
|
1910
|
+
"FunctionExpression",
|
|
1911
|
+
"WhileStatement"
|
|
1912
|
+
];
|
|
1913
|
+
function runsOnce(input) {
|
|
1914
|
+
const node = input.node;
|
|
1915
|
+
const child = input.child;
|
|
1916
|
+
if ((node.type === "ForOfStatement" || node.type === "ForInStatement") && node.right === child || node.type === "IfStatement" && node.test === child || node.type === "ConditionalExpression" && node.test === child || node.type === "LogicalExpression" && node.left === child) return climb({
|
|
1917
|
+
...input,
|
|
1918
|
+
node
|
|
1919
|
+
});
|
|
1920
|
+
if (contains$1(repeating, node.type) || node.type === "IfStatement" || node.type === "ConditionalExpression" || node.type === "LogicalExpression" || node.type === "ChainExpression" || node.type === "SwitchStatement" || node.type === "TryStatement") return false;
|
|
1921
|
+
return climb({
|
|
1922
|
+
...input,
|
|
1923
|
+
node
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
function climb(input) {
|
|
1927
|
+
if (input.node === input.statement) return true;
|
|
1928
|
+
if (isNullish$1(input.node.parent)) return false;
|
|
1929
|
+
return runsOnce({
|
|
1930
|
+
child: input.node,
|
|
1931
|
+
node: input.node.parent,
|
|
1932
|
+
statement: input.statement
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
function insideConditional(input) {
|
|
1936
|
+
if (input.node === input.statement) return false;
|
|
1937
|
+
if (input.node.type === "ConditionalExpression") return true;
|
|
1938
|
+
return isNotNullish(input.node.parent) && insideConditional({
|
|
1939
|
+
...input,
|
|
1940
|
+
node: input.node.parent
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
function candidate(node) {
|
|
1944
|
+
const declaration = node.parent;
|
|
1945
|
+
const init = node.init;
|
|
1946
|
+
return declaration.type === "VariableDeclaration" && declaration.kind === "const" && declaration.declarations.length === 1 && declaration.parent.type === "BlockStatement" && node.id.type === "Identifier" && node.id.typeAnnotation === null && init !== null && !contains$1(["ArrowFunctionExpression", "FunctionExpression"], init.type) && !(init.type === "Literal" && !hasProperty(init, "regex")) && !(init.type === "TemplateLiteral" && isArrayEmpty(init.expressions));
|
|
1947
|
+
}
|
|
1948
|
+
function report(input) {
|
|
1949
|
+
const declaration = input.node.parent;
|
|
1950
|
+
const init = input.node.init;
|
|
1951
|
+
if (declaration.type !== "VariableDeclaration" || declaration.parent.type !== "BlockStatement" || input.node.id.type !== "Identifier" || init === null || pipe(input.context.sourceCode.getText(init), includes("\n"))) return;
|
|
1952
|
+
const statements = declaration.parent.body;
|
|
1953
|
+
const name = input.node.id.name;
|
|
1954
|
+
pipe(findFirstIndex(statements, (statement) => statement === declaration), flatMap((index) => get$1(statements, index + 1)), flatMap((next) => pipe(variableFromScope({
|
|
1955
|
+
name,
|
|
1956
|
+
scope: input.context.sourceCode.getScope(input.node)
|
|
1957
|
+
}), map((variable) => filter$1(variable.references, (reference) => reference.isRead())), filter((reads) => reads.length === 1), flatMap(head), map((reference) => reference.identifier), filter((read) => read.range[0] >= next.range[0] && read.range[1] <= next.range[1] && climb({
|
|
1958
|
+
node: read,
|
|
1959
|
+
statement: next
|
|
1960
|
+
}) && !(init.type === "ConditionalExpression" && insideConditional({
|
|
1961
|
+
node: read,
|
|
1962
|
+
statement: next
|
|
1963
|
+
})) && !some$1(input.effects, (effect) => effect.range[0] >= next.range[0] && effect.range[1] <= read.range[0])))), map(() => {
|
|
1964
|
+
input.context.report({
|
|
1965
|
+
message: "Inline this value into the next statement, its only use.",
|
|
1966
|
+
node: input.node
|
|
1967
|
+
});
|
|
1968
|
+
}));
|
|
1969
|
+
}
|
|
1970
|
+
const noSingleUseValue = defineRule({
|
|
1971
|
+
create: (context) => {
|
|
1972
|
+
const effects = make$2(empty());
|
|
1973
|
+
const candidates = make$2(empty());
|
|
1974
|
+
function effect(node) {
|
|
1975
|
+
update(effects, append(node));
|
|
1976
|
+
}
|
|
1977
|
+
return {
|
|
1978
|
+
AssignmentExpression: effect,
|
|
1979
|
+
AwaitExpression: effect,
|
|
1980
|
+
CallExpression: effect,
|
|
1981
|
+
NewExpression: effect,
|
|
1982
|
+
"Program:exit": () => {
|
|
1983
|
+
for (const node of get$3(candidates)) report({
|
|
1984
|
+
context,
|
|
1985
|
+
effects: get$3(effects),
|
|
1986
|
+
node
|
|
1987
|
+
});
|
|
1988
|
+
},
|
|
1989
|
+
TaggedTemplateExpression: effect,
|
|
1990
|
+
UpdateExpression: effect,
|
|
1991
|
+
VariableDeclarator: (node) => {
|
|
1992
|
+
if (candidate(node)) update(candidates, append(node));
|
|
1993
|
+
},
|
|
1994
|
+
YieldExpression: effect
|
|
1995
|
+
};
|
|
1996
|
+
},
|
|
1997
|
+
meta: { type: "problem" }
|
|
1998
|
+
});
|
|
1999
|
+
//#endregion
|
|
1838
2000
|
//#region src/rules/no-stored-schema-operation.ts
|
|
1839
2001
|
function storedSchemaOperation(node) {
|
|
1840
2002
|
return node.parent.type === "VariableDeclarator" && node.parent.init === node || node.parent.type === "Property" && node.parent.value === node || node.parent.type === "PropertyDefinition" && node.parent.value === node || node.parent.type === "ArrayExpression" || node.parent.type === "AssignmentExpression" && node.parent.right === node;
|
|
@@ -1879,7 +2041,7 @@ function forwardedCall(input) {
|
|
|
1879
2041
|
function exactForwardingFunction(node) {
|
|
1880
2042
|
const names = pipe(node.params, map$1(parameterName), getSomes);
|
|
1881
2043
|
const returned = returnedExpression(node);
|
|
1882
|
-
if (isArrayEmpty(names) || names.length !== node.params.length || isNullish(returned)) return false;
|
|
2044
|
+
if (isArrayEmpty(names) || names.length !== node.params.length || isNullish$1(returned)) return false;
|
|
1883
2045
|
if (returned.type === "Identifier") return names.length === 1 && returned.name === names[0];
|
|
1884
2046
|
return (returned.type === "CallExpression" || returned.type === "NewExpression") && returned.callee.type === "Identifier" && forwardedCall({
|
|
1885
2047
|
names,
|
|
@@ -1904,8 +2066,7 @@ function unexportedBindingName(node) {
|
|
|
1904
2066
|
}
|
|
1905
2067
|
function singleCallerWrapper(input) {
|
|
1906
2068
|
const names = pipe(input.node.params, map$1(parameterName), getSomes);
|
|
1907
|
-
|
|
1908
|
-
if (input.node.async || input.node.generator || input.node.returnType?.typeAnnotation.type === "TSTypePredicate" || isArrayEmpty(names) || names.length !== input.node.params.length || returned?.type !== "CallExpression") return false;
|
|
2069
|
+
if (input.node.async || input.node.generator || input.node.returnType?.typeAnnotation.type === "TSTypePredicate" || isArrayEmpty(names) || names.length !== input.node.params.length || returnedExpression(input.node)?.type !== "CallExpression") return false;
|
|
1909
2070
|
return pipe(unexportedBindingName(input.node), flatMap((name) => variableFromScope({
|
|
1910
2071
|
name,
|
|
1911
2072
|
scope: input.context.sourceCode.getScope(input.node)
|
|
@@ -2061,11 +2222,11 @@ const noUndestructuredUseState = defineRule({
|
|
|
2061
2222
|
//#region src/rules/no-unexplained-disable.ts
|
|
2062
2223
|
const noUnexplainedDisable = defineRule({
|
|
2063
2224
|
create: (context) => ({ Program: () => {
|
|
2064
|
-
for (const comment of context.sourceCode.getAllComments()) if (/^\s*(?:eslint|oxlint)-disable\S*\s(?:(?!\s--\s).)*\bsort-keys\b/u
|
|
2225
|
+
for (const comment of context.sourceCode.getAllComments()) if (matches(/^\s*(?:eslint|oxlint)-disable\S*\s(?:(?!\s--\s).)*\bsort-keys\b/u)(comment.value)) context.report({
|
|
2065
2226
|
message: "Sort the keys, starting a new sorted group after a blank line; never disable sort-keys.",
|
|
2066
2227
|
node: comment
|
|
2067
2228
|
});
|
|
2068
|
-
else if (/^\s*(?:eslint-disable|oxlint-disable|@effect-diagnostics)/u
|
|
2229
|
+
else if (matches(/^\s*(?:eslint-disable|oxlint-disable|@effect-diagnostics)/u)(comment.value) && !matches(/\s--\s*\S/u)(comment.value)) context.report({
|
|
2069
2230
|
message: "Give this disable a `-- reason`.",
|
|
2070
2231
|
node: comment
|
|
2071
2232
|
});
|
|
@@ -2142,7 +2303,7 @@ function schemaDefinitionMember(node) {
|
|
|
2142
2303
|
return none();
|
|
2143
2304
|
}
|
|
2144
2305
|
function isSchemaDefinitionName(name) {
|
|
2145
|
-
return !isSchemaOperationName(name) &&
|
|
2306
|
+
return !isSchemaOperationName(name) && !matches(/^(?:is|to)[A-Z]/u)(name) && !matches(/^makeFilter(?:Group)?$/u)(name);
|
|
2146
2307
|
}
|
|
2147
2308
|
function isSchemaDefinition(input) {
|
|
2148
2309
|
if (input.node.type === "CallExpression" && input.node.callee.type === "Identifier" && isImportBinding({
|
|
@@ -2181,7 +2342,7 @@ function namedTypeAlias(input) {
|
|
|
2181
2342
|
return pipe(input.typeStatement, flatMap(typeAlias), filter((declaration) => declaration.id.name === input.name));
|
|
2182
2343
|
}
|
|
2183
2344
|
function reportSchemaVariable(input) {
|
|
2184
|
-
if (input.variable.id.type !== "Identifier" ||
|
|
2345
|
+
if (input.variable.id.type !== "Identifier" || !matches(/^[A-Z]/u)(input.variable.id.name)) return;
|
|
2185
2346
|
if (input.variable.init === null || !isSchemaDefinition({
|
|
2186
2347
|
context: input.context,
|
|
2187
2348
|
node: input.variable.init
|
|
@@ -2355,6 +2516,7 @@ const oxlint = defineConfig({
|
|
|
2355
2516
|
"@deslop/coding-standards/no-let": "error",
|
|
2356
2517
|
"@deslop/coding-standards/no-native-emptiness-check": "error",
|
|
2357
2518
|
"@deslop/coding-standards/no-native-method-call": "error",
|
|
2519
|
+
"@deslop/coding-standards/no-nullish-ternary": "error",
|
|
2358
2520
|
"@deslop/coding-standards/no-option-value-access": "error",
|
|
2359
2521
|
"@deslop/coding-standards/no-readonly-type-syntax": "error",
|
|
2360
2522
|
"@deslop/coding-standards/no-redundant-return-type": "error",
|
|
@@ -2363,6 +2525,7 @@ const oxlint = defineConfig({
|
|
|
2363
2525
|
"@deslop/coding-standards/no-redundant-variable-annotation": "error",
|
|
2364
2526
|
"@deslop/coding-standards/no-reinvented-schema": "error",
|
|
2365
2527
|
"@deslop/coding-standards/no-renamed-import": "error",
|
|
2528
|
+
"@deslop/coding-standards/no-single-use-value": "error",
|
|
2366
2529
|
"@deslop/coding-standards/no-stored-schema-operation": "error",
|
|
2367
2530
|
"@deslop/coding-standards/no-switch-statement": "error",
|
|
2368
2531
|
"@deslop/coding-standards/no-trivial-indirection": "error",
|
|
@@ -2880,6 +3043,11 @@ const oxlint = defineConfig({
|
|
|
2880
3043
|
"no-unsafe-finally": "error",
|
|
2881
3044
|
"no-unsafe-optional-chaining": ["error", { disallowArithmeticOperators: true }],
|
|
2882
3045
|
"no-unused-expressions": "error",
|
|
3046
|
+
"no-unused-vars": ["error", {
|
|
3047
|
+
argsIgnorePattern: "^_",
|
|
3048
|
+
ignoreRestSiblings: true,
|
|
3049
|
+
reportUsedIgnorePattern: true
|
|
3050
|
+
}],
|
|
2883
3051
|
"no-useless-assignment": "error",
|
|
2884
3052
|
"no-useless-backreference": "error",
|
|
2885
3053
|
"no-useless-call": "error",
|
|
@@ -3046,6 +3214,7 @@ var oxlint_default = definePlugin({
|
|
|
3046
3214
|
"no-let": noLet,
|
|
3047
3215
|
"no-native-emptiness-check": noNativeEmptinessCheck,
|
|
3048
3216
|
"no-native-method-call": noNativeMethodCall,
|
|
3217
|
+
"no-nullish-ternary": noNullishTernary,
|
|
3049
3218
|
"no-option-value-access": noOptionValueAccess,
|
|
3050
3219
|
"no-readonly-type-syntax": noReadonlyTypeSyntax,
|
|
3051
3220
|
"no-redundant-return-type": noRedundantReturnType,
|
|
@@ -3054,6 +3223,7 @@ var oxlint_default = definePlugin({
|
|
|
3054
3223
|
"no-redundant-variable-annotation": noRedundantVariableAnnotation,
|
|
3055
3224
|
"no-reinvented-schema": noReinventedSchema,
|
|
3056
3225
|
"no-renamed-import": noRenamedImport,
|
|
3226
|
+
"no-single-use-value": noSingleUseValue,
|
|
3057
3227
|
"no-stored-schema-operation": noStoredSchemaOperation,
|
|
3058
3228
|
"no-switch-statement": noSwitchStatement,
|
|
3059
3229
|
"no-trivial-indirection": noTrivialIndirection,
|
package/package.json
CHANGED
package/skills/design/SKILL.md
CHANGED
|
@@ -73,8 +73,7 @@ Do not remove necessary names, status, comfortable hit targets or instructions m
|
|
|
73
73
|
| Accents/tall scripts clip | Tight line height or text-box clipping | Fix the owning type-scale/overflow choice; don't shrink the font to fit |
|
|
74
74
|
| Missing metadata leaves separators | Empty optional slots still render | Omit orphaned separators/lines; reserve space only when the layout needs it |
|
|
75
75
|
|
|
76
|
-
**
|
|
77
|
-
|
|
76
|
+
\1\n\n**Prevent before rejecting.** When the app already knows an action is not allowed, such as a missing permission, a referenced record or invalid input, disable or hide the control and show the reason beside it. Never let the user act and then show an error the app could have predicted.\n
|
|
78
77
|
**Motion.** Explain change without delaying input or moving content just for decoration. Reuse existing tokens/components; no new library for a fade or universal curve, duration, bounce or press scale.
|
|
79
78
|
|
|
80
79
|
| Interaction | Treatment |
|
|
@@ -100,7 +99,7 @@ Touch and mouse can coexist; do not infer capabilities from user-agent strings o
|
|
|
100
99
|
|
|
101
100
|
## Prototype
|
|
102
101
|
|
|
103
|
-
**Try the question.** Build one rough representative screen
|
|
102
|
+
**Try the question.** Build one rough representative screen or interaction in the existing host with realistic data. Inspect enough to make the trial trustworthy, then show it to the user before merge-readiness work. Label stubs and limits; a conceptual sketch is sufficient only when execution is unnecessary, never proof of working software. Set a short first-preview budget; show a partial result or blocker rather than expanding unseen. No production completeness or polished gallery first.
|
|
104
103
|
|
|
105
104
|
**Use the findings.** For layout questions, compare hierarchy or interaction, not color-only variants. Add attempts only for unresolved uncertainty, feedback or an explicit request—no fixed count; a style question can compare style. Keep useful comparisons; discard/rebuild freely and combine compatible strengths, not incompatible mechanisms. Test the weakest assumption while it can change the choice. A settled direction needs no manufactured taste question or exploration round.
|
|
106
105
|
|
|
@@ -108,10 +107,52 @@ Touch and mouse can coexist; do not infer capabilities from user-agent strings o
|
|
|
108
107
|
|
|
109
108
|
**Inspect retained UI.** Open actual captures at the sizes needed for the task. Compare related edges, columns, baselines and equivalent controls across rows/screens—not just successful clicks. Use realistic long names/URLs, translated labels, Unicode, missing fields and relevant 0/1/many records at the actual container width. Respect schema/API limits; never add a validation limit to make layout pass. Mix cases across rows. Check selected/focus/error states and scrolling; a clean default row can hide misalignment. Inspect hierarchy, density, legibility and clipping. Exercise the relevant keyboard/touch, narrow/wide, zoom and focus-return journeys; run axe where applicable. A clean scan does not prove contrast or usability. Inspect meaningful video frames and sequence; a playable file is not proof of a good composition. Batch relevant inspection/repairs; recheck affected views rather than restart a whole audit after each micro-edit. Visible misalignment, inconsistent controls or redundant elements remain defects even when types/tests pass; fix them before claiming acceptance.
|
|
110
109
|
|
|
111
|
-
**Show the design.** Use inspected screenshots for layout and short video for behavior. Compare at consistent sizes with states needed for the decision; no variant switcher just to see the options.
|
|
110
|
+
**Show the design.** Use inspected screenshots for layout and short video for behavior. Compare at consistent sizes with states needed for the decision; no variant switcher just to see the options. A visual must add spatial or interactive information, not reproduce prose, a file list or a fake runtime screenshot. Link useful existing evidence rather than render another explanation page. For diagrams, review pages and rendered answers, follow Visual explanations below. Use the user's theme and verify the actual browser color preference. Preserve useful media and retire settled attempts; persistent previews are opt-in.
|
|
112
111
|
|
|
113
112
|
**Review surfaces.** Open on the outcome the user needs to judge, with technical detail available on demand. Group by changed behavior, not files or agent stages. A selected example, diagram or diff must reveal its scope; never make curated evidence look like complete coverage. Do not build a custom review dashboard when existing captures and the PR can answer the question. A design-skill edit is not a product-screen change.
|
|
114
113
|
|
|
114
|
+
## Visual explanations
|
|
115
|
+
|
|
116
|
+
These rules cover diagrams, review pages and rendered agent answers. A visual earns its place only when it shows what text or a table cannot: space, flow, time, a visible comparison or a measurement.
|
|
117
|
+
|
|
118
|
+
**Pick the exhibit from the question.**
|
|
119
|
+
|
|
120
|
+
| The reader asks | Exhibit |
|
|
121
|
+
| ------------------------------------ | -------------------------------------------------------------- |
|
|
122
|
+
| What changed on screen? | Before and after pair with the same data, the old state left |
|
|
123
|
+
| Which transitions are allowed? | State machine with forbidden edges marked |
|
|
124
|
+
| What happens in order, and who acts? | Sequence in swimlanes, one lane per actor |
|
|
125
|
+
| Where did the time go? | Timeline with bars on one time axis |
|
|
126
|
+
| How much faster, bigger or cheaper? | Bars on a shared axis, values printed on the bars |
|
|
127
|
+
| Where exactly is the defect? | Annotated capture with numbered markers and short notes |
|
|
128
|
+
| Which requirement has which proof? | Coverage grid of requirements against proof, colored by state |
|
|
129
|
+
| Where does the change live? | Change map: a tree with sizes, added and removed parts colored |
|
|
130
|
+
| How do the parts relate? | Node and link map with few, labeled links |
|
|
131
|
+
| Which option is better? | Options side by side at the same size and with the same data |
|
|
132
|
+
| What will a video show? | Storyboard of chapter frames, the outcome frame marked |
|
|
133
|
+
|
|
134
|
+
When none fits, choose the form that matches the data's shape, not the first card layout that comes to mind. Draw a list as a list.
|
|
135
|
+
|
|
136
|
+
- **One message.** Each page answers one question. Its title states the conclusion, such as "The catalog loads 26 times faster", not the topic.
|
|
137
|
+
- **One dominant element.** Size, weight and position show importance. Mute the context and make secondary detail smaller.
|
|
138
|
+
- **Color means something.** Use the theme's status colors only for states and the chart colors only for series. Keep one accent, and never color for decoration.
|
|
139
|
+
- **Label directly.** Put names and values on the data instead of a distant legend. Show units, and use one scale per comparison.
|
|
140
|
+
- **Real content.** Use real names, numbers and captures. Show the scope, so a sample never looks like full coverage.
|
|
141
|
+
|
|
142
|
+
- **Grid and rhythm.** Align to one grid. Use a spacing scale of 4, 8, 12 and 16 pixels, two or three type sizes, and one corner radius.
|
|
143
|
+
- **Little text.** Labels and captions stay under twelve words. A visual holds no paragraphs.
|
|
144
|
+
- **Small multiples.** Compare several cases with repeated small exhibits on the same scale, rather than one crowded chart.
|
|
145
|
+
- **Both themes and widths.** Use the host's theme variables, check light and dark, and keep the page readable at phone width.
|
|
146
|
+
- **Inspect before showing.** Look at the rendered page for clipping, overlap, contrast and empty areas, and fix them first.
|
|
147
|
+
|
|
148
|
+
**Avoid** these:
|
|
149
|
+
|
|
150
|
+
- A table or bullet list redrawn as styled boxes.
|
|
151
|
+
- A card, badge or icon for every fact.
|
|
152
|
+
- Gradients, shadows and illustrations that carry no information.
|
|
153
|
+
- Crossing links, when an ordered list or lanes would show the same relation.
|
|
154
|
+
- A visual that repeats the text around it.
|
|
155
|
+
|
|
115
156
|
## Image assets
|
|
116
157
|
|
|
117
158
|
Use image generation only for a needed illustrative asset, atmosphere, texture or icon exploration—not ordinary text, exact diagrams or fake working UI. Use the native tool exposed by the harness; no invented invocation or replacement API client when unavailable. Report missing capability honestly.
|
|
@@ -54,7 +54,7 @@ Math.floor(Duration.toDays(DateTime.distance(oldest.timestamp, newest.timestamp)
|
|
|
54
54
|
import {glob} from 'glob' // a dependency Effect's FileSystem replaces
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
**Whole values.** Pass whole values or spread them. No parameter/callback/loop destructuring except labeled tuple parameters, useState and component ref; project fields only to exclude data the recipient must not receive.
|
|
57
|
+
**Whole values.** Pass whole values or spread them. No parameter/callback/loop destructuring except labeled tuple parameters, useState and component ref; project fields only to exclude data the recipient must not receive. Name the whole value by its role; a leading underscore marks only a parameter the body never reads, and no name gains an underscore or a number to dodge shadowing.
|
|
58
58
|
|
|
59
59
|
```ts
|
|
60
60
|
// good
|
|
@@ -62,11 +62,13 @@ server => ({server: {...server, forwardConsole: true, warmup}})
|
|
|
62
62
|
for (const schema of schemas) report(schema.name)
|
|
63
63
|
Record.map(groups, entries => entries.length)
|
|
64
64
|
upload({scope: input.scope, type: input.type}) // input also holds the bearer token
|
|
65
|
+
submission => onSubmit(submission.value)
|
|
65
66
|
// bad
|
|
66
67
|
({host, port}) => ({server: {forwardConsole: true, host, port, warmup}})
|
|
67
68
|
server: {forwardConsole: true, host: config.host, port: config.port, warmup}
|
|
68
69
|
for (const {name} of schemas) report(name)
|
|
69
70
|
Array.map(Record.toEntries(groups), ([key, entries]) => entries.length)
|
|
71
|
+
_input => onSubmit(_input.value) // or input2: an outer input is shadowed
|
|
70
72
|
```
|
|
71
73
|
|
|
72
74
|
**Nearest sibling.** Extend the nearest implementation; match permissions, errors, refresh and tests. Reuse its helpers and change only affected state.
|
package/skills/testing/SKILL.md
CHANGED
|
@@ -58,8 +58,9 @@ assert.strictEqual(Array.flatMap(attioProvider.groups, group => group.actions).l
|
|
|
58
58
|
|
|
59
59
|
## Run and reconcile
|
|
60
60
|
|
|
61
|
-
- **Run.** Use root check
|
|
61
|
+
- **Run.** Use root check commands, fix commands scoped to the files the change touches, and documented test selectors; confirm intended collection/execution. Zero tests/setup-only/exit0 alone is not proof; no invented final suite.
|
|
62
62
|
- **Inventory.** Collect independent failures once instead of cancelling the suite at the first failure. Distinguish separate cases from repeated observations. A timeout receives one isolated rerun of that same test; after a genuine pass, stop investigating it. Do not call a bypassed selector or a skipped operation flaky.
|
|
63
63
|
- **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.
|
|
64
64
|
- **Stable inputs.** No writer changes a running check's source, dependencies, configuration or build inputs. A relevant edit invalidates its prior result; rerun affected criteria and reuse unaffected evidence. Independent research or browser journeys may continue without mutating those inputs.
|
|
65
65
|
- **Final proof.** Reconcile every required behavior on the final code. Screenshots/video prove visible outcomes; public-seam execution proves nonvisual behavior; use fresh agents for changed instructions only when they can settle a consequential unresolved uncertainty, not routine wording edits. Keep the result and useful evidence, not disposable drivers, fixture installs, reports or preview services.
|
|
66
|
+
- **Visual proof.** A capture proves one claim and ends on its outcome, held long enough to read: the run, the file or the rejection. Seed data first, and keep loading states off camera unless loading is the claim. Show realistic journeys end to end, cropped to the element at a readable scale; speed up waits instead of cutting steps. A capture is stale once any component it shows changes.
|