@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.
Files changed (46) hide show
  1. package/.claude/agents/csharp-code-writer.md +32 -0
  2. package/.claude/agents/diagrammer.md +49 -0
  3. package/.claude/agents/frontend-code-writer.md +36 -0
  4. package/.claude/agents/prompt-writer.md +38 -0
  5. package/.claude/agents/python-code-writer.md +32 -0
  6. package/.claude/agents/swift-code-writer.md +32 -0
  7. package/.claude/agents/typescript-code-writer.md +32 -0
  8. package/.claude/commands/git-sync.md +96 -0
  9. package/.claude/commands/project-docs.md +287 -0
  10. package/.claude/settings.json +112 -0
  11. package/.claude/settings.json.default +15 -0
  12. package/.claude/skills/csharp-coding/SKILL.md +368 -0
  13. package/.claude/skills/ddd-architecture-python/SKILL.md +288 -0
  14. package/.claude/skills/feature-driven-architecture-python/SKILL.md +302 -0
  15. package/.claude/skills/gemini-3-prompting/SKILL.md +483 -0
  16. package/.claude/skills/gpt-5-2-prompting/SKILL.md +295 -0
  17. package/.claude/skills/opus-4-6-prompting/SKILL.md +315 -0
  18. package/.claude/skills/plantuml-diagramming/SKILL.md +758 -0
  19. package/.claude/skills/python-coding/SKILL.md +293 -0
  20. package/.claude/skills/react-coding/SKILL.md +264 -0
  21. package/.claude/skills/rest-api/SKILL.md +421 -0
  22. package/.claude/skills/shadcn-ui-coding/SKILL.md +454 -0
  23. package/.claude/skills/swift-coding/SKILL.md +401 -0
  24. package/.claude/skills/tailwind-css-coding/SKILL.md +268 -0
  25. package/.claude/skills/typescript-coding/SKILL.md +464 -0
  26. package/.claude/statusline-command.sh +34 -0
  27. package/.codex/prompts/plan-reviewer.md +162 -0
  28. package/.codex/prompts/project-docs.md +287 -0
  29. package/.codex/skills/ddd-architecture-python/SKILL.md +288 -0
  30. package/.codex/skills/feature-driven-architecture-python/SKILL.md +302 -0
  31. package/.codex/skills/gemini-3-prompting/SKILL.md +483 -0
  32. package/.codex/skills/gpt-5-2-prompting/SKILL.md +295 -0
  33. package/.codex/skills/opus-4-6-prompting/SKILL.md +315 -0
  34. package/.codex/skills/plantuml-diagramming/SKILL.md +758 -0
  35. package/.codex/skills/python-coding/SKILL.md +293 -0
  36. package/.codex/skills/react-coding/SKILL.md +264 -0
  37. package/.codex/skills/rest-api/SKILL.md +421 -0
  38. package/.codex/skills/shadcn-ui-coding/SKILL.md +454 -0
  39. package/.codex/skills/tailwind-css-coding/SKILL.md +268 -0
  40. package/.codex/skills/typescript-coding/SKILL.md +464 -0
  41. package/AGENTS.md +57 -0
  42. package/CLAUDE.md +98 -0
  43. package/LICENSE +201 -0
  44. package/README.md +114 -0
  45. package/bin/cli.mjs +291 -0
  46. 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.