@dynokostya/just-works 1.0.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.
- package/.claude/agents/csharp-code-writer.md +32 -0
- package/.claude/agents/diagrammer.md +49 -0
- package/.claude/agents/frontend-code-writer.md +36 -0
- package/.claude/agents/prompt-writer.md +38 -0
- package/.claude/agents/python-code-writer.md +32 -0
- package/.claude/agents/swift-code-writer.md +32 -0
- package/.claude/agents/typescript-code-writer.md +32 -0
- package/.claude/commands/git-sync.md +96 -0
- package/.claude/commands/project-docs.md +287 -0
- package/.claude/settings.json +112 -0
- package/.claude/settings.json.default +15 -0
- package/.claude/skills/csharp-coding/SKILL.md +368 -0
- package/.claude/skills/ddd-architecture-python/SKILL.md +288 -0
- package/.claude/skills/feature-driven-architecture-python/SKILL.md +302 -0
- package/.claude/skills/gemini-3-prompting/SKILL.md +483 -0
- package/.claude/skills/gpt-5-2-prompting/SKILL.md +295 -0
- package/.claude/skills/opus-4-6-prompting/SKILL.md +315 -0
- package/.claude/skills/plantuml-diagramming/SKILL.md +758 -0
- package/.claude/skills/python-coding/SKILL.md +293 -0
- package/.claude/skills/react-coding/SKILL.md +264 -0
- package/.claude/skills/rest-api/SKILL.md +421 -0
- package/.claude/skills/shadcn-ui-coding/SKILL.md +454 -0
- package/.claude/skills/swift-coding/SKILL.md +401 -0
- package/.claude/skills/tailwind-css-coding/SKILL.md +268 -0
- package/.claude/skills/typescript-coding/SKILL.md +464 -0
- package/.claude/statusline-command.sh +34 -0
- package/.codex/prompts/plan-reviewer.md +162 -0
- package/.codex/prompts/project-docs.md +287 -0
- package/.codex/skills/ddd-architecture-python/SKILL.md +288 -0
- package/.codex/skills/feature-driven-architecture-python/SKILL.md +302 -0
- package/.codex/skills/gemini-3-prompting/SKILL.md +483 -0
- package/.codex/skills/gpt-5-2-prompting/SKILL.md +295 -0
- package/.codex/skills/opus-4-6-prompting/SKILL.md +315 -0
- package/.codex/skills/plantuml-diagramming/SKILL.md +758 -0
- package/.codex/skills/python-coding/SKILL.md +293 -0
- package/.codex/skills/react-coding/SKILL.md +264 -0
- package/.codex/skills/rest-api/SKILL.md +421 -0
- package/.codex/skills/shadcn-ui-coding/SKILL.md +454 -0
- package/.codex/skills/tailwind-css-coding/SKILL.md +268 -0
- package/.codex/skills/typescript-coding/SKILL.md +464 -0
- package/AGENTS.md +57 -0
- package/CLAUDE.md +98 -0
- package/LICENSE +201 -0
- package/README.md +114 -0
- package/bin/cli.mjs +291 -0
- package/package.json +39 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typescript-coding
|
|
3
|
+
description: Apply when writing or editing TypeScript (.ts) files. Behavioral corrections for error handling, async patterns, type system, module system, security defaults, and common antipatterns. Project conventions always override these defaults.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TypeScript Coding
|
|
7
|
+
|
|
8
|
+
Match the project's existing conventions. When uncertain, read 2-3 existing files to infer the local style. Check `tsconfig.json` for `strict` mode, `target`, `module`, and `moduleResolution` settings. Check `package.json` for runtime (Node.js, Deno, Bun) and dependency versions. These defaults apply only when the project has no established convention.
|
|
9
|
+
|
|
10
|
+
## Never rules
|
|
11
|
+
|
|
12
|
+
These are unconditional. They prevent bugs and vulnerabilities regardless of project style.
|
|
13
|
+
|
|
14
|
+
- **Never use `any`** — contagious type erasure that disables all downstream checking. Use `unknown` and narrow with type guards, or use generics with constraints.
|
|
15
|
+
- **Never use `@ts-ignore`** — permanently suppresses errors with no feedback when conditions change. Use `@ts-expect-error` with a justification comment; it fails the build when the suppressed error disappears.
|
|
16
|
+
- **Never use `==` for equality** — implicit type coercion produces unintuitive results (`0 == ''` is `true`). Use `===` and `!==`. The only acceptable exception is `x == null` to check both `null` and `undefined`.
|
|
17
|
+
- **Never use `as` type assertions on external data** — zero runtime validation; the program continues with corrupted state until it crashes far from the source. Validate at system boundaries with Zod `safeParse` or equivalent runtime validators.
|
|
18
|
+
- **Never use `!` non-null assertion without proof** — tells TypeScript a value is not `null` without any runtime check. Use nullish coalescing (`??`), optional chaining (`?.`), or explicit `if` checks.
|
|
19
|
+
- **Never use `eval()` or `new Function()`** — executes arbitrary strings as code, enabling injection attacks. Use `JSON.parse` for data, lookup tables for dispatch, and proper parsers for expressions.
|
|
20
|
+
- **Never leave a Promise floating** — unhandled rejections cause silent failures or process termination. Always `await`, chain `.catch()`, or prefix with `void` and add an error handler.
|
|
21
|
+
- **Never mutate function parameters** — objects and arrays are passed by reference; mutation silently corrupts the caller's data. Spread or `structuredClone()` before mutating. Mark parameters `readonly`.
|
|
22
|
+
- **Never use `delete` on typed objects** — violates the type contract, creating objects that no longer match their type. Destructure to omit properties (`const { removed, ...rest } = obj`). For arrays, use `splice()` or `filter()`.
|
|
23
|
+
- **Never use `export *` in barrel files** — defeats tree-shaking, creates namespace collisions, and breaks "go to definition". Use explicit named re-exports.
|
|
24
|
+
- **Never omit `type` on type-only imports** — retains unnecessary import statements in compiled output, bloats bundles, and breaks isolated transpilers. Use `import type { }` or inline `type` qualifiers. Enable `verbatimModuleSyntax`.
|
|
25
|
+
- **Never use `enum`** — emits runtime IIFE code, introduces nominal typing friction, and numeric enums silently accept any number. Use `as const` objects with derived union types.
|
|
26
|
+
- **Never trust array index access without `noUncheckedIndexedAccess`** — TypeScript types `items[0]` as `T` even when the array could be empty. Enable `noUncheckedIndexedAccess: true` in tsconfig.
|
|
27
|
+
- **Never skip exhaustiveness checks in switch/union handling** — adding a new union member silently falls through without a compile error. Add a `default` case that assigns to `never`: `const _exhaustive: never = value`.
|
|
28
|
+
- **Never use `JSON.parse()` without runtime validation at system boundaries** — returns `any`, and assigning to a typed variable provides zero runtime guarantees. Validate with Zod `safeParse` or equivalent.
|
|
29
|
+
|
|
30
|
+
## Error handling
|
|
31
|
+
|
|
32
|
+
Use custom error classes with the prototype fix for correct `instanceof` checks:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
class AppError extends Error {
|
|
36
|
+
constructor(
|
|
37
|
+
message: string,
|
|
38
|
+
readonly code: string,
|
|
39
|
+
options?: ErrorOptions,
|
|
40
|
+
) {
|
|
41
|
+
super(message, options);
|
|
42
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class NotFoundError extends AppError {
|
|
47
|
+
constructor(resource: string, id: string) {
|
|
48
|
+
super(`${resource} ${id} not found`, 'NOT_FOUND');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Catch blocks receive `unknown` — always narrow before accessing properties:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
try {
|
|
57
|
+
await fetchUser(id);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error instanceof NotFoundError) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
throw new AppError('Failed to fetch user', 'FETCH_ERROR', { cause: error });
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Result pattern using discriminated unions for expected domain failures:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
type Result<T, E = Error> =
|
|
70
|
+
| { readonly ok: true; readonly value: T }
|
|
71
|
+
| { readonly ok: false; readonly error: E };
|
|
72
|
+
|
|
73
|
+
function parseConfig(raw: string): Result<Config, ValidationError> {
|
|
74
|
+
const parsed = ConfigSchema.safeParse(JSON.parse(raw));
|
|
75
|
+
if (!parsed.success) {
|
|
76
|
+
return { ok: false, error: new ValidationError(parsed.error) };
|
|
77
|
+
}
|
|
78
|
+
return { ok: true, value: parsed.data };
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Chain error causes (ES2022) to preserve the original stack:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
throw new AppError('Order processing failed', 'ORDER_ERROR', { cause: error });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Situation | Strategy | Reason |
|
|
89
|
+
|-----------|----------|--------|
|
|
90
|
+
| Programmer mistake (bad argument) | `throw` | Fail fast, fix the bug |
|
|
91
|
+
| Expected domain failure (not found, validation) | `Result` | Caller must handle it |
|
|
92
|
+
| Public API boundary | `Result` | Explicit contract, no surprise throws |
|
|
93
|
+
| Internal implementation | `throw` | Simpler, caught at boundary |
|
|
94
|
+
|
|
95
|
+
## Resource cleanup
|
|
96
|
+
|
|
97
|
+
`using` / `await using` (TypeScript 5.2+) with `Symbol.dispose` / `Symbol.asyncDispose` is the preferred pattern — cleanup runs automatically when the scope exits:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
class TempFile implements Disposable {
|
|
101
|
+
readonly path: string;
|
|
102
|
+
|
|
103
|
+
constructor(prefix: string) {
|
|
104
|
+
this.path = `${tmpdir()}/${prefix}-${Date.now()}`;
|
|
105
|
+
writeFileSync(this.path, '');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
[Symbol.dispose](): void {
|
|
109
|
+
rmSync(this.path, { force: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function processData(): void {
|
|
114
|
+
using tmp = new TempFile('data');
|
|
115
|
+
writeFileSync(tmp.path, serialize(data));
|
|
116
|
+
transform(tmp.path);
|
|
117
|
+
// tmp is disposed here, even on throw
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`AbortController` / `AbortSignal` as universal cancellation token:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
async function fetchWithTimeout(url: string, ms: number): Promise<Response> {
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
const timeout = setTimeout(() => controller.abort(), ms);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
return await fetch(url, { signal: controller.signal });
|
|
130
|
+
} finally {
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Event listener cleanup with `{ signal }`:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
element.addEventListener('click', handleClick, { signal: controller.signal });
|
|
141
|
+
element.addEventListener('keydown', handleKey, { signal: controller.signal });
|
|
142
|
+
// cleanup all at once:
|
|
143
|
+
controller.abort();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Use `try/finally` as fallback when `using` is not available.
|
|
147
|
+
|
|
148
|
+
## Async patterns
|
|
149
|
+
|
|
150
|
+
Promise combinators — pick the right one:
|
|
151
|
+
|
|
152
|
+
| Combinator | Settles when | Use case |
|
|
153
|
+
|------------|-------------|----------|
|
|
154
|
+
| `Promise.all` | All fulfill (or first reject) | Independent concurrent work, fail fast |
|
|
155
|
+
| `Promise.allSettled` | All settle | Batch operations where partial success is OK |
|
|
156
|
+
| `Promise.race` | First settles | Timeout racing, first-response-wins |
|
|
157
|
+
| `Promise.any` | First fulfills (or all reject) | Fallback chains, redundant sources |
|
|
158
|
+
|
|
159
|
+
Concurrent execution for independent work — avoid sequential `await` in loops:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Bad: sequential, N round trips
|
|
163
|
+
for (const id of ids) {
|
|
164
|
+
const user = await fetchUser(id);
|
|
165
|
+
results.push(user);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Good: concurrent
|
|
169
|
+
const results = await Promise.all(ids.map((id) => fetchUser(id)));
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Propagate `AbortSignal` through call chains:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
async function processOrder(
|
|
176
|
+
orderId: string,
|
|
177
|
+
signal?: AbortSignal,
|
|
178
|
+
): Promise<Order> {
|
|
179
|
+
signal?.throwIfAborted();
|
|
180
|
+
const order = await fetchOrder(orderId, { signal });
|
|
181
|
+
const validated = await validateInventory(order, { signal });
|
|
182
|
+
return await chargePayment(validated, { signal });
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Async generators for paginated or streaming data:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
async function* fetchPages<T>(
|
|
190
|
+
url: string,
|
|
191
|
+
signal?: AbortSignal,
|
|
192
|
+
): AsyncGenerator<T[]> {
|
|
193
|
+
let cursor: string | undefined;
|
|
194
|
+
|
|
195
|
+
do {
|
|
196
|
+
const params = cursor ? `?cursor=${cursor}` : '';
|
|
197
|
+
const res = await fetch(`${url}${params}`, { signal });
|
|
198
|
+
const data = PageSchema.parse(await res.json());
|
|
199
|
+
yield data.items;
|
|
200
|
+
cursor = data.nextCursor;
|
|
201
|
+
} while (cursor);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for await (const page of fetchPages<User>('/api/users', signal)) {
|
|
205
|
+
await processBatch(page);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Concurrency limiting with a semaphore:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
async function mapConcurrent<T, R>(
|
|
213
|
+
items: T[],
|
|
214
|
+
limit: number,
|
|
215
|
+
fn: (item: T) => Promise<R>,
|
|
216
|
+
): Promise<R[]> {
|
|
217
|
+
const results: R[] = [];
|
|
218
|
+
const executing = new Set<Promise<void>>();
|
|
219
|
+
|
|
220
|
+
for (const item of items) {
|
|
221
|
+
const p = fn(item).then((r) => { results.push(r); });
|
|
222
|
+
executing.add(p);
|
|
223
|
+
p.finally(() => executing.delete(p));
|
|
224
|
+
if (executing.size >= limit) await Promise.race(executing);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await Promise.all(executing);
|
|
228
|
+
return results;
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Type system
|
|
233
|
+
|
|
234
|
+
Generics with constraints and defaults:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
function merge<T extends Record<string, unknown>>(
|
|
238
|
+
target: T,
|
|
239
|
+
...sources: Partial<T>[]
|
|
240
|
+
): T {
|
|
241
|
+
return Object.assign({}, target, ...sources);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
type ApiResponse<T, E = Error> = Result<T, E> & { statusCode: number };
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Discriminated unions with exhaustive checking via `never`:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
type Event =
|
|
251
|
+
| { type: 'created'; payload: { id: string } }
|
|
252
|
+
| { type: 'updated'; payload: { id: string; changes: string[] } }
|
|
253
|
+
| { type: 'deleted'; payload: { id: string } };
|
|
254
|
+
|
|
255
|
+
function handleEvent(event: Event): void {
|
|
256
|
+
switch (event.type) {
|
|
257
|
+
case 'created':
|
|
258
|
+
onCreate(event.payload.id);
|
|
259
|
+
break;
|
|
260
|
+
case 'updated':
|
|
261
|
+
onUpdate(event.payload.id, event.payload.changes);
|
|
262
|
+
break;
|
|
263
|
+
case 'deleted':
|
|
264
|
+
onDelete(event.payload.id);
|
|
265
|
+
break;
|
|
266
|
+
default: {
|
|
267
|
+
const _exhaustive: never = event;
|
|
268
|
+
throw new Error(`Unhandled event: ${_exhaustive}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Type narrowing — all forms:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// typeof
|
|
278
|
+
function format(value: string | number): string {
|
|
279
|
+
return typeof value === 'string' ? value : value.toFixed(2);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// instanceof
|
|
283
|
+
if (error instanceof AppError) { /* error.code is available */ }
|
|
284
|
+
|
|
285
|
+
// in
|
|
286
|
+
if ('email' in user) { /* user has email property */ }
|
|
287
|
+
|
|
288
|
+
// Custom type guard
|
|
289
|
+
function isUser(value: unknown): value is User {
|
|
290
|
+
return (
|
|
291
|
+
typeof value === 'object' && value !== null &&
|
|
292
|
+
'id' in value && 'name' in value
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Assertion function
|
|
297
|
+
function assertDefined<T>(value: T | null | undefined, msg: string): asserts value is T {
|
|
298
|
+
if (value == null) throw new Error(msg);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Branded types for domain IDs:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
type Brand<T, B extends string> = T & { readonly __brand: B };
|
|
306
|
+
type UserId = Brand<string, 'UserId'>;
|
|
307
|
+
type OrderId = Brand<string, 'OrderId'>;
|
|
308
|
+
|
|
309
|
+
function createUserId(id: string): UserId { return id as UserId; }
|
|
310
|
+
|
|
311
|
+
function getUser(id: UserId): Promise<User> { /* ... */ }
|
|
312
|
+
// getUser(orderId) — compile error, prevents mixing IDs
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`satisfies` operator (TS 4.9+) — validate without widening:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
const config = {
|
|
319
|
+
port: 3000,
|
|
320
|
+
host: 'localhost',
|
|
321
|
+
debug: true,
|
|
322
|
+
} satisfies Record<string, string | number | boolean>;
|
|
323
|
+
// config.port is number, not string | number | boolean
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`as const` and const type parameters (TS 5.0+):
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
function createRoute<const T extends readonly string[]>(methods: T) {
|
|
330
|
+
return { methods };
|
|
331
|
+
}
|
|
332
|
+
const route = createRoute(['GET', 'POST']);
|
|
333
|
+
// route.methods is readonly ['GET', 'POST'], not string[]
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Data modeling
|
|
337
|
+
|
|
338
|
+
| Use Case | Choice | Reason |
|
|
339
|
+
|----------|--------|--------|
|
|
340
|
+
| API response/request shape | `type` + Zod schema | Single source of truth with runtime validation |
|
|
341
|
+
| Fixed string constants (type only) | String union | Zero runtime cost |
|
|
342
|
+
| Fixed string constants (need runtime) | `as const` object + derived union | Tree-shakeable, iterable |
|
|
343
|
+
| Object contract for public API | `interface` | Declaration merging, clearer errors |
|
|
344
|
+
| Union of different shapes | Discriminated union with `kind` tag | Exhaustive checking, best narrowing |
|
|
345
|
+
| Structurally identical but semantically different values | Branded types | Prevents mixing `UserId` with `OrderId` |
|
|
346
|
+
| Immutable configuration | `as const` + `Readonly` | Compile-time literal types + immutability |
|
|
347
|
+
| Entity with invariants and behavior | Class with private constructor | Constructor enforces rules |
|
|
348
|
+
|
|
349
|
+
Zod schema as single source of truth:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { z } from 'zod';
|
|
353
|
+
|
|
354
|
+
const UserSchema = z.object({
|
|
355
|
+
id: z.string(),
|
|
356
|
+
email: z.string().email(),
|
|
357
|
+
name: z.string().min(1),
|
|
358
|
+
createdAt: z.coerce.date(),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
type User = z.infer<typeof UserSchema>;
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`as const` object with derived union:
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
const Status = {
|
|
368
|
+
Active: 'active',
|
|
369
|
+
Inactive: 'inactive',
|
|
370
|
+
Suspended: 'suspended',
|
|
371
|
+
} as const;
|
|
372
|
+
|
|
373
|
+
type Status = (typeof Status)[keyof typeof Status];
|
|
374
|
+
// 'active' | 'inactive' | 'suspended'
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Pattern matching
|
|
378
|
+
|
|
379
|
+
Discriminated unions with `switch` and exhaustive checking:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
type Shape =
|
|
383
|
+
| { kind: 'circle'; radius: number }
|
|
384
|
+
| { kind: 'rectangle'; width: number; height: number };
|
|
385
|
+
|
|
386
|
+
function area(shape: Shape): number {
|
|
387
|
+
switch (shape.kind) {
|
|
388
|
+
case 'circle':
|
|
389
|
+
return Math.PI * shape.radius ** 2;
|
|
390
|
+
case 'rectangle':
|
|
391
|
+
return shape.width * shape.height;
|
|
392
|
+
default: {
|
|
393
|
+
const _exhaustive: never = shape;
|
|
394
|
+
throw new Error(`Unexpected shape: ${_exhaustive}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Type guard functions for filtering and narrowing:
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
function isNonNull<T>(value: T | null | undefined): value is T {
|
|
404
|
+
return value != null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const items = [1, null, 2, undefined, 3].filter(isNonNull);
|
|
408
|
+
// items: number[]
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Naming conventions
|
|
412
|
+
|
|
413
|
+
| Element | Convention | Example |
|
|
414
|
+
|---------|-----------|---------|
|
|
415
|
+
| Files | kebab-case | `user-service.ts` |
|
|
416
|
+
| Types / interfaces / classes | PascalCase, no `I` prefix | `UserService`, `Config` |
|
|
417
|
+
| Variables / functions | camelCase | `fetchUser`, `isValid` |
|
|
418
|
+
| Constants (primitives) | UPPER_SNAKE_CASE | `MAX_RETRIES` |
|
|
419
|
+
| Constants (objects) | camelCase | `defaultConfig` |
|
|
420
|
+
| Generics | `T` simple, `TDescriptive` complex | `T`, `TResponse` |
|
|
421
|
+
| Booleans | `is`/`has`/`should`/`can` prefix | `isLoading`, `hasAccess` |
|
|
422
|
+
| Private fields | `#` (runtime enforcement) | `#cache`, `#state` |
|
|
423
|
+
|
|
424
|
+
## Module system
|
|
425
|
+
|
|
426
|
+
ESM as the default — set `"type": "module"` in `package.json`. Use `import type` for type-only imports and enable `verbatimModuleSyntax: true` in tsconfig to enforce it.
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
import type { User, Config } from './types.js';
|
|
430
|
+
import { fetchUser } from './api.js';
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Avoid deep barrel export chains — they defeat tree-shaking and slow IDE indexing. One level of barrel at package boundaries is acceptable:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
// packages/auth/index.ts — acceptable
|
|
437
|
+
export { AuthService } from './auth-service.js';
|
|
438
|
+
export type { AuthToken, AuthConfig } from './types.js';
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Testing
|
|
442
|
+
|
|
443
|
+
Use Vitest as the default test runner. Use Jest for legacy projects already standardized on it.
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
447
|
+
|
|
448
|
+
describe('UserService', () => {
|
|
449
|
+
it('returns user when found', async () => {
|
|
450
|
+
const mockRepo = { findById: vi.fn<[string], Promise<User | null>>() };
|
|
451
|
+
mockRepo.findById.mockResolvedValue({ id: '1', name: 'Alice' });
|
|
452
|
+
|
|
453
|
+
const service = new UserService(mockRepo);
|
|
454
|
+
const user = await service.getUser('1');
|
|
455
|
+
|
|
456
|
+
expect(user).toEqual({ id: '1', name: 'Alice' });
|
|
457
|
+
expect(mockRepo.findById).toHaveBeenCalledWith('1');
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Mock at boundaries (HTTP, DB, clock), test logic directly. Use the AAA pattern: Arrange (set up data and mocks), Act (call the function), Assert (verify the outcome). Type-safe mocking with `vi.fn()` and `vi.mocked()` — avoid untyped mocks that drift from the real interface.
|
|
463
|
+
|
|
464
|
+
When to mock: external APIs with rate limits or costs, network-dependent behavior, error paths, timers. When to use real instances: pure logic, value types, in-memory implementations. Test behavior, not implementation — test what a function returns or what state it changes, not how it works internally.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
input=$(cat)
|
|
3
|
+
MODEL=$(echo "$input" | jq -r '.model.display_name')
|
|
4
|
+
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
|
|
5
|
+
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
|
|
6
|
+
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
|
|
7
|
+
|
|
8
|
+
GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; ORANGE='\033[38;5;172m'; BLUE='\033[34m'; RESET='\033[0m'
|
|
9
|
+
|
|
10
|
+
# Pick bar color based on context usage
|
|
11
|
+
if [ "$PCT" -ge 70 ]; then BAR_COLOR="$RED"
|
|
12
|
+
elif [ "$PCT" -ge 50 ]; then BAR_COLOR="$YELLOW"
|
|
13
|
+
else BAR_COLOR="$GREEN"; fi
|
|
14
|
+
|
|
15
|
+
FILLED=$((PCT / 8)); EMPTY=$((8 - FILLED))
|
|
16
|
+
BAR=$(printf "%${FILLED}s" | tr ' ' '▮')$(printf "%${EMPTY}s" | tr ' ' '▯')
|
|
17
|
+
|
|
18
|
+
BRANCH=""
|
|
19
|
+
git rev-parse --git-dir > /dev/null 2>&1 && BRANCH="${BLUE}$(git branch --show-current 2>/dev/null)${RESET}"
|
|
20
|
+
|
|
21
|
+
TIME=$(date +%H:%M)
|
|
22
|
+
HOUR=$(date +%H | sed 's/^0//')
|
|
23
|
+
# Time color: green 9-20, light blue 6-9, dark red 20-5
|
|
24
|
+
if [ "$HOUR" -ge 9 ] && [ "$HOUR" -lt 20 ]; then TIME_COLOR='\033[38;5;71m'
|
|
25
|
+
elif [ "$HOUR" -ge 6 ] && [ "$HOUR" -lt 9 ]; then TIME_COLOR='\033[38;5;117m'
|
|
26
|
+
else TIME_COLOR='\033[38;5;124m'
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
COST_FMT=$(printf '$%.2f' "$COST")
|
|
30
|
+
|
|
31
|
+
echo -e "${ORANGE}${MODEL}${RESET}"
|
|
32
|
+
echo -e "${DIR##*/}"
|
|
33
|
+
[ -n "$BRANCH" ] && echo -e "${BRANCH}"
|
|
34
|
+
echo -e "${TIME_COLOR}${TIME}${RESET} | ${COST_FMT} | ${BAR_COLOR}${BAR}${RESET} ${PCT}%"
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Review implementation plans with evidence-based critique before coding begins
|
|
3
|
+
argument-hint: [PLAN_FILE=<path>]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plan Reviewer
|
|
7
|
+
|
|
8
|
+
Review an implementation plan and produce a review file at `.codex/plan-reviews/{plan-name}-reviewed.md`.
|
|
9
|
+
|
|
10
|
+
## Plan Discovery
|
|
11
|
+
|
|
12
|
+
1. If a `PLAN_FILE` argument is provided, use that path directly.
|
|
13
|
+
2. Otherwise, find the most recently modified file in `.claude/plans/`.
|
|
14
|
+
|
|
15
|
+
You are reviewing a plan BEFORE implementation. The proposed changes do not exist in the codebase yet. Read the current codebase to understand existing architecture, patterns, and dependencies -- then evaluate whether the proposal is sound given that current state.
|
|
16
|
+
|
|
17
|
+
## Plan Author Context
|
|
18
|
+
|
|
19
|
+
Plans are authored by Claude Opus 4.6. Watch for these common planning tendencies:
|
|
20
|
+
|
|
21
|
+
- **Over-abstraction**: Protocol classes, factories, base classes, or strategy patterns for single implementations. If there's only one concrete type, an abstraction layer is overhead.
|
|
22
|
+
- **Defensive over-engineering**: Try/except blocks for scenarios that cannot fail, redundant validation of trusted internal data, fallback paths that will never execute.
|
|
23
|
+
- **Unnecessary dependencies**: Proposing new libraries when stdlib or existing project dependencies already cover the use case.
|
|
24
|
+
- **Scope creep via "good practice"**: Adding logging, metrics, config flexibility, event systems, or analytics that weren't in the requirements -- justified as "good engineering" but expanding scope.
|
|
25
|
+
|
|
26
|
+
Flag these as warnings when spotted. A minimal plan that solves the problem is a strength, not a gap.
|
|
27
|
+
|
|
28
|
+
## Autonomy
|
|
29
|
+
|
|
30
|
+
- Make reasonable assumptions when the plan is ambiguous. Note them in the review.
|
|
31
|
+
- If you re-read the same files or retry the same approach more than twice without progress, stop, summarize what you tried, and try a different approach.
|
|
32
|
+
|
|
33
|
+
## Review Scope
|
|
34
|
+
|
|
35
|
+
**Review these areas:**
|
|
36
|
+
|
|
37
|
+
- Architecture decisions and trade-offs
|
|
38
|
+
- Dependency choices (necessity, security, maintenance)
|
|
39
|
+
- Implementation complexity vs. requirements
|
|
40
|
+
- Missing considerations (error handling, edge cases)
|
|
41
|
+
- Alternative approaches
|
|
42
|
+
- Risk assessment
|
|
43
|
+
- Whether the proposal aligns with existing codebase patterns and conventions
|
|
44
|
+
|
|
45
|
+
**Skip these areas:**
|
|
46
|
+
|
|
47
|
+
- Code style/formatting
|
|
48
|
+
- Naming conventions (unless confusing)
|
|
49
|
+
- Minor optimizations
|
|
50
|
+
- Hypothetical future requirements
|
|
51
|
+
|
|
52
|
+
## Severity Definitions
|
|
53
|
+
|
|
54
|
+
Two levels only:
|
|
55
|
+
|
|
56
|
+
- **Critical**: Would cause runtime failures, security vulnerabilities, data loss, or fundamental conflicts with existing architecture. Must be resolved before implementation.
|
|
57
|
+
- **Warning**: Unnecessary complexity, suboptimal approach, or missing considerations that don't block correctness. Should be considered but not blocking.
|
|
58
|
+
|
|
59
|
+
## Review Constraints
|
|
60
|
+
|
|
61
|
+
1. **Every criticism requires a concrete solution.** Never raise an issue without proposing a fix.
|
|
62
|
+
2. **Evidence-based only.** Reference specific plan sections or codebase files using `path/to/file.py:line` notation.
|
|
63
|
+
3. **Report all critical issues (no cap).** Never suppress blockers.
|
|
64
|
+
4. **Prioritize warnings by impact.** If more than ~7 warnings exist, consolidate related ones under a single topic.
|
|
65
|
+
5. **No nitpicking.** Skip style preferences and minor concerns.
|
|
66
|
+
6. **Strengths: only mention non-obvious or deliberately good decisions.** Skip generic praise. For clean plans, confirm that key architectural decisions align with the codebase and list 1-2 non-obvious strengths.
|
|
67
|
+
|
|
68
|
+
## Review Output Limits
|
|
69
|
+
|
|
70
|
+
- **Summary**: 2-3 sentences max.
|
|
71
|
+
- **Each review item**: 3-5 sentences for explanation, 2-4 sentences or a short code snippet for solution.
|
|
72
|
+
- **Strengths**: 1-3 bullets, or omit the section entirely.
|
|
73
|
+
- **Total review**: Aim for 300-600 words excluding code snippets. Longer only if critical count justifies it.
|
|
74
|
+
|
|
75
|
+
## Exploration Priorities
|
|
76
|
+
|
|
77
|
+
Before writing the review, gather context. Parallelize independent reads.
|
|
78
|
+
|
|
79
|
+
1. **Plan file** -- the thing being reviewed (via discovery logic above).
|
|
80
|
+
2. **Files the plan will modify** -- understand current patterns the plan should follow.
|
|
81
|
+
3. **Project dependencies** (`pyproject.toml`) -- check if proposed dependencies already exist or are redundant.
|
|
82
|
+
4. **Applicable skill standards** -- scan available skills directories and evaluate compliance against every skill whose description matches the plan's file types and tasks.
|
|
83
|
+
5. **New dependency docs** -- verify APIs, maintenance status, and alternatives using available documentation tools and web search.
|
|
84
|
+
|
|
85
|
+
### Skill Reference
|
|
86
|
+
|
|
87
|
+
Check all available skills before writing the review. For each skill:
|
|
88
|
+
1. Read the skill's description to determine what file types and tasks it covers
|
|
89
|
+
2. If the plan touches files or tasks matching that description, evaluate the proposal against the skill's standards
|
|
90
|
+
3. Multiple skills may apply — check all that match
|
|
91
|
+
|
|
92
|
+
### Dependency Verification
|
|
93
|
+
|
|
94
|
+
For each new dependency proposed in the plan, verify using available documentation tools and web search:
|
|
95
|
+
- Actively maintained?
|
|
96
|
+
- Current API patterns (not deprecated)?
|
|
97
|
+
- Built-in alternatives in existing project dependencies?
|
|
98
|
+
- Known security issues?
|
|
99
|
+
|
|
100
|
+
## Output Format
|
|
101
|
+
|
|
102
|
+
Write the review to `.codex/plan-reviews/{plan-name}-reviewed.md` using exactly this structure:
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
# Plan Review: {plan-name}
|
|
106
|
+
|
|
107
|
+
## Summary
|
|
108
|
+
[2-3 sentences: overall assessment, blockers if any]
|
|
109
|
+
|
|
110
|
+
## Strengths
|
|
111
|
+
- [Non-obvious or deliberate good decisions only. Omit section if nothing noteworthy.]
|
|
112
|
+
|
|
113
|
+
## Review Items
|
|
114
|
+
|
|
115
|
+
### {Topic}
|
|
116
|
+
**Level**: critical | warning
|
|
117
|
+
**Explanation**: [reference specific plan sections or current codebase files]
|
|
118
|
+
**Solution**: [concrete fix, code snippet if helpful]
|
|
119
|
+
|
|
120
|
+
## Verdict
|
|
121
|
+
[See verdict criteria below]
|
|
122
|
+
|
|
123
|
+
[1 sentence: next steps]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Verdict Criteria
|
|
127
|
+
|
|
128
|
+
- **Approved**: Zero criticals. Warnings are minor or cosmetic.
|
|
129
|
+
- **Approved with Changes**: Zero criticals. Warnings identify real risks worth addressing before implementation.
|
|
130
|
+
- **Needs Revision**: One or more critical issues exist. Plan should not proceed to implementation.
|
|
131
|
+
|
|
132
|
+
If no issues are found, state "No significant issues identified" and verdict "Approved".
|
|
133
|
+
|
|
134
|
+
## Example Review Item
|
|
135
|
+
|
|
136
|
+
### Dependency: Tenacity
|
|
137
|
+
|
|
138
|
+
**Level**: warning
|
|
139
|
+
|
|
140
|
+
**Explanation**: Plan proposes adding `tenacity` for retries (Section 3.2). The current codebase uses `httpx` which provides native retry support via `AsyncHTTPTransport(retries=N)` -- see `src/clients/base.py:18`. Adding `tenacity` introduces an unnecessary dependency.
|
|
141
|
+
|
|
142
|
+
**Solution**: Use httpx's built-in retry:
|
|
143
|
+
```python
|
|
144
|
+
transport = httpx.AsyncHTTPTransport(retries=3)
|
|
145
|
+
client = httpx.AsyncClient(transport=transport)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Incremental Reviews
|
|
149
|
+
|
|
150
|
+
When a prior review already exists in `.codex/plan-reviews/` for the same plan:
|
|
151
|
+
|
|
152
|
+
1. Read the prior review file alongside the updated plan.
|
|
153
|
+
2. Focus on whether previous `critical` items were addressed. Mark each as resolved or still open.
|
|
154
|
+
3. Flag new issues introduced by plan revisions only -- do not re-report items already covered.
|
|
155
|
+
4. Reference the prior review in the output header:
|
|
156
|
+
|
|
157
|
+
```markdown
|
|
158
|
+
# Plan Review: {plan-name} (revision)
|
|
159
|
+
**Prior review**: `.codex/plan-reviews/{plan-name}-reviewed.md`
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
5. If all prior criticals are resolved and no new criticals exist, the verdict can be upgraded.
|