@lousy-agents/cli 1.0.2 → 1.0.6

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,401 @@
1
+ ---
2
+ applyTo: "src/**/*.{ts,tsx}"
3
+ ---
4
+
5
+ # Clean Architecture Instructions for SPA
6
+
7
+ ## The Dependency Rule
8
+
9
+ Dependencies point inward only. Outer layers depend on inner layers, never the reverse.
10
+
11
+ **Layers (innermost to outermost):**
12
+ 1. Entities — Enterprise business rules
13
+ 2. Use Cases — Application business rules
14
+ 3. Adapters — Interface converters (gateways, components)
15
+ 4. Infrastructure — Frameworks, drivers, composition root
16
+
17
+ ## Directory Structure
18
+
19
+ ```
20
+ src/
21
+ ├── entities/ # Layer 1: Business domain entities
22
+ ├── use-cases/ # Layer 2: Application business rules
23
+ ├── gateways/ # Layer 3: Backend-for-Frontend (BFF) API adapters
24
+ ├── app/ # Layer 4: Next.js App Router (pages, layouts)
25
+ │ ├── api/ # BFF API routes (proxy to backend services)
26
+ │ ├── (routes)/ # Page routes
27
+ │ └── layout.tsx # Root layout
28
+ ├── components/ # Layer 3: React components (UI adapters)
29
+ │ ├── ui/ # Primitive UI components
30
+ │ └── features/ # Feature-specific components
31
+ ├── hooks/ # Layer 3: React hooks for data fetching
32
+ └── lib/ # Layer 3: Configuration and utilities
33
+ ```
34
+
35
+ ## Layer 1: Entities
36
+
37
+ **Location:** `src/entities/`
38
+
39
+ - MUST NOT import from any other layer
40
+ - MUST NOT depend on frameworks or infrastructure
41
+ - MUST NOT use non-deterministic or side-effect-producing global APIs (e.g., `crypto.randomUUID()`, `Date.now()`, `Math.random()`)
42
+ - MAY use pure, deterministic global APIs (e.g., `Intl.NumberFormat`, `parseInt()`, `JSON.parse()`)
43
+ - MUST be plain TypeScript objects/classes with business logic
44
+ - MAY contain validation and business rules
45
+
46
+ ```typescript
47
+ // src/entities/product.ts
48
+ export interface Product {
49
+ readonly id: string;
50
+ readonly name: string;
51
+ readonly price: number;
52
+ readonly inStock: boolean;
53
+ }
54
+
55
+ export function isAvailableForPurchase(product: Product): boolean {
56
+ return product.inStock && product.price > 0;
57
+ }
58
+
59
+ export function formatPrice(product: Product): string {
60
+ return new Intl.NumberFormat('en-US', {
61
+ style: 'currency',
62
+ currency: 'USD',
63
+ }).format(product.price);
64
+ }
65
+ ```
66
+
67
+ **Violations:**
68
+ - Importing React, Next.js, or any framework
69
+ - Importing from `src/use-cases/`, `src/gateways/`, `src/components/`, or `src/lib/`
70
+ - HTTP calls or API operations
71
+ - Using non-deterministic global APIs like `crypto.randomUUID()`, `Date.now()`, or `Math.random()`
72
+
73
+ ## Layer 2: Use Cases
74
+
75
+ **Location:** `src/use-cases/`
76
+
77
+ - MUST only import from entities and ports (interfaces)
78
+ - MUST define input/output DTOs
79
+ - MUST define ports for external dependencies
80
+ - MUST NOT import concrete implementations
81
+
82
+ ```typescript
83
+ // src/use-cases/get-products.ts
84
+ import type { Product } from '../entities/product';
85
+
86
+ export interface GetProductsInput {
87
+ category?: string;
88
+ limit?: number;
89
+ }
90
+
91
+ export interface GetProductsOutput {
92
+ products: Product[];
93
+ total: number;
94
+ }
95
+
96
+ // Port - interface for the API gateway
97
+ export interface ProductApiGateway {
98
+ fetchProducts(category?: string, limit?: number): Promise<{ products: Product[]; total: number }>;
99
+ }
100
+
101
+ export class GetProductsUseCase {
102
+ constructor(private readonly productApi: ProductApiGateway) {}
103
+
104
+ async execute(input: GetProductsInput): Promise<GetProductsOutput> {
105
+ const { products, total } = await this.productApi.fetchProducts(
106
+ input.category,
107
+ input.limit
108
+ );
109
+
110
+ return { products, total };
111
+ }
112
+ }
113
+ ```
114
+
115
+ **Violations:**
116
+ - Importing React, Next.js, or any framework
117
+ - Importing from `gateways/`, `components/`, or `lib/`
118
+ - Making HTTP calls directly
119
+
120
+ ## Layer 3: Adapters
121
+
122
+ **Location:** `src/gateways/`, `src/components/`, `src/hooks/`, and `src/lib/`
123
+
124
+ ### Gateways (Backend-for-Frontend API Adapters)
125
+
126
+ Gateways act as the BFF layer, calling your backend API routes and transforming data for the frontend.
127
+
128
+ ```typescript
129
+ // src/gateways/product-api-gateway.ts
130
+ import { z } from 'zod';
131
+ import type { Product } from '@/entities/product';
132
+ import type { ProductApiGateway } from '@/use-cases/get-products';
133
+
134
+ // Schema for runtime validation of API responses
135
+ const ProductSchema = z.object({
136
+ id: z.string(),
137
+ name: z.string(),
138
+ price: z.number(),
139
+ inStock: z.boolean(),
140
+ });
141
+
142
+ const ProductsResponseSchema = z.object({
143
+ products: z.array(ProductSchema),
144
+ total: z.number(),
145
+ });
146
+
147
+ export function createProductApiGateway(baseUrl: string): ProductApiGateway {
148
+ return {
149
+ async fetchProducts(
150
+ category?: string,
151
+ limit?: number
152
+ ): Promise<{ products: Product[]; total: number }> {
153
+ const params = new URLSearchParams();
154
+ if (category) params.set('category', category);
155
+ if (limit) params.set('limit', String(limit));
156
+
157
+ const response = await fetch(`${baseUrl}/api/products?${params}`);
158
+
159
+ if (!response.ok) {
160
+ throw new Error(`Failed to fetch products: ${response.status}`);
161
+ }
162
+
163
+ const data: unknown = await response.json();
164
+ return ProductsResponseSchema.parse(data);
165
+ },
166
+ };
167
+ }
168
+ ```
169
+
170
+ ### React Hooks (Data Fetching Adapters)
171
+
172
+ Hooks wire use cases to React components and manage loading/error states. Use factory functions to enable testing with different implementations.
173
+
174
+ ```typescript
175
+ // src/hooks/use-products.ts
176
+ 'use client';
177
+
178
+ import { useState, useEffect } from 'react';
179
+ import type { Product } from '@/entities/product';
180
+ import type { GetProductsUseCase } from '@/use-cases/get-products';
181
+
182
+ interface UseProductsDeps {
183
+ getProductsUseCase: GetProductsUseCase;
184
+ }
185
+
186
+ // Factory to create a hook with injected dependencies
187
+ export function createUseProductsHook({ getProductsUseCase }: UseProductsDeps) {
188
+ return function useProducts(category?: string) {
189
+ const [products, setProducts] = useState<Product[]>([]);
190
+ const [loading, setLoading] = useState(true);
191
+ const [error, setError] = useState<Error | null>(null);
192
+
193
+ useEffect(() => {
194
+ setLoading(true);
195
+ getProductsUseCase
196
+ .execute({ category })
197
+ .then(({ products }) => setProducts(products))
198
+ .catch(setError)
199
+ .finally(() => setLoading(false));
200
+ }, [category]);
201
+
202
+ return { products, loading, error };
203
+ };
204
+ }
205
+
206
+ // src/hooks/index.ts - Composition root for hooks
207
+ import { GetProductsUseCase } from '@/use-cases/get-products';
208
+ import { createProductApiGateway } from '@/gateways/product-api-gateway';
209
+ import { createUseProductsHook } from './use-products';
210
+
211
+ const productApi = createProductApiGateway('');
212
+ const getProductsUseCase = new GetProductsUseCase(productApi);
213
+
214
+ // Export the fully-wired hook for use in components
215
+ export const useProducts = createUseProductsHook({ getProductsUseCase });
216
+ ```
217
+
218
+ ### React Components (UI Adapters)
219
+
220
+ Components receive data as props and focus purely on presentation.
221
+
222
+ ```typescript
223
+ // src/components/features/product-list.tsx
224
+ 'use client';
225
+
226
+ import type { Product } from '@/entities/product';
227
+ import { formatPrice, isAvailableForPurchase } from '@/entities/product';
228
+
229
+ interface ProductListProps {
230
+ products: Product[];
231
+ onAddToCart: (product: Product) => void;
232
+ }
233
+
234
+ export function ProductList({ products, onAddToCart }: ProductListProps) {
235
+ return (
236
+ <ul>
237
+ {products.map((product) => (
238
+ <li key={product.id}>
239
+ <span>{product.name}</span>
240
+ <span>{formatPrice(product)}</span>
241
+ <button
242
+ onClick={() => onAddToCart(product)}
243
+ disabled={!isAvailableForPurchase(product)}
244
+ >
245
+ Add to Cart
246
+ </button>
247
+ </li>
248
+ ))}
249
+ </ul>
250
+ );
251
+ }
252
+ ```
253
+
254
+ **Violations:**
255
+ - Business logic (validation rules, pricing calculations)
256
+ - Domain decisions that should be in entities or use cases
257
+ - Direct API calls in components (use hooks instead)
258
+
259
+ ## Layer 4: Infrastructure
260
+
261
+ **Location:** `src/app/` (Next.js App Router)
262
+
263
+ The BFF API routes act as a proxy layer between your SPA and backend services.
264
+
265
+ ```typescript
266
+ // src/app/api/products/route.ts
267
+ import { NextResponse } from 'next/server';
268
+
269
+ const BACKEND_API_URL = process.env.BACKEND_API_URL || 'https://api.example.com';
270
+
271
+ export async function GET(request: Request) {
272
+ const { searchParams } = new URL(request.url);
273
+ const category = searchParams.get('category');
274
+ const limit = searchParams.get('limit');
275
+
276
+ try {
277
+ // Proxy to backend service
278
+ const backendUrl = new URL('/v1/products', BACKEND_API_URL);
279
+ if (category) backendUrl.searchParams.set('category', category);
280
+ if (limit) backendUrl.searchParams.set('limit', limit);
281
+
282
+ const response = await fetch(backendUrl.toString(), {
283
+ headers: {
284
+ 'Authorization': `Bearer ${process.env.BACKEND_API_KEY}`,
285
+ },
286
+ });
287
+
288
+ if (!response.ok) {
289
+ throw new Error(`Backend error: ${response.status}`);
290
+ }
291
+
292
+ const data = await response.json();
293
+ return NextResponse.json(data);
294
+ } catch (error) {
295
+ return NextResponse.json(
296
+ { error: error instanceof Error ? error.message : 'Unknown error' },
297
+ { status: 500 }
298
+ );
299
+ }
300
+ }
301
+ ```
302
+
303
+ ### Page Components (Composition Root)
304
+
305
+ Pages wire together hooks and components.
306
+
307
+ ```typescript
308
+ // src/app/products/page.tsx
309
+ 'use client';
310
+
311
+ import { useProducts } from '@/hooks/use-products';
312
+ import { ProductList } from '@/components/features/product-list';
313
+ import type { Product } from '@/entities/product';
314
+
315
+ export default function ProductsPage() {
316
+ const { products, loading, error } = useProducts();
317
+
318
+ const handleAddToCart = (product: Product) => {
319
+ // Handle add to cart action
320
+ console.log('Added to cart:', product.name);
321
+ };
322
+
323
+ if (loading) return <div>Loading...</div>;
324
+ if (error) return <div>Error: {error.message}</div>;
325
+
326
+ return <ProductList products={products} onAddToCart={handleAddToCart} />;
327
+ }
328
+ ```
329
+
330
+ ## Dependency Injection Patterns
331
+
332
+ ### Factory Functions (Preferred for SPA)
333
+
334
+ Factory functions create gateway instances with injected configuration.
335
+
336
+ ```typescript
337
+ // ✅ Good - Factory function with dependency injection
338
+ export function createProductApiGateway(baseUrl: string): ProductApiGateway {
339
+ return {
340
+ async fetchProducts(category?: string): Promise<{ products: Product[] }> {
341
+ const response = await fetch(`${baseUrl}/api/products`);
342
+ return response.json();
343
+ },
344
+ };
345
+ }
346
+
347
+ // Usage in tests
348
+ const mockGateway = createProductApiGateway('http://mock-api');
349
+
350
+ // ❌ Bad - Hardcoded URL (hard to test)
351
+ export const productApiGateway: ProductApiGateway = {
352
+ async fetchProducts(): Promise<{ products: Product[] }> {
353
+ const response = await fetch('/api/products'); // Hardcoded
354
+ return response.json();
355
+ },
356
+ };
357
+ ```
358
+
359
+ ### Constructor Injection for Classes
360
+
361
+ Use constructor injection when classes are preferred.
362
+
363
+ ```typescript
364
+ // ✅ Good - Constructor injection
365
+ export class GetProductsUseCase {
366
+ constructor(private readonly productApi: ProductApiGateway) {}
367
+
368
+ async execute(input: GetProductsInput): Promise<GetProductsOutput> {
369
+ return this.productApi.fetchProducts(input.category);
370
+ }
371
+ }
372
+ ```
373
+
374
+ ## Import Rules Summary
375
+
376
+ | From | Entities | Use Cases | Gateways/Hooks/Components | App (Infrastructure) |
377
+ |------|----------|-----------|---------------------------|---------------------|
378
+ | Entities | ✓ | ✗ | ✗ | ✗ |
379
+ | Use Cases | ✓ | ✓ | ✗ | ✗ |
380
+ | Gateways/Hooks/Components | ✓ | ✓ | ✓ | ✗ |
381
+ | App (Infrastructure) | ✓ | ✓ | ✓ | ✓ |
382
+
383
+ ## Anti-Patterns
384
+
385
+ **Anemic Domain Model:** Entities as data-only containers with logic in services. Put business rules in entities.
386
+
387
+ **Leaky Abstractions:** Gateways exposing fetch Response objects. Return domain types only.
388
+
389
+ **Business Logic in Components:** Authorization checks or validation in React components. Move to entities/use cases.
390
+
391
+ **Direct API Calls in Components:** Components making fetch calls directly. Use hooks or gateways.
392
+
393
+ ## Code Review Checklist
394
+
395
+ - Entities have zero imports from other layers
396
+ - Use cases define ports for all external dependencies
397
+ - Gateways implement ports and handle API communication
398
+ - Hooks wire use cases to React lifecycle
399
+ - Components receive data as props, focus on presentation
400
+ - API routes act as BFF proxy layer
401
+ - Use cases testable with simple mocks (no HTTP)