@coralai/sps-cli 0.41.2 → 0.43.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.
Files changed (168) hide show
  1. package/README.md +34 -3
  2. package/dist/commands/cardAdd.d.ts +1 -1
  3. package/dist/commands/cardAdd.d.ts.map +1 -1
  4. package/dist/commands/cardAdd.js +16 -6
  5. package/dist/commands/cardAdd.js.map +1 -1
  6. package/dist/commands/cardDashboard.js +1 -1
  7. package/dist/commands/cardDashboard.js.map +1 -1
  8. package/dist/commands/doctor.d.ts +9 -0
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/doctor.js +3 -314
  11. package/dist/commands/doctor.js.map +1 -1
  12. package/dist/commands/hookCommand.d.ts.map +1 -1
  13. package/dist/commands/hookCommand.js +6 -7
  14. package/dist/commands/hookCommand.js.map +1 -1
  15. package/dist/commands/pmCommand.js +1 -1
  16. package/dist/commands/pmCommand.js.map +1 -1
  17. package/dist/commands/projectInit.d.ts.map +1 -1
  18. package/dist/commands/projectInit.js +60 -37
  19. package/dist/commands/projectInit.js.map +1 -1
  20. package/dist/commands/setup.d.ts.map +1 -1
  21. package/dist/commands/setup.js +3 -30
  22. package/dist/commands/setup.js.map +1 -1
  23. package/dist/commands/skillCommand.d.ts +2 -0
  24. package/dist/commands/skillCommand.d.ts.map +1 -0
  25. package/dist/commands/skillCommand.js +235 -0
  26. package/dist/commands/skillCommand.js.map +1 -0
  27. package/dist/commands/tick.js +1 -1
  28. package/dist/commands/tick.js.map +1 -1
  29. package/dist/core/checklist.d.ts +22 -0
  30. package/dist/core/checklist.d.ts.map +1 -0
  31. package/dist/core/checklist.js +38 -0
  32. package/dist/core/checklist.js.map +1 -0
  33. package/dist/core/checklist.test.d.ts +2 -0
  34. package/dist/core/checklist.test.d.ts.map +1 -0
  35. package/dist/core/checklist.test.js +74 -0
  36. package/dist/core/checklist.test.js.map +1 -0
  37. package/dist/core/config.d.ts +1 -1
  38. package/dist/core/config.d.ts.map +1 -1
  39. package/dist/core/config.js +1 -1
  40. package/dist/core/config.js.map +1 -1
  41. package/dist/core/config.test.js +7 -4
  42. package/dist/core/config.test.js.map +1 -1
  43. package/dist/core/context.d.ts +1 -1
  44. package/dist/core/context.d.ts.map +1 -1
  45. package/dist/core/skillStore.d.ts +46 -0
  46. package/dist/core/skillStore.d.ts.map +1 -0
  47. package/dist/core/skillStore.js +197 -0
  48. package/dist/core/skillStore.js.map +1 -0
  49. package/dist/core/skillStore.test.d.ts +2 -0
  50. package/dist/core/skillStore.test.d.ts.map +1 -0
  51. package/dist/core/skillStore.test.js +190 -0
  52. package/dist/core/skillStore.test.js.map +1 -0
  53. package/dist/engines/EventHandler.test.js +3 -3
  54. package/dist/engines/EventHandler.test.js.map +1 -1
  55. package/dist/engines/MonitorEngine.js +2 -2
  56. package/dist/engines/MonitorEngine.js.map +1 -1
  57. package/dist/engines/SchedulerEngine.js +1 -1
  58. package/dist/engines/SchedulerEngine.js.map +1 -1
  59. package/dist/engines/StageEngine.js +3 -3
  60. package/dist/engines/StageEngine.js.map +1 -1
  61. package/dist/engines/engine-pipeline-adapter.test.js +2 -2
  62. package/dist/engines/engine-pipeline-adapter.test.js.map +1 -1
  63. package/dist/interfaces/TaskBackend.d.ts +3 -1
  64. package/dist/interfaces/TaskBackend.d.ts.map +1 -1
  65. package/dist/main.js +19 -17
  66. package/dist/main.js.map +1 -1
  67. package/dist/models/types.d.ts +16 -1
  68. package/dist/models/types.d.ts.map +1 -1
  69. package/dist/providers/MarkdownTaskBackend.d.ts +2 -1
  70. package/dist/providers/MarkdownTaskBackend.d.ts.map +1 -1
  71. package/dist/providers/MarkdownTaskBackend.js +28 -5
  72. package/dist/providers/MarkdownTaskBackend.js.map +1 -1
  73. package/dist/providers/registry.d.ts.map +1 -1
  74. package/dist/providers/registry.js +5 -7
  75. package/dist/providers/registry.js.map +1 -1
  76. package/package.json +1 -1
  77. package/project-template/.claude/hooks/start.sh +44 -0
  78. package/project-template/.claude/settings.json +1 -1
  79. package/skills/architecture-decision-records/SKILL.md +207 -0
  80. package/skills/backend/SKILL.md +62 -0
  81. package/skills/backend/references/api-design.md +168 -0
  82. package/skills/backend/references/caching.md +181 -0
  83. package/skills/backend/references/data-access.md +173 -0
  84. package/skills/backend/references/layering.md +181 -0
  85. package/skills/backend/references/observability.md +190 -0
  86. package/skills/backend/references/resilience.md +201 -0
  87. package/skills/backend/references/security.md +186 -0
  88. package/skills/backend-architect/SKILL.md +119 -0
  89. package/skills/code-reviewer/SKILL.md +143 -0
  90. package/skills/coding-standards/SKILL.md +60 -0
  91. package/skills/coding-standards/references/clean-code.md +258 -0
  92. package/skills/coding-standards/references/code-review.md +192 -0
  93. package/skills/coding-standards/references/commits-and-prs.md +226 -0
  94. package/skills/coding-standards/references/error-strategy.md +193 -0
  95. package/skills/coding-standards/references/naming.md +185 -0
  96. package/skills/coding-standards/references/tdd.md +171 -0
  97. package/skills/database/SKILL.md +53 -0
  98. package/skills/database/references/indexing.md +190 -0
  99. package/skills/database/references/migrations.md +199 -0
  100. package/skills/database/references/nosql.md +185 -0
  101. package/skills/database/references/queries.md +295 -0
  102. package/skills/database/references/scaling.md +203 -0
  103. package/skills/database/references/schema.md +191 -0
  104. package/skills/database-optimizer/SKILL.md +168 -0
  105. package/skills/debugging-workflow/SKILL.md +244 -0
  106. package/skills/devops/SKILL.md +55 -0
  107. package/skills/devops/references/ci-cd.md +204 -0
  108. package/skills/devops/references/containers.md +272 -0
  109. package/skills/devops/references/deploy.md +201 -0
  110. package/skills/devops/references/iac.md +252 -0
  111. package/skills/devops/references/observability.md +228 -0
  112. package/skills/devops/references/secrets.md +178 -0
  113. package/skills/devops-automator/SKILL.md +164 -0
  114. package/skills/frontend/SKILL.md +52 -0
  115. package/skills/frontend/references/accessibility.md +222 -0
  116. package/skills/frontend/references/components.md +206 -0
  117. package/skills/frontend/references/performance.md +219 -0
  118. package/skills/frontend/references/routing.md +209 -0
  119. package/skills/frontend/references/state.md +190 -0
  120. package/skills/frontend/references/testing.md +216 -0
  121. package/skills/frontend-developer/SKILL.md +115 -0
  122. package/skills/git-workflow/SKILL.md +355 -0
  123. package/skills/golang/SKILL.md +49 -0
  124. package/skills/golang/references/concurrency.md +284 -0
  125. package/skills/golang/references/errors.md +241 -0
  126. package/skills/golang/references/idioms.md +285 -0
  127. package/skills/golang/references/testing.md +238 -0
  128. package/skills/java/SKILL.md +50 -0
  129. package/skills/java/references/concurrency.md +194 -0
  130. package/skills/java/references/idioms.md +283 -0
  131. package/skills/java/references/testing.md +228 -0
  132. package/skills/kotlin/SKILL.md +47 -0
  133. package/skills/kotlin/references/coroutines.md +240 -0
  134. package/skills/kotlin/references/idioms.md +268 -0
  135. package/skills/kotlin/references/testing.md +219 -0
  136. package/skills/mobile/SKILL.md +50 -0
  137. package/skills/mobile/references/architecture.md +204 -0
  138. package/skills/mobile/references/navigation.md +158 -0
  139. package/skills/mobile/references/performance.md +152 -0
  140. package/skills/mobile/references/platform.md +166 -0
  141. package/skills/mobile/references/state-and-data.md +174 -0
  142. package/skills/python/SKILL.md +51 -0
  143. package/skills/python/THIRD_PARTY.md +14 -0
  144. package/skills/python/references/async.md +218 -0
  145. package/skills/python/references/error-handling.md +254 -0
  146. package/skills/python/references/idioms.md +279 -0
  147. package/skills/python/references/packaging.md +233 -0
  148. package/skills/python/references/testing.md +269 -0
  149. package/skills/python/references/typing.md +292 -0
  150. package/skills/qa-tester/SKILL.md +186 -0
  151. package/skills/rust/SKILL.md +50 -0
  152. package/skills/rust/references/async.md +224 -0
  153. package/skills/rust/references/errors.md +240 -0
  154. package/skills/rust/references/ownership.md +263 -0
  155. package/skills/rust/references/testing.md +274 -0
  156. package/skills/rust/references/traits.md +250 -0
  157. package/skills/security-engineer/SKILL.md +157 -0
  158. package/skills/swift/SKILL.md +48 -0
  159. package/skills/swift/references/concurrency.md +280 -0
  160. package/skills/swift/references/idioms.md +334 -0
  161. package/skills/swift/references/testing.md +229 -0
  162. package/skills/typescript/SKILL.md +51 -0
  163. package/skills/typescript/references/async.md +241 -0
  164. package/skills/typescript/references/errors.md +208 -0
  165. package/skills/typescript/references/idioms.md +246 -0
  166. package/skills/typescript/references/testing.md +225 -0
  167. package/skills/typescript/references/tooling.md +208 -0
  168. package/skills/typescript/references/types.md +259 -0
@@ -0,0 +1,208 @@
1
+ # TypeScript — Tooling
2
+
3
+ `tsconfig`, linting, formatting, bundlers, monorepos.
4
+
5
+ ## `tsconfig.json` — the baseline
6
+
7
+ ```json
8
+ {
9
+ "compilerOptions": {
10
+ "target": "ES2022",
11
+ "module": "ESNext",
12
+ "moduleResolution": "Bundler",
13
+
14
+ "strict": true,
15
+ "noUncheckedIndexedAccess": true,
16
+ "noImplicitOverride": true,
17
+ "exactOptionalPropertyTypes": true,
18
+
19
+ "esModuleInterop": true,
20
+ "skipLibCheck": true,
21
+ "forceConsistentCasingInFileNames": true,
22
+
23
+ "resolveJsonModule": true,
24
+ "isolatedModules": true,
25
+
26
+ "declaration": true,
27
+ "declarationMap": true,
28
+ "sourceMap": true,
29
+
30
+ "outDir": "dist",
31
+ "rootDir": "src"
32
+ },
33
+ "include": ["src/**/*"],
34
+ "exclude": ["**/*.test.ts", "dist"]
35
+ }
36
+ ```
37
+
38
+ Key switches:
39
+
40
+ | Option | Why |
41
+ |---|---|
42
+ | `strict: true` | Turns on all strict flags. Required. |
43
+ | `noUncheckedIndexedAccess` | `arr[0]` returns `T \| undefined`. Prevents off-by-one bugs. |
44
+ | `exactOptionalPropertyTypes` | `{ x?: number }` vs `{ x: number \| undefined }` are actually different. |
45
+ | `isolatedModules` | Required for swc/esbuild/bun. Catches things the single-file compilers can't handle. |
46
+ | `skipLibCheck: true` | Faster; trust your deps. |
47
+
48
+ ## Node vs. bundler resolution
49
+
50
+ `moduleResolution: "Bundler"` for Vite, esbuild, webpack, Vite, Bun. `"NodeNext"` for plain Node.js. Pick based on the runtime.
51
+
52
+ ## Path aliases
53
+
54
+ ```json
55
+ {
56
+ "compilerOptions": {
57
+ "baseUrl": ".",
58
+ "paths": {
59
+ "@/*": ["src/*"]
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ Mirror this in the bundler and test runner, or imports resolve in TS but not at runtime.
66
+
67
+ ## Linting — ESLint (flat config)
68
+
69
+ ```js
70
+ // eslint.config.js
71
+ import tseslint from 'typescript-eslint';
72
+ export default tseslint.config(
73
+ ...tseslint.configs.recommendedTypeChecked,
74
+ {
75
+ languageOptions: { parserOptions: { project: true } },
76
+ rules: {
77
+ '@typescript-eslint/no-floating-promises': 'error',
78
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
79
+ '@typescript-eslint/switch-exhaustiveness-check': 'error',
80
+ '@typescript-eslint/consistent-type-imports': 'error',
81
+ '@typescript-eslint/no-explicit-any': 'error',
82
+ },
83
+ },
84
+ );
85
+ ```
86
+
87
+ High-signal rules:
88
+ - `no-floating-promises` — forgotten `await` is a frequent bug
89
+ - `switch-exhaustiveness-check` — forgotten case in a union switch
90
+ - `no-misused-promises` — `async` callback passed where sync is expected
91
+ - `consistent-type-imports` — `import type { X }` keeps runtime lean
92
+
93
+ ## Formatting — Prettier (or project-specific)
94
+
95
+ Let the formatter run in CI and on save. Don't argue style in PRs.
96
+
97
+ ```json
98
+ // .prettierrc
99
+ {
100
+ "semi": true,
101
+ "singleQuote": true,
102
+ "trailingComma": "all",
103
+ "printWidth": 100,
104
+ "arrowParens": "always"
105
+ }
106
+ ```
107
+
108
+ Team preference is fine — the choice matters less than the consistency.
109
+
110
+ ## `tsc` vs. the bundlers
111
+
112
+ | Tool | Role |
113
+ |---|---|
114
+ | `tsc` | Type checking + `.d.ts` emit |
115
+ | `esbuild` / `swc` | Fast transpile (strip types, downlevel syntax) |
116
+ | `tsup` / `rollup` | Libraries with exports maps |
117
+ | `vite` / `rsbuild` / `rspack` | App bundling (frontend / dev server) |
118
+
119
+ Typical modern setup: `vitest` for tests, `vite` for the dev server, `tsc --noEmit` in CI for type checking. Transpile is handled by the bundler — `tsc` is not on the hot path.
120
+
121
+ ## Package managers
122
+
123
+ `pnpm` is the default recommendation:
124
+ - Content-addressable store → disk savings, fast installs
125
+ - Strict by default → catches phantom dependencies
126
+ - Excellent monorepo support via workspaces
127
+
128
+ `npm` works. `yarn classic` (v1) is deprecated; `yarn berry` (v3+) is fine but niche. `bun` is fastest but still rough around the edges for some toolchains.
129
+
130
+ Commit the lockfile.
131
+
132
+ ## Monorepo
133
+
134
+ For 3+ packages that share code, use workspaces.
135
+
136
+ ```json
137
+ // package.json (root)
138
+ {
139
+ "private": true,
140
+ "workspaces": ["packages/*", "apps/*"]
141
+ }
142
+ ```
143
+
144
+ Prefer workspace tools over hand-rolled symlinks:
145
+ - `pnpm` workspaces (native)
146
+ - `turborepo` / `nx` for task orchestration + caching
147
+ - `changesets` for versioning + release notes
148
+
149
+ Keep the dependency graph explicit — `apps/web` depends on `packages/ui`, not the reverse.
150
+
151
+ ## `package.json` essentials
152
+
153
+ ```json
154
+ {
155
+ "name": "@org/pkg",
156
+ "version": "1.2.0",
157
+ "type": "module",
158
+ "exports": {
159
+ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
160
+ "./utils": { "types": "./dist/utils.d.ts", "import": "./dist/utils.js" }
161
+ },
162
+ "files": ["dist"],
163
+ "scripts": {
164
+ "build": "tsup src/index.ts --format esm --dts",
165
+ "test": "vitest run",
166
+ "lint": "eslint .",
167
+ "typecheck": "tsc --noEmit"
168
+ },
169
+ "engines": { "node": ">=20" }
170
+ }
171
+ ```
172
+
173
+ - `type: "module"` — ESM by default. New projects should be ESM.
174
+ - `exports` — explicit public API, not "everything in the dist directory".
175
+ - `engines` — fail loudly when users install on an unsupported Node.
176
+
177
+ ## Dev vs. prod dependencies
178
+
179
+ - `dependencies`: anything the shipped app actually runs
180
+ - `devDependencies`: build tools, test frameworks, types
181
+ - `peerDependencies`: libraries (only) — "I need X, user provides it"
182
+
183
+ A library that ships `react` in `dependencies` causes duplicate-React bugs. Put it in `peerDependencies` with a loose range.
184
+
185
+ ## CI checklist
186
+
187
+ ```yaml
188
+ # GitHub Actions sketch
189
+ - run: pnpm install --frozen-lockfile
190
+ - run: pnpm typecheck
191
+ - run: pnpm lint
192
+ - run: pnpm test --coverage
193
+ - run: pnpm build
194
+ ```
195
+
196
+ Order matters: typecheck before tests (cheap fail); lint before tests (cheap fail); build last.
197
+
198
+ ## Anti-patterns
199
+
200
+ | Anti-pattern | Fix |
201
+ |---|---|
202
+ | `strict: false` | Turn it on; fix errors incrementally with `// @ts-expect-error` |
203
+ | `tsc` on every file save in a large repo | Use `--incremental` or IDE's language server |
204
+ | Leaving `skipLibCheck: false` | Fine for a tiny project; wastes CI on any larger one |
205
+ | Committing `dist/` | Publish it, don't commit it |
206
+ | `type: "commonjs"` in new projects | Go ESM |
207
+ | Mixing formatters across directories | One config at the repo root |
208
+ | `ts-node` in production | Pre-compile; don't transpile at boot |
@@ -0,0 +1,259 @@
1
+ # TypeScript — Types
2
+
3
+ Generics, unions, discriminated unions, utility types, brands.
4
+
5
+ ## `unknown` over `any`
6
+
7
+ `any` disables type checking. `unknown` forces you to narrow before use.
8
+
9
+ ```ts
10
+ // ❌ silent bugs
11
+ function parse(raw: any) {
12
+ return raw.user.email.toLowerCase(); // crashes at runtime, no TS error
13
+ }
14
+
15
+ // ✅
16
+ function parse(raw: unknown) {
17
+ if (typeof raw === 'object' && raw !== null && 'user' in raw) {
18
+ // ... still need more narrowing
19
+ }
20
+ }
21
+
22
+ // ✅✅ parse at boundary with a schema
23
+ import { z } from 'zod';
24
+ const Schema = z.object({ user: z.object({ email: z.string().email() }) });
25
+ function parse(raw: unknown) {
26
+ const { user } = Schema.parse(raw); // throws on bad shape
27
+ return user.email.toLowerCase();
28
+ }
29
+ ```
30
+
31
+ ## Discriminated unions — the TS superpower
32
+
33
+ Model state with a tagged sum type. Narrowing happens automatically.
34
+
35
+ ```ts
36
+ type Result<T, E> =
37
+ | { ok: true; value: T }
38
+ | { ok: false; error: E };
39
+
40
+ function handle(r: Result<User, Error>) {
41
+ if (r.ok) {
42
+ // r is narrowed to { ok: true; value: User }
43
+ console.log(r.value.name);
44
+ } else {
45
+ // r is narrowed to { ok: false; error: Error }
46
+ console.error(r.error.message);
47
+ }
48
+ }
49
+ ```
50
+
51
+ Prefer over boolean flags + optional fields:
52
+
53
+ ```ts
54
+ // ❌
55
+ type Loading = { isLoading: boolean; data?: User; error?: string };
56
+
57
+ // ✅
58
+ type Loading =
59
+ | { status: 'idle' }
60
+ | { status: 'loading' }
61
+ | { status: 'success'; data: User }
62
+ | { status: 'error'; error: string };
63
+ ```
64
+
65
+ The compiler stops you from accessing `data` on an `error` state.
66
+
67
+ ## Exhaustiveness checks
68
+
69
+ When you switch over a union, make sure every case is handled.
70
+
71
+ ```ts
72
+ function render(r: Loading) {
73
+ switch (r.status) {
74
+ case 'idle': return <Idle />;
75
+ case 'loading': return <Spinner />;
76
+ case 'success': return <Show data={r.data} />;
77
+ case 'error': return <Err msg={r.error} />;
78
+ default: return assertNever(r);
79
+ }
80
+ }
81
+
82
+ function assertNever(x: never): never {
83
+ throw new Error(`Unreachable: ${JSON.stringify(x)}`);
84
+ }
85
+ ```
86
+
87
+ Add a new variant → the compiler forces you to update every `switch`.
88
+
89
+ ## Generics
90
+
91
+ Use when a function / class preserves a type relationship.
92
+
93
+ ```ts
94
+ function first<T>(xs: readonly T[]): T | undefined {
95
+ return xs[0];
96
+ }
97
+
98
+ // Bounded
99
+ function byId<T extends { id: string }>(xs: T[], id: string): T | undefined {
100
+ return xs.find(x => x.id === id);
101
+ }
102
+
103
+ // Default
104
+ type Paginated<T, Cursor = string> = {
105
+ data: T[];
106
+ nextCursor: Cursor | null;
107
+ };
108
+ ```
109
+
110
+ If a generic parameter appears only once in the signature, you probably don't need generics.
111
+
112
+ ## Utility types — the greatest hits
113
+
114
+ | Type | Use |
115
+ |---|---|
116
+ | `Partial<T>` | All fields optional |
117
+ | `Required<T>` | All fields required |
118
+ | `Readonly<T>` | All fields readonly |
119
+ | `Pick<T, K>` | Subset of fields |
120
+ | `Omit<T, K>` | All fields except K |
121
+ | `Record<K, V>` | Map-like object |
122
+ | `Awaited<P>` | Unwrap Promise |
123
+ | `ReturnType<F>` | The return type of a function |
124
+ | `Parameters<F>` | Tuple of function params |
125
+ | `NonNullable<T>` | Exclude null / undefined |
126
+
127
+ ```ts
128
+ type User = { id: string; email: string; password: string };
129
+ type PublicUser = Omit<User, 'password'>;
130
+ type UserUpdate = Partial<Pick<User, 'email' | 'password'>>;
131
+ ```
132
+
133
+ ## Branded types
134
+
135
+ Prevent string / number mix-ups at compile time.
136
+
137
+ ```ts
138
+ type Brand<K, T> = K & { __brand: T };
139
+
140
+ type UserId = Brand<string, 'UserId'>;
141
+ type OrgId = Brand<string, 'OrgId'>;
142
+
143
+ function userId(s: string): UserId { return s as UserId; }
144
+ function orgId(s: string): OrgId { return s as OrgId; }
145
+
146
+ function findUser(id: UserId): User { ... }
147
+
148
+ findUser(userId('u_1')); // ✅
149
+ findUser('u_1'); // ❌ not a UserId
150
+ findUser(orgId('o_1')); // ❌ wrong brand
151
+ ```
152
+
153
+ The runtime cost is zero — it's just a compile-time distinction. Use it for domain ids, units, hashed vs raw strings.
154
+
155
+ ## Type guards
156
+
157
+ Custom predicates that narrow.
158
+
159
+ ```ts
160
+ function isUser(x: unknown): x is User {
161
+ return typeof x === 'object' && x !== null && 'id' in x && typeof (x as any).id === 'string';
162
+ }
163
+
164
+ const raw: unknown = fetchSomething();
165
+ if (isUser(raw)) {
166
+ raw.id.toLowerCase(); // raw is narrowed to User
167
+ }
168
+ ```
169
+
170
+ For anything more than two fields, use a schema validator (`zod`, `valibot`). Hand-written guards drift from the shape.
171
+
172
+ ## `as const` — literal-typed values
173
+
174
+ Widening turns `"hello"` into `string`. `as const` keeps it literal.
175
+
176
+ ```ts
177
+ const ROLES = ['admin', 'user', 'guest'] as const;
178
+ type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'
179
+
180
+ const CONFIG = { retries: 3, timeout: 5000 } as const;
181
+ // CONFIG.retries is 3, not number; CONFIG is readonly
182
+ ```
183
+
184
+ ## Conditional & mapped types — use sparingly
185
+
186
+ Powerful, but expensive on the reader. Reach for them when the alternative is copy-paste.
187
+
188
+ ```ts
189
+ // Conditional
190
+ type NonNull<T> = T extends null | undefined ? never : T;
191
+
192
+ // Mapped
193
+ type Nullable<T> = { [K in keyof T]: T[K] | null };
194
+
195
+ // Both, with `infer`
196
+ type UnwrapArray<T> = T extends (infer U)[] ? U : T;
197
+
198
+ type X = UnwrapArray<User[]>; // User
199
+ type Y = UnwrapArray<string>; // string
200
+ ```
201
+
202
+ If the type gets fancy enough that a teammate asks "what does this do?", add a comment with an example, or simplify.
203
+
204
+ ## `never` — the bottom type
205
+
206
+ `never` appears when TS knows no value can reach here.
207
+
208
+ ```ts
209
+ function throwErr(m: string): never { throw new Error(m); }
210
+
211
+ const x = cond ? 1 : throwErr('no'); // x is number
212
+
213
+ // Exhaustiveness (see above)
214
+ default: return assertNever(r);
215
+ ```
216
+
217
+ `never` in a position where you expect a value is a bug signal: "you forgot a case".
218
+
219
+ ## `satisfies` — typecheck without widening
220
+
221
+ Force a value to conform to a type, but keep its narrow inferred type.
222
+
223
+ ```ts
224
+ const routes = {
225
+ home: '/home',
226
+ profile: '/profile/:id',
227
+ } satisfies Record<string, string>;
228
+
229
+ // routes.home is the literal "/home", not string
230
+ type RouteKey = keyof typeof routes; // 'home' | 'profile'
231
+ ```
232
+
233
+ Great for config objects. `as Record<string, string>` would erase the literals.
234
+
235
+ ## Enums — avoid; prefer string unions
236
+
237
+ ```ts
238
+ // ❌ enum (generates runtime code, not tree-shakeable, weird reverse mapping)
239
+ enum Role { Admin, User, Guest }
240
+
241
+ // ✅ string union (zero runtime, tree-shakeable, grepable)
242
+ type Role = 'admin' | 'user' | 'guest';
243
+
244
+ // ✅ object + `as const` if you need a value container
245
+ const Role = { Admin: 'admin', User: 'user', Guest: 'guest' } as const;
246
+ type Role = typeof Role[keyof typeof Role];
247
+ ```
248
+
249
+ `const enum` exists but trips up bundlers and project-references. Not worth it.
250
+
251
+ ## Class vs. type vs. interface
252
+
253
+ | Use | When |
254
+ |---|---|
255
+ | `type` | Unions, intersections, mapped / conditional, brands, non-extensible shapes |
256
+ | `interface` | Public object contract, open for extension by users (declaration merging) |
257
+ | `class` | Need a runtime object with methods and state |
258
+
259
+ Prefer `type` for data and `interface` for object behaviours. Don't mix conventions arbitrarily.