@lousy-agents/cli 1.1.0 → 2.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 (32) hide show
  1. package/README.md +11 -12
  2. package/api/copilot-with-fastify/.devcontainer/devcontainer.json +90 -0
  3. package/api/copilot-with-fastify/.editorconfig +16 -0
  4. package/api/copilot-with-fastify/.github/ISSUE_TEMPLATE/feature-to-spec.yml +55 -0
  5. package/api/copilot-with-fastify/.github/copilot-instructions.md +387 -0
  6. package/api/copilot-with-fastify/.github/instructions/pipeline.instructions.md +149 -0
  7. package/api/copilot-with-fastify/.github/instructions/software-architecture.instructions.md +430 -0
  8. package/api/copilot-with-fastify/.github/instructions/spec.instructions.md +411 -0
  9. package/api/copilot-with-fastify/.github/instructions/test.instructions.md +268 -0
  10. package/api/copilot-with-fastify/.github/specs/README.md +84 -0
  11. package/api/copilot-with-fastify/.github/workflows/assign-copilot.yml +59 -0
  12. package/api/copilot-with-fastify/.github/workflows/ci.yml +88 -0
  13. package/api/copilot-with-fastify/.nvmrc +1 -0
  14. package/api/copilot-with-fastify/.vscode/extensions.json +14 -0
  15. package/api/copilot-with-fastify/.vscode/launch.json +30 -0
  16. package/api/copilot-with-fastify/.vscode/mcp.json +19 -0
  17. package/api/copilot-with-fastify/.yamllint +18 -0
  18. package/api/copilot-with-fastify/biome.json +31 -0
  19. package/api/copilot-with-fastify/package.json +37 -0
  20. package/api/copilot-with-fastify/tsconfig.json +34 -0
  21. package/api/copilot-with-fastify/vitest.config.ts +21 -0
  22. package/api/copilot-with-fastify/vitest.integration.config.ts +18 -0
  23. package/api/copilot-with-fastify/vitest.setup.ts +5 -0
  24. package/dist/commands/init.d.ts +2 -1
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +39 -45
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/lib/config.d.ts +6 -5
  29. package/dist/lib/config.d.ts.map +1 -1
  30. package/dist/lib/config.js +186 -6
  31. package/dist/lib/config.js.map +1 -1
  32. package/package.json +4 -3
@@ -0,0 +1,149 @@
1
+ ---
2
+ applyTo: ".github/workflows/*.{yml,yaml}"
3
+ ---
4
+
5
+ # Pipeline Instructions for REST API
6
+
7
+ ## MANDATORY: After Modifying Workflows
8
+
9
+ Run these validation commands in order:
10
+
11
+ ```bash
12
+ npm run lint:workflows # Validate GitHub Actions workflows with actionlint
13
+ npm run lint:yaml # Validate YAML syntax with yamllint
14
+ ```
15
+
16
+ ## Workflow Structure Requirements
17
+
18
+ 1. Every workflow MUST include test and lint jobs.
19
+ 2. Reference Node.js version from `.nvmrc` using `actions/setup-node` with `node-version-file` input.
20
+ 3. Use official setup actions: `actions/checkout`, `actions/setup-node`, `actions/cache`.
21
+ 4. Integration tests require Docker for Testcontainers.
22
+
23
+ ## Action Pinning Format
24
+
25
+ Pin ALL third-party actions to exact commit SHA with version comment:
26
+
27
+ ```yaml
28
+ # CORRECT format:
29
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
30
+
31
+ # INCORRECT formats (do NOT use):
32
+ uses: actions/checkout@v4 # ❌ version tag only
33
+ uses: actions/checkout@v4.1.1 # ❌ version tag only
34
+ uses: actions/checkout@main # ❌ branch reference
35
+ ```
36
+
37
+ Before adding any action:
38
+ 1. Check GitHub for the LATEST stable version
39
+ 2. Find the full commit SHA for that version
40
+ 3. Add both SHA and version comment
41
+
42
+ ## Runner Requirements
43
+
44
+ | Workflow | Runner |
45
+ |----------|--------|
46
+ | Default (all workflows) | `ubuntu-latest` |
47
+ | `copilot-setup-steps.yml` | May use different runners as needed |
48
+
49
+ ## Integration Tests with Testcontainers
50
+
51
+ Testcontainers manages Docker containers automatically. No explicit Docker service configuration is needed in the workflow, but Docker must be available on the runner.
52
+
53
+ ### Example CI Workflow with Integration Tests
54
+
55
+ ```yaml
56
+ name: CI
57
+
58
+ on:
59
+ push:
60
+ branches: [main]
61
+ pull_request:
62
+ branches: [main]
63
+
64
+ jobs:
65
+ lint:
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
69
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
70
+ with:
71
+ node-version-file: '.nvmrc'
72
+ cache: 'npm'
73
+ - run: npm ci
74
+ - run: npx biome check
75
+
76
+ unit-tests:
77
+ runs-on: ubuntu-latest
78
+ steps:
79
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
80
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
81
+ with:
82
+ node-version-file: '.nvmrc'
83
+ cache: 'npm'
84
+ - run: npm ci
85
+ - run: npm test
86
+
87
+ integration-tests:
88
+ runs-on: ubuntu-latest
89
+ steps:
90
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
91
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
92
+ with:
93
+ node-version-file: '.nvmrc'
94
+ cache: 'npm'
95
+ - run: npm ci
96
+ - run: npm run test:integration
97
+ env:
98
+ # Testcontainers configuration
99
+ TESTCONTAINERS_RYUK_DISABLED: false
100
+
101
+ build:
102
+ runs-on: ubuntu-latest
103
+ needs: [lint, unit-tests, integration-tests]
104
+ steps:
105
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
106
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
107
+ with:
108
+ node-version-file: '.nvmrc'
109
+ cache: 'npm'
110
+ - run: npm ci
111
+ - run: npm run build
112
+ ```
113
+
114
+ ## Testcontainers in GitHub Actions
115
+
116
+ Testcontainers works out of the box on `ubuntu-latest` runners since they have Docker pre-installed.
117
+
118
+ ### Environment Variables
119
+
120
+ | Variable | Description | Default |
121
+ |----------|-------------|---------|
122
+ | `TESTCONTAINERS_RYUK_DISABLED` | Disable Ryuk container cleanup | `false` |
123
+ | `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | Override Docker socket path | auto-detected |
124
+
125
+ ### Troubleshooting
126
+
127
+ If container startup is slow:
128
+ - Use `TESTCONTAINERS_RYUK_DISABLED=true` to skip Ryuk (manual cleanup needed)
129
+ - Consider caching Docker images in a separate step
130
+
131
+ If containers fail to start:
132
+ - Ensure Docker is available on the runner
133
+ - Check container logs for startup errors
134
+ - Increase test timeouts for container startup
135
+
136
+ ## Database Migrations in CI
137
+
138
+ Run migrations as part of the integration test setup:
139
+
140
+ ```typescript
141
+ // In test setup
142
+ beforeAll(async () => {
143
+ container = await new PostgreSqlContainer().start();
144
+ db = createConnection(container);
145
+ await runMigrations(db); // Apply schema before tests
146
+ });
147
+ ```
148
+
149
+ Do not run migrations as a separate CI step for integration tests - let Testcontainers handle the isolated database setup.
@@ -0,0 +1,430 @@
1
+ ---
2
+ applyTo: "src/**/*.ts"
3
+ ---
4
+
5
+ # Clean Architecture Instructions for REST API
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 (routes, repositories, gateways)
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: Database and external service adapters
24
+ ├── routes/ # Layer 3: Fastify route handlers
25
+ ├── plugins/ # Layer 3: Fastify plugins
26
+ ├── db/ # Layer 3: Database configuration
27
+ │ ├── connection.ts # Database connection factory
28
+ │ ├── migrations/ # Kysely migrations
29
+ │ └── types.ts # Database schema types
30
+ ├── lib/ # Layer 3: Configuration and utilities
31
+ └── index.ts # Layer 4: Composition root
32
+ ```
33
+
34
+ ## Layer 1: Entities
35
+
36
+ **Location:** `src/entities/`
37
+
38
+ - MUST NOT import from any other layer
39
+ - MUST NOT depend on frameworks or infrastructure
40
+ - MUST NOT use non-deterministic or side-effect-producing global APIs (e.g., `crypto.randomUUID()`, `Date.now()`)
41
+ - MUST be plain TypeScript objects/classes with business logic
42
+ - MAY contain validation and business rules
43
+
44
+ ```typescript
45
+ // src/entities/user.ts
46
+ export interface User {
47
+ readonly id: string;
48
+ readonly name: string;
49
+ readonly email: string;
50
+ readonly createdAt: Date;
51
+ }
52
+
53
+ export interface CreateUserInput {
54
+ readonly name: string;
55
+ readonly email: string;
56
+ }
57
+
58
+ export function isValidEmail(email: string): boolean {
59
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
60
+ }
61
+
62
+ // Note: currentDate is passed in to avoid non-deterministic Date() in entities
63
+ export function canUserBeDeleted(user: User, currentDate: Date): boolean {
64
+ const oneWeekAgo = new Date(currentDate);
65
+ oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
66
+ return user.createdAt < oneWeekAgo;
67
+ }
68
+ ```
69
+
70
+ **Violations:**
71
+ - Importing Fastify, Kysely, or any framework
72
+ - Importing from `src/use-cases/`, `src/gateways/`, `src/routes/`, or `src/lib/`
73
+ - Database operations or HTTP calls
74
+ - Using non-deterministic global APIs like `crypto.randomUUID()` or `Date.now()`
75
+
76
+ ## Layer 2: Use Cases
77
+
78
+ **Location:** `src/use-cases/`
79
+
80
+ - MUST only import from entities and ports (interfaces)
81
+ - MUST define input/output DTOs
82
+ - MUST define ports for external dependencies
83
+ - MUST NOT import concrete implementations
84
+
85
+ ```typescript
86
+ // src/use-cases/create-user.ts
87
+ import type { User, CreateUserInput } from '../entities/user.js';
88
+ import { isValidEmail } from '../entities/user.js';
89
+
90
+ export interface CreateUserOutput {
91
+ user: User;
92
+ }
93
+
94
+ // Port - interface for the repository
95
+ export interface UserRepository {
96
+ create(id: string, input: CreateUserInput): Promise<User>;
97
+ findByEmail(email: string): Promise<User | null>;
98
+ }
99
+
100
+ // Port - interface for ID generation
101
+ export interface IdGenerator {
102
+ generate(): string;
103
+ }
104
+
105
+ export function createUserUseCase(
106
+ repository: UserRepository,
107
+ idGenerator: IdGenerator
108
+ ) {
109
+ return {
110
+ async execute(input: CreateUserInput): Promise<CreateUserOutput> {
111
+ if (!input.name || input.name.trim().length === 0) {
112
+ throw new Error('Name is required');
113
+ }
114
+
115
+ if (!isValidEmail(input.email)) {
116
+ throw new Error('Invalid email format');
117
+ }
118
+
119
+ const existingUser = await repository.findByEmail(input.email);
120
+ if (existingUser) {
121
+ throw new Error('Email already exists');
122
+ }
123
+
124
+ const id = idGenerator.generate();
125
+ const user = await repository.create(id, input);
126
+ return { user };
127
+ },
128
+ };
129
+ }
130
+ ```
131
+
132
+ **Violations:**
133
+ - Importing Fastify, Kysely, or any framework
134
+ - Importing from `gateways/`, `routes/`, or `lib/`
135
+ - Making database queries or HTTP calls directly
136
+
137
+ ## Layer 3: Adapters
138
+
139
+ **Location:** `src/gateways/`, `src/routes/`, `src/plugins/`, and `src/db/`
140
+
141
+ ### Database Gateway with Kysely
142
+
143
+ ```typescript
144
+ // src/gateways/user-repository.ts
145
+ import type { Kysely } from 'kysely';
146
+ import type { User, CreateUserInput } from '../entities/user.js';
147
+ import type { UserRepository } from '../use-cases/create-user.js';
148
+ import type { Database } from '../db/types.js';
149
+
150
+ export function createUserRepository(db: Kysely<Database>): UserRepository {
151
+ return {
152
+ async create(id: string, input: CreateUserInput): Promise<User> {
153
+ const result = await db
154
+ .insertInto('users')
155
+ .values({
156
+ id,
157
+ name: input.name,
158
+ email: input.email,
159
+ created_at: new Date(),
160
+ })
161
+ .returning(['id', 'name', 'email', 'created_at as createdAt'])
162
+ .executeTakeFirstOrThrow();
163
+
164
+ return result;
165
+ },
166
+
167
+ async findByEmail(email: string): Promise<User | null> {
168
+ const result = await db
169
+ .selectFrom('users')
170
+ .select(['id', 'name', 'email', 'created_at as createdAt'])
171
+ .where('email', '=', email)
172
+ .executeTakeFirst();
173
+
174
+ return result ?? null;
175
+ },
176
+ };
177
+ }
178
+ ```
179
+
180
+ ### Database Types
181
+
182
+ ```typescript
183
+ // src/db/types.ts
184
+ export interface Database {
185
+ users: UsersTable;
186
+ posts: PostsTable;
187
+ }
188
+
189
+ export interface UsersTable {
190
+ id: string;
191
+ name: string;
192
+ email: string;
193
+ created_at: Date;
194
+ }
195
+
196
+ export interface PostsTable {
197
+ id: string;
198
+ user_id: string;
199
+ title: string;
200
+ content: string;
201
+ created_at: Date;
202
+ }
203
+ ```
204
+
205
+ ### Database Connection with Postgres.js
206
+
207
+ ```typescript
208
+ // src/db/connection.ts
209
+ import { Kysely, PostgresDialect } from 'kysely';
210
+ import postgres from 'postgres';
211
+ import type { Database } from './types.js';
212
+
213
+ export interface DatabaseConfig {
214
+ host: string;
215
+ port: number;
216
+ database: string;
217
+ username: string;
218
+ password: string;
219
+ }
220
+
221
+ export function createDatabase(config: DatabaseConfig): Kysely<Database> {
222
+ const sql = postgres({
223
+ host: config.host,
224
+ port: config.port,
225
+ database: config.database,
226
+ username: config.username,
227
+ password: config.password,
228
+ });
229
+
230
+ return new Kysely<Database>({
231
+ dialect: new PostgresDialect({ pool: sql }),
232
+ });
233
+ }
234
+ ```
235
+
236
+ ### Fastify Route Handlers
237
+
238
+ ```typescript
239
+ // src/routes/user-routes.ts
240
+ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
241
+ import { z } from 'zod';
242
+ import type { createUserUseCase } from '../use-cases/create-user.js';
243
+
244
+ const CreateUserBodySchema = z.object({
245
+ name: z.string().min(1),
246
+ email: z.string().email(),
247
+ });
248
+
249
+ type CreateUserBody = z.infer<typeof CreateUserBodySchema>;
250
+
251
+ export function createUserRoutes(
252
+ createUser: ReturnType<typeof createUserUseCase>
253
+ ) {
254
+ return async function userRoutes(app: FastifyInstance) {
255
+ app.post<{ Body: CreateUserBody }>(
256
+ '/users',
257
+ {
258
+ schema: {
259
+ body: {
260
+ type: 'object',
261
+ required: ['name', 'email'],
262
+ properties: {
263
+ name: { type: 'string', minLength: 1 },
264
+ email: { type: 'string', format: 'email' },
265
+ },
266
+ },
267
+ },
268
+ },
269
+ async (request: FastifyRequest<{ Body: CreateUserBody }>, reply: FastifyReply) => {
270
+ try {
271
+ const validated = CreateUserBodySchema.parse(request.body);
272
+ const result = await createUser.execute(validated);
273
+ return reply.status(201).send(result.user);
274
+ } catch (error) {
275
+ if (error instanceof Error && error.message === 'Email already exists') {
276
+ return reply.status(409).send({ error: error.message });
277
+ }
278
+ throw error;
279
+ }
280
+ }
281
+ );
282
+ };
283
+ }
284
+ ```
285
+
286
+ **Violations:**
287
+ - Business logic (validation rules, authorization decisions)
288
+ - Domain decisions that should be in entities or use cases
289
+ - Direct SQL strings without Kysely query builder
290
+
291
+ ## Layer 4: Infrastructure
292
+
293
+ **Location:** `src/index.ts` (composition root)
294
+
295
+ The composition root wires all dependencies together.
296
+
297
+ ```typescript
298
+ // src/index.ts
299
+ import Fastify from 'fastify';
300
+ import sensible from '@fastify/sensible';
301
+ import cors from '@fastify/cors';
302
+ import { createDatabase } from './db/connection.js';
303
+ import { createUserRepository } from './gateways/user-repository.js';
304
+ import { createUserUseCase } from './use-cases/create-user.js';
305
+ import { createUserRoutes } from './routes/user-routes.js';
306
+
307
+ async function main() {
308
+ const app = Fastify({
309
+ logger: {
310
+ level: process.env.LOG_LEVEL || 'info',
311
+ },
312
+ });
313
+
314
+ // Register plugins
315
+ await app.register(sensible);
316
+ await app.register(cors, { origin: true });
317
+
318
+ // Create database connection
319
+ const db = createDatabase({
320
+ host: process.env.DB_HOST || 'localhost',
321
+ port: Number(process.env.DB_PORT) || 5432,
322
+ database: process.env.DB_NAME || 'app',
323
+ username: process.env.DB_USER || 'postgres',
324
+ password: process.env.DB_PASSWORD || 'postgres',
325
+ });
326
+
327
+ // Create repositories
328
+ const userRepository = createUserRepository(db);
329
+
330
+ // Create ID generator
331
+ const idGenerator = { generate: () => crypto.randomUUID() };
332
+
333
+ // Create use cases
334
+ const createUser = createUserUseCase(userRepository, idGenerator);
335
+
336
+ // Register routes
337
+ await app.register(createUserRoutes(createUser));
338
+
339
+ // Health check
340
+ app.get('/health', async () => ({ status: 'ok' }));
341
+
342
+ // Start server
343
+ const port = Number(process.env.PORT) || 3000;
344
+ await app.listen({ port, host: '0.0.0.0' });
345
+
346
+ console.log(`Server listening on port ${port}`);
347
+ }
348
+
349
+ main().catch(console.error);
350
+ ```
351
+
352
+ ## Dependency Injection Patterns
353
+
354
+ ### Factory Functions (Preferred)
355
+
356
+ Factory functions create adapter instances with injected dependencies.
357
+
358
+ ```typescript
359
+ // ✅ Good - Factory function with dependency injection
360
+ export function createUserRepository(db: Kysely<Database>): UserRepository {
361
+ return {
362
+ async findById(id: string): Promise<User | null> {
363
+ const result = await db
364
+ .selectFrom('users')
365
+ .where('id', '=', id)
366
+ .selectAll()
367
+ .executeTakeFirst();
368
+ return result ?? null;
369
+ },
370
+ };
371
+ }
372
+
373
+ // Usage in composition root
374
+ const db = createDatabase(config);
375
+ const userRepository = createUserRepository(db);
376
+ const createUser = createUserUseCase(userRepository, idGenerator);
377
+ ```
378
+
379
+ ### Constructor Injection for Classes
380
+
381
+ Use constructor injection when classes are preferred.
382
+
383
+ ```typescript
384
+ // ✅ Good - Constructor injection
385
+ export class UserRepositoryImpl implements UserRepository {
386
+ constructor(private readonly db: Kysely<Database>) {}
387
+
388
+ async findById(id: string): Promise<User | null> {
389
+ const result = await this.db
390
+ .selectFrom('users')
391
+ .where('id', '=', id)
392
+ .selectAll()
393
+ .executeTakeFirst();
394
+ return result ?? null;
395
+ }
396
+ }
397
+ ```
398
+
399
+ ## Import Rules Summary
400
+
401
+ | From | Entities | Use Cases | Gateways/Routes/DB | Index (Root) |
402
+ |------|----------|-----------|-------------------|--------------|
403
+ | Entities | ✓ | ✗ | ✗ | ✗ |
404
+ | Use Cases | ✓ | ✓ | ✗ | ✗ |
405
+ | Gateways/Routes/DB | ✓ | ✓ | ✓ | ✗ |
406
+ | Index (Root) | ✓ | ✓ | ✓ | ✓ |
407
+
408
+ ## Anti-Patterns
409
+
410
+ > ⚠️ **CRITICAL: The following anti-patterns MUST ALWAYS be avoided. Violating these patterns will result in code review rejection.**
411
+
412
+ **🚫 Anemic Domain Model:** Entities as data-only containers with logic in services. Put business rules in entities. **NEVER** create entities without behavior.
413
+
414
+ **🚫 Leaky Abstractions:** Repositories exposing Kysely types or raw SQL. Return domain entities only. **NEVER** expose database implementation details outside the gateway layer.
415
+
416
+ **🚫 Business Logic in Routes:** Authorization checks or validation in route handlers. Move to entities/use cases. **NEVER** put business rules in the route layer.
417
+
418
+ **🚫 Direct Database Access in Use Cases:** Use cases making Kysely queries directly. Use repository ports. **NEVER** import database libraries in use case files.
419
+
420
+ **🚫 Raw SQL Strings:** Using template strings for SQL. Always use Kysely query builder for type safety. **NEVER** use string interpolation for SQL queries.
421
+
422
+ ## Code Review Checklist
423
+
424
+ - Entities have zero imports from other layers
425
+ - Use cases define ports for all external dependencies
426
+ - Repositories implement ports, contain no business logic
427
+ - Route handlers validate input with Zod, delegate to use cases
428
+ - Only composition root instantiates concrete implementations
429
+ - Use cases testable with simple mocks (no database, no HTTP)
430
+ - All database queries use Kysely query builder