@bfirestone45/opencode-slop-review 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,296 @@
1
+ # Rust AI Slop Signals
2
+
3
+ Language-specific signals for Rust codebases. These supplement the universal signals
4
+ in SKILL.md -- apply both.
5
+
6
+ ## What idiomatic Rust looks like
7
+
8
+ ### At version 1.94+ (Edition 2024)
9
+ - `async fn` in traits -- no more `async-trait` crate for simple cases
10
+ - `impl Trait` in more return positions
11
+ - `LazyLock`/`LazyCell` from stdlib over `once_cell`/`lazy_static` crates
12
+ - Let chains for cleaner pattern matching
13
+ - `#[must_use]` on fallible functions and important return values
14
+ - `#[diagnostic::on_unimplemented]` for better error messages on traits
15
+
16
+ ### Stdlib preferences
17
+ - Lazy init: `std::sync::LazyLock` over `once_cell::sync::Lazy`
18
+ - Error types: `thiserror` for library errors, `anyhow`/`eyre` for application errors
19
+ - Async runtime: `tokio` (check project convention)
20
+ - Serialization: `serde` with derive macros
21
+ - CLI: `clap` with derive macros
22
+
23
+ ### Error handling convention
24
+ - `?` propagation, never manual `match Ok/Err` for simple forwarding
25
+ - Typed error enums with `thiserror` in library code
26
+ - `anyhow::Result` acceptable in binary/CLI code, not libraries
27
+ - `expect()` only in `main()` or provably unreachable paths
28
+ - `unwrap()` only in tests
29
+
30
+ ### Type system usage
31
+ - Enums over bools for state/mode parameters
32
+ - Newtype pattern for domain values (UserId, OrderId)
33
+ - `From`/`Into` implementations for error conversion
34
+ - `Default` derive when obvious default exists
35
+ - Private fields with public constructors for invariant enforcement
36
+
37
+ ### Project adaptation
38
+ Before flagging any idiom violation, check if the project's baseline uses different conventions.
39
+
40
+ ---
41
+
42
+ ## Priority order for Rust
43
+
44
+ 1. Error handling -- unwrap/expect abuse, missing ? propagation
45
+ 2. Ownership and borrowing -- clone storms, RefCell overuse
46
+ 3. Type system usage -- enums vs. bools, newtypes, trait implementations
47
+ 4. Unsafe and panic safety -- library code that panics or uses unsafe carelessly
48
+ 5. Clippy and tooling -- suppressed warnings, unnecessary pub visibility
49
+
50
+ ---
51
+
52
+ ## Error handling (highest priority)
53
+
54
+ **unwrap() outside of tests:**
55
+ ```rust
56
+ // SLOP -- panics in production on any failure
57
+ let config = read_config().unwrap();
58
+ let conn = db.connect().unwrap();
59
+
60
+ // IDIOMATIC -- propagate with ?
61
+ let config = read_config()?;
62
+ let conn = db.connect().context("connecting to database")?;
63
+ ```
64
+
65
+ **expect() as a substitute for error propagation:**
66
+ ```rust
67
+ // SLOP -- slightly better than unwrap but still panics
68
+ let user = find_user(id).expect("user should exist");
69
+
70
+ // expect() is OK for genuinely unrecoverable cases in main() or bootstrap
71
+ // but not in library code or request handlers
72
+ ```
73
+
74
+ **Box<dyn Error> in library code:**
75
+ ```rust
76
+ // SLOP -- erases the error type, consumers cannot match on it
77
+ fn process(data: &[u8]) -> Result<Output, Box<dyn Error>> {
78
+
79
+ // IDIOMATIC -- typed error enum (thiserror)
80
+ #[derive(Debug, thiserror::Error)]
81
+ enum ProcessError {
82
+ #[error("invalid format: {0}")]
83
+ InvalidFormat(String),
84
+ #[error("io error")]
85
+ Io(#[from] std::io::Error),
86
+ }
87
+
88
+ fn process(data: &[u8]) -> Result<Output, ProcessError> {
89
+ ```
90
+
91
+ **Not using ? propagation:**
92
+ ```rust
93
+ // SLOP -- manual match on every Result
94
+ let data = match read_file(path) {
95
+ Ok(d) => d,
96
+ Err(e) => return Err(e.into()),
97
+ };
98
+
99
+ // IDIOMATIC
100
+ let data = read_file(path)?;
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Borrow checker fights
106
+
107
+ **Excessive .clone() -- the #1 Rust slop signal:**
108
+ ```rust
109
+ // SLOP -- cloning to avoid borrow checker errors
110
+ fn process(items: &[Item]) -> Vec<Output> {
111
+ let cloned = items.to_vec(); // unnecessary clone
112
+ cloned.iter().map(|item| {
113
+ let name = item.name.clone(); // another unnecessary clone
114
+ transform(name)
115
+ }).collect()
116
+ }
117
+ ```
118
+
119
+ When you see `.clone()` more than once or twice in a function, check whether
120
+ ownership could be restructured instead. Some clones are necessary and correct --
121
+ the signal is *pervasive* cloning as a pattern.
122
+
123
+ **Rc<RefCell<T>> when ownership could be restructured:**
124
+ ```rust
125
+ // SLOP -- interior mutability as a crutch
126
+ let shared_state = Rc::new(RefCell::new(State::new()));
127
+
128
+ // Often the real fix is restructuring so one owner holds the state
129
+ // and others get references or communicate via channels
130
+ ```
131
+
132
+ Note: with Edition 2024, `async fn` in traits eliminates some cases where
133
+ `Rc<RefCell<T>>` was used as a workaround for trait object limitations in
134
+ async code. If you see this pattern in async trait implementations, check
135
+ whether native `async fn` in traits makes it unnecessary.
136
+
137
+ **Arc<Mutex<T>> overuse:**
138
+ Same pattern as Rc<RefCell<T>> but in async or threaded code. If every piece of
139
+ shared state is behind Arc<Mutex<T>>, the author likely fought the borrow checker
140
+ rather than designing for ownership.
141
+
142
+ ---
143
+
144
+ ## Type system underuse
145
+
146
+ **Bool flags instead of enums:**
147
+ ```rust
148
+ // SLOP -- what does (true, false) mean?
149
+ fn create_user(name: &str, is_admin: bool, is_active: bool) -> User {
150
+
151
+ // IDIOMATIC -- illegal states unrepresentable
152
+ enum Role { Admin, User }
153
+ enum Status { Active, Inactive }
154
+ fn create_user(name: &str, role: Role, status: Status) -> User {
155
+ ```
156
+
157
+ **Stringly-typed state:**
158
+ ```rust
159
+ // SLOP
160
+ fn set_status(status: &str) {
161
+ match status {
162
+ "active" | "inactive" | "pending" => { ... }
163
+ _ => panic!("invalid status"), // runtime error for a compile-time problem
164
+ }
165
+ }
166
+
167
+ // IDIOMATIC
168
+ enum Status { Active, Inactive, Pending }
169
+ fn set_status(status: Status) { ... }
170
+ ```
171
+
172
+ **Missing standard trait implementations:**
173
+ - `Debug` derived but `Display` not implemented (users see `MyError { kind: ... }` instead of a message)
174
+ - No `From`/`Into` implementations for error types (forces manual conversion everywhere)
175
+ - No `Default` when there is an obvious default configuration
176
+ - Missing `Clone`, `PartialEq`, `Hash` on types that clearly need them
177
+
178
+ **Raw primitives for domain values:**
179
+ ```rust
180
+ // SLOP -- easy to pass user_id where order_id is expected
181
+ fn get_order(user_id: u64, order_id: u64) -> Order {
182
+
183
+ // IDIOMATIC -- newtype pattern
184
+ struct UserId(u64);
185
+ struct OrderId(u64);
186
+ fn get_order(user_id: UserId, order_id: OrderId) -> Order {
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Unsafe and panic safety
192
+
193
+ **Unsafe without SAFETY comment:**
194
+ ```rust
195
+ // SLOP
196
+ unsafe {
197
+ ptr::write(dest, value);
198
+ }
199
+
200
+ // REQUIRED
201
+ // SAFETY: dest is guaranteed to be valid and aligned because
202
+ // it was allocated by Vec::with_capacity and index < len.
203
+ unsafe {
204
+ ptr::write(dest, value);
205
+ }
206
+ ```
207
+
208
+ **Panicking in library code:**
209
+ - `panic!()`, `unreachable!()`, `todo!()` in non-test code
210
+ - `.unwrap()` on user-influenced data paths
211
+ - `assert!()` for input validation (use `Result` instead)
212
+ - Array indexing without bounds checks where the index comes from external input
213
+
214
+ **transmute without justification:**
215
+ `std::mem::transmute` should be extremely rare and always documented. If AI generated
216
+ a transmute, it almost certainly found a Stack Overflow answer from 2016 and
217
+ copy-pasted it.
218
+
219
+ ---
220
+
221
+ ## Build and tooling signals
222
+
223
+ **`lazy_static!` or `once_cell` for lazy initialization (Edition 2024+):**
224
+ ```rust
225
+ // SLOP -- unnecessary third-party crate since Rust 1.80+
226
+ use lazy_static::lazy_static;
227
+ lazy_static! {
228
+ static ref CONFIG: Config = load_config();
229
+ }
230
+
231
+ // SLOP -- same issue with once_cell
232
+ use once_cell::sync::Lazy;
233
+ static CONFIG: Lazy<Config> = Lazy::new(|| load_config());
234
+
235
+ // IDIOMATIC -- stdlib LazyLock (stable since 1.80, preferred in Edition 2024)
236
+ use std::sync::LazyLock;
237
+ static CONFIG: LazyLock<Config> = LazyLock::new(|| load_config());
238
+ ```
239
+
240
+ If you see `lazy_static` or `once_cell::sync::Lazy` in new code targeting
241
+ Rust 1.80+, flag it. For `once_cell::sync::OnceCell`, the stdlib equivalent
242
+ is `std::sync::OnceLock`. Existing projects may still use the crates for MSRV
243
+ reasons -- check `Cargo.toml` for `rust-version` before flagging.
244
+
245
+ **`async-trait` crate in Edition 2024 code:**
246
+ ```rust
247
+ // SLOP -- unnecessary with native async fn in traits (Edition 2024)
248
+ #[async_trait]
249
+ trait MyService {
250
+ async fn handle(&self) -> Result<()>;
251
+ }
252
+
253
+ // IDIOMATIC -- native async fn in traits
254
+ trait MyService {
255
+ async fn handle(&self) -> Result<()>;
256
+ }
257
+ ```
258
+
259
+ The `async-trait` crate is still needed for `dyn Trait` usage or when trait
260
+ objects require `Send` bounds that native async traits do not yet handle well.
261
+ Flag it only when the trait is used with static dispatch (generics/`impl Trait`).
262
+
263
+ **Suppressed warnings:**
264
+ ```rust
265
+ // SLOP -- hiding problems instead of fixing them
266
+ #[allow(unused_variables)]
267
+ #[allow(dead_code)]
268
+ #[allow(unused_imports)]
269
+ ```
270
+
271
+ A few `#[allow]` annotations are normal. Dozens scattered across a file means the
272
+ code was generated and then warnings were suppressed to make it compile.
273
+
274
+ **Over-broad pub visibility:**
275
+ ```rust
276
+ // SLOP -- everything public for no reason
277
+ pub struct Config {
278
+ pub db_url: String,
279
+ pub db_pool_size: u32,
280
+ pub secret_key: String, // this should NOT be pub
281
+ }
282
+ ```
283
+
284
+ In idiomatic Rust, items are private by default and only made `pub` when needed
285
+ by the module's public API. If every struct, field, function, and module is `pub`,
286
+ it is likely AI-generated with no thought about encapsulation.
287
+
288
+ ---
289
+
290
+ ## Rust-specific test signals
291
+
292
+ - No `#[should_panic]` or explicit error matching on tests for error paths
293
+ - `assert!(result.is_ok())` instead of `let value = result.unwrap()` + assertions on value
294
+ - Tests that do not use `#[test]` helper patterns (common setup extracted to functions)
295
+ - Integration tests in `src/` instead of `tests/` directory
296
+ - No `proptest` or `quickcheck` where the function has clear algebraic properties
@@ -0,0 +1,384 @@
1
+ # Svelte / TypeScript AI Slop Signals
2
+
3
+ Language-specific signals for Svelte and TypeScript codebases. These supplement the
4
+ universal signals in SKILL.md -- apply both.
5
+
6
+ ## What idiomatic Svelte/TypeScript looks like
7
+
8
+ ### Svelte 24.x LTS (Svelte 5)
9
+ - Runes: `$state`, `$derived`, `$effect` -- no more `$:` reactive declarations
10
+ - `$props()` with typed interface -- no more `export let`
11
+ - `$bindable()` for two-way binding props
12
+ - Snippets over slots for content composition
13
+ - `$inspect()` for debugging (dev-only)
14
+ - Fine-grained reactivity -- no more immutable assignment patterns needed for arrays/objects
15
+
16
+ ### TypeScript 5.9+
17
+ - `satisfies` operator for type narrowing without widening
18
+ - `using` declarations for resource management (Explicit Resource Management)
19
+ - `const` type parameters for literal inference
20
+ - `NoInfer<T>` utility type
21
+ - Discriminated unions over optional fields
22
+ - `readonly` on config/prop interfaces
23
+ - Template literal types for string validation
24
+
25
+ ### SvelteKit patterns
26
+ - `+page.server.ts` load functions over `onMount` fetching
27
+ - Form actions over manual fetch POST
28
+ - `$page.data` for layout-shared data
29
+ - SvelteKit's `fetch` (not axios/node-fetch) -- handles cookies, SSR, relative URLs
30
+ - `$env/static/private` for secrets (never in `+page.ts`)
31
+
32
+ ### Project adaptation
33
+ Before flagging any idiom violation, check the project's baseline conventions.
34
+
35
+ ## Priority order for Svelte/TypeScript
36
+
37
+ 1. TypeScript type safety -- any abuse, type assertions, missing discriminated unions
38
+ 2. Svelte reactivity model -- correct reactive updates, cleanup, binding usage
39
+ 3. SvelteKit patterns -- load functions, form actions, data flow
40
+ 4. Component design -- prop drilling, store usage, composition
41
+ 5. Security -- XSS via {@html}, client-side secrets
42
+
43
+ ---
44
+
45
+ ## TypeScript misuse
46
+
47
+ **`any` as an escape hatch:**
48
+ ```typescript
49
+ // SLOP -- gives up type safety entirely
50
+ const response: any = await fetch('/api/users');
51
+ const data: any = await response.json();
52
+
53
+ // IDIOMATIC -- define the shape
54
+ interface User {
55
+ id: string;
56
+ name: string;
57
+ email: string;
58
+ }
59
+ const response = await fetch('/api/users');
60
+ const data: User[] = await response.json();
61
+
62
+ // EVEN BETTER -- runtime validation
63
+ const data = UserArraySchema.parse(await response.json());
64
+ ```
65
+
66
+ **@ts-ignore without explanation:**
67
+ ```typescript
68
+ // SLOP
69
+ // @ts-ignore
70
+ const result = thing.method();
71
+
72
+ // IF NECESSARY -- explain why
73
+ // @ts-expect-error: library types are wrong, see https://github.com/lib/issues/123
74
+ const result = thing.method();
75
+ ```
76
+
77
+ **Type assertions without runtime validation:**
78
+ ```typescript
79
+ // SLOP -- trusts external data blindly
80
+ const user = JSON.parse(body) as User;
81
+
82
+ // IDIOMATIC -- validate at the boundary
83
+ const user = userSchema.parse(JSON.parse(body));
84
+ ```
85
+
86
+ **Optionals everywhere instead of discriminated unions:**
87
+ ```typescript
88
+ // SLOP -- which combinations are valid? Who knows
89
+ interface ApiResponse {
90
+ data?: User[];
91
+ error?: string;
92
+ loading?: boolean;
93
+ total?: number;
94
+ }
95
+
96
+ // IDIOMATIC -- each state is explicit
97
+ type ApiResponse =
98
+ | { status: 'loading' }
99
+ | { status: 'error'; error: string }
100
+ | { status: 'success'; data: User[]; total: number };
101
+ ```
102
+
103
+ **Missing readonly:**
104
+ ```typescript
105
+ // SLOP -- props and config objects should be immutable
106
+ interface Config {
107
+ apiUrl: string;
108
+ timeout: number;
109
+ }
110
+
111
+ // IDIOMATIC
112
+ interface Config {
113
+ readonly apiUrl: string;
114
+ readonly timeout: number;
115
+ }
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Svelte reactivity model violations
121
+
122
+ **Using Svelte 4 reactive declarations instead of runes:**
123
+ ```svelte
124
+ <script lang="ts">
125
+ // SLOP -- Svelte 4 syntax, no longer idiomatic
126
+ export let count = 0;
127
+ $: doubled = count * 2;
128
+ $: if (count > 10) {
129
+ console.log('count is big');
130
+ }
131
+
132
+ // IDIOMATIC -- Svelte 5 runes
133
+ interface Props {
134
+ count: number;
135
+ }
136
+ let { count }: Props = $props();
137
+ let doubled = $derived(count * 2);
138
+ $effect(() => {
139
+ if (count > 10) {
140
+ console.log('count is big');
141
+ }
142
+ });
143
+ </script>
144
+ ```
145
+
146
+ **Imperative mutation instead of reactive state:**
147
+ ```svelte
148
+ <script lang="ts">
149
+ // SLOP -- using $state but mutating without Svelte detecting it
150
+ let items: string[] = $state([]);
151
+ function addItem(item: string) {
152
+ // In Svelte 5, $state makes arrays/objects deeply reactive,
153
+ // so .push() DOES work. But creating new references is still
154
+ // clearer for complex transformations.
155
+ items = [...items, item];
156
+ }
157
+ </script>
158
+ ```
159
+
160
+ **Missing $effect cleanup:**
161
+ ```svelte
162
+ <script lang="ts">
163
+ // SLOP -- interval never cleared, memory leak
164
+ $effect(() => {
165
+ setInterval(pollData, 5000);
166
+ });
167
+
168
+ // IDIOMATIC -- $effect return value is the cleanup function
169
+ $effect(() => {
170
+ const interval = setInterval(pollData, 5000);
171
+ return () => clearInterval(interval);
172
+ });
173
+ </script>
174
+ ```
175
+
176
+ **Direct DOM manipulation bypassing Svelte:**
177
+ ```svelte
178
+ <script lang="ts">
179
+ // SLOP -- fighting the framework
180
+ import { onMount } from 'svelte';
181
+ onMount(() => {
182
+ document.querySelector('.header')?.classList.add('active');
183
+ });
184
+
185
+ // IDIOMATIC -- use Svelte bindings and class directives
186
+ let isActive = $state(false);
187
+ </script>
188
+ <div class="header" class:active={isActive}>
189
+ ```
190
+
191
+ **Missing UI states -- the classic AI tell:**
192
+ ```svelte
193
+ <!-- SLOP -- only shows data, no loading/error/empty states -->
194
+ {#if data}
195
+ {#each data as item}
196
+ <Card {item} />
197
+ {/each}
198
+ {/if}
199
+
200
+ <!-- IDIOMATIC -- handles all states -->
201
+ {#if loading}
202
+ <Spinner />
203
+ {:else if error}
204
+ <ErrorMessage {error} />
205
+ {:else if data.length === 0}
206
+ <EmptyState />
207
+ {:else}
208
+ {#each data as item}
209
+ <Card {item} />
210
+ {/each}
211
+ {/if}
212
+ ```
213
+
214
+ ---
215
+
216
+ ## SvelteKit patterns
217
+
218
+ **Data fetching in onMount instead of load function:**
219
+ ```svelte
220
+ <script lang="ts">
221
+ // SLOP -- data fetched client-side only, no SSR, no loading state from SvelteKit
222
+ import { onMount } from 'svelte';
223
+ let users: User[] = $state([]);
224
+ onMount(async () => {
225
+ const res = await fetch('/api/users');
226
+ users = await res.json();
227
+ });
228
+ </script>
229
+ ```
230
+
231
+ ```typescript
232
+ // IDIOMATIC -- in +page.ts or +page.server.ts
233
+ export const load = async ({ fetch }) => {
234
+ const res = await fetch('/api/users');
235
+ return { users: await res.json() };
236
+ };
237
+ ```
238
+
239
+ **Not using SvelteKit's fetch:**
240
+ ```typescript
241
+ // SLOP -- loses cookie forwarding, SSR compatibility
242
+ import axios from 'axios';
243
+ const data = await axios.get('/api/data');
244
+
245
+ // IDIOMATIC -- SvelteKit's fetch handles cookies, SSR, and relative URLs
246
+ export const load = async ({ fetch }) => {
247
+ const res = await fetch('/api/data');
248
+ return { data: await res.json() };
249
+ };
250
+ ```
251
+
252
+ **Manual form handling instead of form actions:**
253
+ ```svelte
254
+ <!-- SLOP -- reinventing what SvelteKit provides, using Svelte 4 event syntax -->
255
+ <form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
256
+ <input bind:value={name} />
257
+ <button>Submit</button>
258
+ </form>
259
+
260
+ <script lang="ts">
261
+ let name = $state('');
262
+ async function handleSubmit() {
263
+ await fetch('/api/users', {
264
+ method: 'POST',
265
+ body: JSON.stringify({ name }),
266
+ });
267
+ }
268
+ </script>
269
+ ```
270
+
271
+ ```typescript
272
+ // IDIOMATIC -- in +page.server.ts
273
+ export const actions = {
274
+ default: async ({ request }) => {
275
+ const data = await request.formData();
276
+ const name = data.get('name');
277
+ // validate and save
278
+ }
279
+ };
280
+ ```
281
+
282
+ **Ignoring layout data and page stores:**
283
+ - Not using `$page.data` for shared data from layout load functions
284
+ - Not using `$navigating` for navigation state
285
+ - Duplicating data fetching that a parent layout already provides
286
+
287
+ ---
288
+
289
+ ## Component design
290
+
291
+ **Prop drilling (4+ levels):**
292
+ ```svelte
293
+ <!-- SLOP -- passing theme through every intermediate component -->
294
+ <App {theme}>
295
+ <Layout {theme}>
296
+ <Sidebar {theme}>
297
+ <NavItem {theme} />
298
+
299
+ <!-- IDIOMATIC -- use Svelte context -->
300
+ <!-- App.svelte -->
301
+ <script lang="ts">
302
+ import { setContext } from 'svelte';
303
+ setContext('theme', theme);
304
+ </script>
305
+
306
+ <!-- NavItem.svelte -->
307
+ <script lang="ts">
308
+ import { getContext } from 'svelte';
309
+ const theme = getContext<Theme>('theme');
310
+ </script>
311
+ ```
312
+
313
+ **One-off component duplication:**
314
+ If a component exists that does 90% of what is needed, and AI created a new component
315
+ with 90% identical code plus a small variation -- that is slop. The existing component
316
+ should be extended with a prop or snippet.
317
+
318
+ **Untyped props and events:**
319
+ ```svelte
320
+ <script lang="ts">
321
+ // SLOP -- no types on props
322
+ let { data, onSubmit } = $props();
323
+
324
+ // IDIOMATIC -- typed interface with $props()
325
+ interface Props {
326
+ readonly data: User;
327
+ onSubmit: (user: User) => void;
328
+ }
329
+ let { data, onSubmit }: Props = $props();
330
+ </script>
331
+ ```
332
+
333
+ **Using `export let` (Svelte 4 legacy):**
334
+ ```svelte
335
+ <script lang="ts">
336
+ // SLOP -- Svelte 4 prop declaration, no longer idiomatic
337
+ export let user: User;
338
+ export let onSave: (user: User) => void;
339
+
340
+ // IDIOMATIC -- Svelte 5 $props() with typed interface
341
+ interface Props {
342
+ readonly user: User;
343
+ onSave: (user: User) => void;
344
+ }
345
+ let { user, onSave }: Props = $props();
346
+ </script>
347
+ ```
348
+
349
+ ---
350
+
351
+ ## Security signals
352
+
353
+ **XSS via {@html}:**
354
+ ```svelte
355
+ <!-- CRITICAL -- user content rendered as raw HTML -->
356
+ {@html userComment}
357
+
358
+ <!-- SAFE -- Svelte auto-escapes by default in text content -->
359
+ {userComment}
360
+ ```
361
+
362
+ **Client-side secrets:**
363
+ ```typescript
364
+ // SLOP -- exposed to the browser
365
+ const API_KEY = 'sk-1234567890';
366
+
367
+ // IDIOMATIC -- server-side only, in +page.server.ts or via $env/static/private
368
+ import { API_KEY } from '$env/static/private';
369
+ ```
370
+
371
+ - Any `import` from `$env/static/private` in a `+page.ts` (non-server) file
372
+ - API keys in `.env` without the `PUBLIC_` prefix that are used client-side
373
+ - Sensitive data in `+page.ts` load functions instead of `+page.server.ts`
374
+
375
+ ---
376
+
377
+ ## TypeScript-specific test signals
378
+
379
+ - Tests that use `as any` to bypass type errors instead of providing proper test data
380
+ - No `vitest` or `playwright` in a SvelteKit project (the default test runners)
381
+ - Component tests that only check rendering, not interaction or state changes
382
+ - Missing `@testing-library/svelte` patterns -- testing implementation details instead of behavior
383
+ - No type-level tests for discriminated unions and conditional types
384
+ - E2E tests that use `data-testid` on everything instead of accessible selectors
package/version.txt ADDED
@@ -0,0 +1 @@
1
+ 0.5.0