@el-j/magic-helix-plugins 4.0.0-beta.1 → 4.0.0-beta.3

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 (120) hide show
  1. package/dist/architecture/codeowners.md +123 -0
  2. package/dist/architecture/monorepo.md +146 -0
  3. package/dist/architecture/nx.md +122 -0
  4. package/dist/architecture/turborepo.md +114 -0
  5. package/dist/ci/github-actions.md +268 -0
  6. package/dist/ci/gitlab-ci.md +330 -0
  7. package/dist/containers/docker-multistage.md +120 -0
  8. package/dist/containers/kubernetes-deploy.md +210 -0
  9. package/dist/cpp/index.cjs +79 -0
  10. package/dist/cpp/index.mjs +209 -0
  11. package/dist/csharp/index.cjs +2 -2
  12. package/dist/csharp/{index.js → index.mjs} +17 -11
  13. package/dist/csharp/templates/framework-aspnetcore.md +205 -0
  14. package/dist/csharp/templates/framework-blazor.md +271 -0
  15. package/dist/csharp/templates/lang-csharp.md +162 -0
  16. package/dist/devops/docker-compose.md +111 -0
  17. package/dist/devops/docker-dockerfile.md +94 -0
  18. package/dist/devops/github-actions.md +160 -0
  19. package/dist/devops/gitlab-ci.md +210 -0
  20. package/dist/generic/lang-typescript.md +57 -0
  21. package/dist/generic/state-redux.md +21 -0
  22. package/dist/generic/state-rxjs.md +6 -0
  23. package/dist/generic/style-mui.md +23 -0
  24. package/dist/generic/style-tailwind.md +76 -0
  25. package/dist/generic/test-cypress.md +21 -0
  26. package/dist/generic/test-jest.md +20 -0
  27. package/dist/generic/test-playwright.md +21 -0
  28. package/dist/generic/test-vitest.md +131 -0
  29. package/dist/go/index.cjs +3 -3
  30. package/dist/go/{index.js → index.mjs} +18 -15
  31. package/dist/go/templates/lang-go.md +571 -0
  32. package/dist/index.cjs +1 -1
  33. package/dist/index.mjs +24 -0
  34. package/dist/java/index.cjs +2 -2
  35. package/dist/java/{index.js → index.mjs} +25 -19
  36. package/dist/java/templates/build-gradle.md +102 -0
  37. package/dist/java/templates/build-maven.md +86 -0
  38. package/dist/java/templates/framework-spring-boot.md +179 -0
  39. package/dist/java/templates/lang-java.md +78 -0
  40. package/dist/java/templates/lang-kotlin.md +88 -0
  41. package/dist/meta/magic-helix-meta.md +213 -0
  42. package/dist/meta/meta-debug.md +459 -0
  43. package/dist/meta/meta-implement.md +450 -0
  44. package/dist/meta/meta-roadmap.md +265 -0
  45. package/dist/nodejs/templates/angular-core.md +19 -0
  46. package/dist/nodejs/templates/lang-typescript.md +57 -0
  47. package/dist/nodejs/templates/nestjs-core.md +7 -0
  48. package/dist/nodejs/templates/react-core.md +677 -0
  49. package/dist/nodejs/templates/react-zustand.md +7 -0
  50. package/dist/nodejs/templates/state-redux.md +21 -0
  51. package/dist/nodejs/templates/state-rxjs.md +6 -0
  52. package/dist/nodejs/templates/style-primevue.md +6 -0
  53. package/dist/nodejs/templates/style-quasar.md +22 -0
  54. package/dist/nodejs/templates/style-tailwind.md +76 -0
  55. package/dist/nodejs/templates/test-cypress.md +21 -0
  56. package/dist/nodejs/templates/test-jest.md +20 -0
  57. package/dist/nodejs/templates/test-playwright.md +21 -0
  58. package/dist/nodejs/templates/test-vitest.md +131 -0
  59. package/dist/nodejs/templates/vue-core.md +108 -0
  60. package/dist/nodejs/templates/vue-pinia.md +5 -0
  61. package/dist/patterns/architecture/clean-architecture.md +469 -0
  62. package/dist/patterns/architecture/dependency-injection.md +517 -0
  63. package/dist/patterns/architecture/domain-driven-design.md +621 -0
  64. package/dist/patterns/architecture/layered-architecture.md +382 -0
  65. package/dist/patterns/architecture/repository-pattern.md +408 -0
  66. package/dist/patterns/domain-expertise/nextjs-rules.md +115 -0
  67. package/dist/patterns/domain-expertise/react-patterns.md +181 -0
  68. package/dist/patterns/domain-expertise/server-components.md +212 -0
  69. package/dist/patterns/domain-expertise/shadcn-ui.md +52 -0
  70. package/dist/patterns/domain-expertise/tailwind-patterns.md +52 -0
  71. package/dist/patterns/environment/container-awareness.md +17 -0
  72. package/dist/patterns/environment/ide-features.md +17 -0
  73. package/dist/patterns/environment/os-commands.md +17 -0
  74. package/dist/patterns/organization/heading-hierarchy.md +103 -0
  75. package/dist/patterns/organization/sequential-workflows.md +102 -0
  76. package/dist/patterns/organization/xml-rule-groups.md +64 -0
  77. package/dist/patterns/reasoning/agent-loop.md +151 -0
  78. package/dist/patterns/reasoning/confirmation-gates.md +141 -0
  79. package/dist/patterns/reasoning/dependency-analysis.md +132 -0
  80. package/dist/patterns/reasoning/one-tool-per-iteration.md +152 -0
  81. package/dist/patterns/reasoning/preview-before-action.md +194 -0
  82. package/dist/patterns/reasoning/reflection-checkpoints.md +166 -0
  83. package/dist/patterns/reasoning/result-verification.md +157 -0
  84. package/dist/patterns/reasoning/subtask-breakdown.md +131 -0
  85. package/dist/patterns/reasoning/thinking-tags.md +100 -0
  86. package/dist/patterns/role-definition/capability-declarations.md +72 -0
  87. package/dist/patterns/role-definition/expert-identity.md +45 -0
  88. package/dist/patterns/role-definition/scope-boundaries.md +61 -0
  89. package/dist/patterns/safety/code-safety-rules.md +17 -0
  90. package/dist/patterns/safety/credential-handling.md +17 -0
  91. package/dist/patterns/safety/destructive-warnings.md +17 -0
  92. package/dist/patterns/safety/refusal-messages.md +17 -0
  93. package/dist/patterns/tone/adaptive-tone.md +17 -0
  94. package/dist/patterns/tone/concise-communication.md +17 -0
  95. package/dist/patterns/tone/forbidden-phrases.md +17 -0
  96. package/dist/patterns/tool-guidelines/function-schemas.md +143 -0
  97. package/dist/patterns/tool-guidelines/parameter-examples.md +137 -0
  98. package/dist/patterns/tool-guidelines/usage-policies.md +105 -0
  99. package/dist/php/index.cjs +2 -2
  100. package/dist/php/{index.js → index.mjs} +12 -6
  101. package/dist/php/templates/framework-laravel.md +112 -0
  102. package/dist/php/templates/lang-php.md +94 -0
  103. package/dist/python/index.cjs +4 -4
  104. package/dist/python/{index.js → index.mjs} +10 -7
  105. package/dist/python/templates/lang-python.md +508 -0
  106. package/dist/ruby/index.cjs +2 -2
  107. package/dist/ruby/{index.js → index.mjs} +16 -10
  108. package/dist/ruby/templates/framework-rails.md +309 -0
  109. package/dist/ruby/templates/framework-sinatra.md +227 -0
  110. package/dist/ruby/templates/lang-ruby.md +216 -0
  111. package/dist/rust/index.cjs +3 -3
  112. package/dist/rust/{index.js → index.mjs} +24 -18
  113. package/dist/rust/templates/lang-rust.md +89 -0
  114. package/dist/swift/index.cjs +32 -0
  115. package/dist/swift/index.mjs +112 -0
  116. package/dist/swift/templates/framework-vapor.md +352 -0
  117. package/dist/swift/templates/lang-swift.md +291 -0
  118. package/package.json +31 -21
  119. package/dist/index.js +0 -20
  120. /package/dist/nodejs/{index.js → index.mjs} +0 -0
@@ -0,0 +1,508 @@
1
+ # Language: Python
2
+
3
+ ## Architecture: Layered Architecture for Python Applications
4
+
5
+ **Organize Python applications with clear separation of concerns: API → Services → Domain → Data Access.**
6
+
7
+ ### Layer Structure
8
+
9
+ ```
10
+ src/
11
+ ├── api/ # Presentation layer (FastAPI/Flask routes)
12
+ │ ├── routes/
13
+ │ │ ├── users.py # HTTP handlers, request/response
14
+ │ │ └── orders.py
15
+ │ └── schemas/
16
+ │ └── user_schema.py # Pydantic models for API contracts
17
+
18
+ ├── services/ # Business logic layer
19
+ │ ├── user_service.py # Orchestrates domain operations
20
+ │ └── order_service.py
21
+
22
+ ├── domain/ # Domain models and business rules
23
+ │ ├── models/
24
+ │ │ ├── user.py # Domain entities
25
+ │ │ └── order.py
26
+ │ └── value_objects/
27
+ │ └── email.py # Immutable value objects
28
+
29
+ ├── infrastructure/ # Data access and external services
30
+ │ ├── repositories/
31
+ │ │ ├── user_repository.py
32
+ │ │ └── order_repository.py
33
+ │ └── database/
34
+ │ └── db.py # Database connection
35
+
36
+ └── main.py # Application entry point
37
+ ```
38
+
39
+ ### Rule: Routes Handle HTTP Only
40
+
41
+ **ALWAYS** keep route handlers thin. They should only handle HTTP concerns (parsing, validation, serialization).
42
+
43
+ ```python
44
+ # ✅ Good: Thin route handler (FastAPI)
45
+ from fastapi import APIRouter, Depends, HTTPException
46
+ from src.services.user_service import UserService
47
+ from src.api.schemas.user_schema import CreateUserRequest, UserResponse
48
+
49
+ router = APIRouter()
50
+
51
+ @router.post("/users", response_model=UserResponse, status_code=201)
52
+ async def create_user(
53
+ request: CreateUserRequest,
54
+ user_service: UserService = Depends()
55
+ ):
56
+ """Create a new user - HTTP handler only."""
57
+ try:
58
+ user = await user_service.create_user(
59
+ email=request.email,
60
+ name=request.name
61
+ )
62
+ return UserResponse.from_domain(user)
63
+ except ValueError as e:
64
+ raise HTTPException(status_code=400, detail=str(e))
65
+
66
+ # ❌ Bad: Business logic in route
67
+ @router.post("/users")
68
+ async def create_user(request: CreateUserRequest, db: Session = Depends()):
69
+ # ❌ Validation logic in route
70
+ if "@" not in request.email:
71
+ raise HTTPException(status_code=400, detail="Invalid email")
72
+
73
+ # ❌ Database access in route
74
+ existing = db.query(UserModel).filter_by(email=request.email).first()
75
+ if existing:
76
+ raise HTTPException(status_code=400, detail="Email exists")
77
+
78
+ # ❌ Domain logic in route
79
+ user = UserModel(email=request.email, name=request.name)
80
+ db.add(user)
81
+ db.commit()
82
+
83
+ return user
84
+ ```
85
+
86
+ ### Rule: Services Orchestrate Business Logic
87
+
88
+ **ALWAYS** put business logic in services. Services coordinate between repositories and domain models.
89
+
90
+ ```python
91
+ # ✅ services/user_service.py - Business logic layer
92
+ from src.domain.models.user import User
93
+ from src.domain.value_objects.email import Email
94
+ from src.infrastructure.repositories.user_repository import IUserRepository
95
+
96
+ class UserService:
97
+ """Orchestrates user-related business operations."""
98
+
99
+ def __init__(self, user_repository: IUserRepository):
100
+ self._user_repo = user_repository
101
+
102
+ async def create_user(self, email: str, name: str) -> User:
103
+ """Create a new user with validation and business rules."""
104
+ # Validate email using value object
105
+ email_vo = Email(email) # Raises ValueError if invalid
106
+
107
+ # Business rule: Check uniqueness
108
+ existing = await self._user_repo.find_by_email(email_vo)
109
+ if existing:
110
+ raise ValueError(f"User with email {email} already exists")
111
+
112
+ # Create domain entity
113
+ user = User.create(email=email_vo, name=name)
114
+
115
+ # Persist
116
+ await self._user_repo.save(user)
117
+
118
+ return user
119
+
120
+ async def update_user_email(self, user_id: str, new_email: str) -> User:
121
+ """Update user email with validation."""
122
+ user = await self._user_repo.find_by_id(user_id)
123
+ if not user:
124
+ raise ValueError(f"User {user_id} not found")
125
+
126
+ # Use domain method for business logic
127
+ email_vo = Email(new_email)
128
+ user.change_email(email_vo)
129
+
130
+ await self._user_repo.save(user)
131
+ return user
132
+ ```
133
+
134
+ ### Rule: Domain Models Encapsulate Business Rules
135
+
136
+ **ALWAYS** put business rules and invariants in domain models. Keep them database-agnostic.
137
+
138
+ ```python
139
+ # ✅ domain/models/user.py - Domain entity
140
+ from dataclasses import dataclass
141
+ from datetime import datetime
142
+ from uuid import UUID, uuid4
143
+ from src.domain.value_objects.email import Email
144
+
145
+ @dataclass
146
+ class User:
147
+ """User domain entity with business logic."""
148
+ id: UUID
149
+ email: Email
150
+ name: str
151
+ created_at: datetime
152
+ is_active: bool = True
153
+
154
+ @classmethod
155
+ def create(cls, email: Email, name: str) -> "User":
156
+ """Factory method for creating new users."""
157
+ if not name or len(name) < 2:
158
+ raise ValueError("Name must be at least 2 characters")
159
+
160
+ return cls(
161
+ id=uuid4(),
162
+ email=email,
163
+ name=name,
164
+ created_at=datetime.utcnow(),
165
+ is_active=True
166
+ )
167
+
168
+ def change_email(self, new_email: Email) -> None:
169
+ """Change user email with validation."""
170
+ if new_email == self.email:
171
+ raise ValueError("New email must be different")
172
+
173
+ self.email = new_email
174
+
175
+ def deactivate(self) -> None:
176
+ """Deactivate user account."""
177
+ if not self.is_active:
178
+ raise ValueError("User is already inactive")
179
+
180
+ self.is_active = False
181
+
182
+ def is_admin(self) -> bool:
183
+ """Business rule: Admins have company email."""
184
+ return self.email.value.endswith("@company.com")
185
+
186
+ # ✅ domain/value_objects/email.py - Value object
187
+ @dataclass(frozen=True) # Immutable
188
+ class Email:
189
+ """Email value object with validation."""
190
+ value: str
191
+
192
+ def __post_init__(self):
193
+ """Validate email format on creation."""
194
+ if "@" not in self.value or "." not in self.value.split("@")[1]:
195
+ raise ValueError(f"Invalid email format: {self.value}")
196
+
197
+ if len(self.value) > 255:
198
+ raise ValueError("Email too long")
199
+
200
+ def __str__(self) -> str:
201
+ return self.value
202
+ ```
203
+
204
+ ### Rule: Repositories Abstract Data Access
205
+
206
+ **ALWAYS** define repository interfaces in domain layer, implement in infrastructure layer.
207
+
208
+ ```python
209
+ # ✅ domain/repositories/user_repository.py - Interface
210
+ from abc import ABC, abstractmethod
211
+ from typing import Optional
212
+ from uuid import UUID
213
+ from src.domain.models.user import User
214
+ from src.domain.value_objects.email import Email
215
+
216
+ class IUserRepository(ABC):
217
+ """Repository interface for User aggregate."""
218
+
219
+ @abstractmethod
220
+ async def save(self, user: User) -> None:
221
+ """Save or update a user."""
222
+ pass
223
+
224
+ @abstractmethod
225
+ async def find_by_id(self, user_id: UUID) -> Optional[User]:
226
+ """Find user by ID."""
227
+ pass
228
+
229
+ @abstractmethod
230
+ async def find_by_email(self, email: Email) -> Optional[User]:
231
+ """Find user by email."""
232
+ pass
233
+
234
+ @abstractmethod
235
+ async def delete(self, user_id: UUID) -> None:
236
+ """Delete a user."""
237
+ pass
238
+
239
+ # ✅ infrastructure/repositories/user_repository.py - Implementation
240
+ from sqlalchemy.ext.asyncio import AsyncSession
241
+ from sqlalchemy import select
242
+ from src.domain.repositories.user_repository import IUserRepository
243
+ from src.domain.models.user import User
244
+ from src.infrastructure.database.models import UserModel
245
+
246
+ class SQLAlchemyUserRepository(IUserRepository):
247
+ """SQLAlchemy implementation of user repository."""
248
+
249
+ def __init__(self, session: AsyncSession):
250
+ self._session = session
251
+
252
+ async def save(self, user: User) -> None:
253
+ """Save domain user to database."""
254
+ # Check if exists
255
+ stmt = select(UserModel).where(UserModel.id == user.id)
256
+ result = await self._session.execute(stmt)
257
+ db_user = result.scalar_one_or_none()
258
+
259
+ if db_user:
260
+ # Update existing
261
+ db_user.email = str(user.email)
262
+ db_user.name = user.name
263
+ db_user.is_active = user.is_active
264
+ else:
265
+ # Create new
266
+ db_user = UserModel(
267
+ id=user.id,
268
+ email=str(user.email),
269
+ name=user.name,
270
+ created_at=user.created_at,
271
+ is_active=user.is_active
272
+ )
273
+ self._session.add(db_user)
274
+
275
+ await self._session.commit()
276
+
277
+ async def find_by_id(self, user_id: UUID) -> Optional[User]:
278
+ """Find user by ID and map to domain model."""
279
+ stmt = select(UserModel).where(UserModel.id == user_id)
280
+ result = await self._session.execute(stmt)
281
+ db_user = result.scalar_one_or_none()
282
+
283
+ if not db_user:
284
+ return None
285
+
286
+ return self._map_to_domain(db_user)
287
+
288
+ async def find_by_email(self, email: Email) -> Optional[User]:
289
+ stmt = select(UserModel).where(UserModel.email == str(email))
290
+ result = await self._session.execute(stmt)
291
+ db_user = result.scalar_one_or_none()
292
+
293
+ return self._map_to_domain(db_user) if db_user else None
294
+
295
+ def _map_to_domain(self, db_user: UserModel) -> User:
296
+ """Map database model to domain model."""
297
+ return User(
298
+ id=db_user.id,
299
+ email=Email(db_user.email),
300
+ name=db_user.name,
301
+ created_at=db_user.created_at,
302
+ is_active=db_user.is_active
303
+ )
304
+ ```
305
+
306
+ ### Complete Example: FastAPI Application
307
+
308
+ ```python
309
+ # main.py - Application entry point
310
+ from fastapi import FastAPI
311
+ from src.api.routes import users, orders
312
+ from src.infrastructure.database.db import init_db
313
+
314
+ app = FastAPI()
315
+
316
+ @app.on_event("startup")
317
+ async def startup():
318
+ await init_db()
319
+
320
+ app.include_router(users.router, prefix="/api")
321
+ app.include_router(orders.router, prefix="/api")
322
+
323
+ # api/routes/users.py - HTTP layer
324
+ from fastapi import APIRouter, Depends
325
+ from src.services.user_service import UserService
326
+ from src.api.dependencies import get_user_service
327
+
328
+ router = APIRouter()
329
+
330
+ @router.post("/users")
331
+ async def create_user(
332
+ request: CreateUserRequest,
333
+ service: UserService = Depends(get_user_service)
334
+ ):
335
+ user = await service.create_user(request.email, request.name)
336
+ return UserResponse.from_domain(user)
337
+
338
+ # api/schemas/user_schema.py - API contracts
339
+ from pydantic import BaseModel, EmailStr
340
+
341
+ class CreateUserRequest(BaseModel):
342
+ email: EmailStr
343
+ name: str
344
+
345
+ class UserResponse(BaseModel):
346
+ id: str
347
+ email: str
348
+ name: str
349
+ is_active: bool
350
+
351
+ @classmethod
352
+ def from_domain(cls, user: User) -> "UserResponse":
353
+ return cls(
354
+ id=str(user.id),
355
+ email=str(user.email),
356
+ name=user.name,
357
+ is_active=user.is_active
358
+ )
359
+
360
+ # api/dependencies.py - Dependency injection
361
+ from fastapi import Depends
362
+ from sqlalchemy.ext.asyncio import AsyncSession
363
+ from src.infrastructure.database.db import get_session
364
+ from src.infrastructure.repositories.user_repository import SQLAlchemyUserRepository
365
+ from src.services.user_service import UserService
366
+
367
+ async def get_user_service(
368
+ session: AsyncSession = Depends(get_session)
369
+ ) -> UserService:
370
+ repository = SQLAlchemyUserRepository(session)
371
+ return UserService(repository)
372
+ ```
373
+
374
+ ## Python Best Practices
375
+
376
+ - **ALWAYS** use type hints for function parameters and return values.
377
+ - **ALWAYS** use descriptive variable and function names (snake_case).
378
+ - **ALWAYS** follow PEP 8 style guidelines.
379
+ - **ALWAYS** use docstrings for modules, classes, and functions.
380
+ - **ALWAYS** handle exceptions properly with specific exception types.
381
+ - **ALWAYS** use context managers (`with` statements) for resource management.
382
+ - **ALWAYS** prefer list comprehensions and generator expressions over loops when appropriate.
383
+ - **ALWAYS** use `if __name__ == "__main__":` for script entry points.
384
+ - **ALWAYS** import modules at the top of files.
385
+ - **ALWAYS** use relative imports within packages.
386
+ - **ALWAYS** validate input parameters and return values.
387
+ - **ALWAYS** use logging instead of print statements for production code.
388
+ - **ALWAYS** write unit tests with pytest or unittest.
389
+ - **ALWAYS** use virtual environments for dependency management.
390
+ - **ALWAYS** keep functions small and focused on single responsibilities.
391
+ - **ALWAYS** use dataclasses or namedtuples for simple data structures.
392
+ - **ALWAYS** prefer `pathlib` over `os.path` for path operations.
393
+ - **ALWAYS** use f-strings for string formatting.
394
+ - **ALWAYS** handle encoding explicitly when working with files.
395
+ - **ALWAYS** use type checking tools like mypy in CI/CD pipelines.
396
+
397
+ ## Testing Strategy
398
+
399
+ **Routes:** Test HTTP contracts (request/response)
400
+ ```python
401
+ # tests/api/test_users.py
402
+ import pytest
403
+ from httpx import AsyncClient
404
+ from src.main import app
405
+
406
+ @pytest.mark.asyncio
407
+ async def test_create_user():
408
+ async with AsyncClient(app=app, base_url="http://test") as client:
409
+ response = await client.post(
410
+ "/api/users",
411
+ json={"email": "test@example.com", "name": "Test User"}
412
+ )
413
+
414
+ assert response.status_code == 201
415
+ data = response.json()
416
+ assert data["email"] == "test@example.com"
417
+ assert data["name"] == "Test User"
418
+ ```
419
+
420
+ **Services:** Test business logic (mock repositories)
421
+ ```python
422
+ # tests/services/test_user_service.py
423
+ import pytest
424
+ from unittest.mock import AsyncMock
425
+ from src.services.user_service import UserService
426
+ from src.domain.value_objects.email import Email
427
+
428
+ @pytest.mark.asyncio
429
+ async def test_create_user_validates_uniqueness():
430
+ # Arrange
431
+ mock_repo = AsyncMock()
432
+ mock_repo.find_by_email.return_value = User(...) # Existing user
433
+
434
+ service = UserService(mock_repo)
435
+
436
+ # Act & Assert
437
+ with pytest.raises(ValueError, match="already exists"):
438
+ await service.create_user("test@example.com", "Test")
439
+ ```
440
+
441
+ **Domain Models:** Test business rules (no mocking)
442
+ ```python
443
+ # tests/domain/test_user.py
444
+ import pytest
445
+ from src.domain.models.user import User
446
+ from src.domain.value_objects.email import Email
447
+
448
+ def test_user_change_email():
449
+ user = User.create(Email("old@example.com"), "Test User")
450
+
451
+ user.change_email(Email("new@example.com"))
452
+
453
+ assert user.email.value == "new@example.com"
454
+
455
+ def test_user_change_email_rejects_same_email():
456
+ user = User.create(Email("test@example.com"), "Test User")
457
+
458
+ with pytest.raises(ValueError, match="must be different"):
459
+ user.change_email(Email("test@example.com"))
460
+ ```
461
+
462
+ **Repositories:** Test database operations (integration tests)
463
+ ```python
464
+ # tests/infrastructure/test_user_repository.py
465
+ import pytest
466
+ from src.infrastructure.repositories.user_repository import SQLAlchemyUserRepository
467
+ from src.domain.models.user import User
468
+ from src.domain.value_objects.email import Email
469
+
470
+ @pytest.mark.asyncio
471
+ async def test_save_and_find_user(db_session):
472
+ # Arrange
473
+ repo = SQLAlchemyUserRepository(db_session)
474
+ user = User.create(Email("test@example.com"), "Test User")
475
+
476
+ # Act
477
+ await repo.save(user)
478
+ found = await repo.find_by_id(user.id)
479
+
480
+ # Assert
481
+ assert found is not None
482
+ assert found.email.value == "test@example.com"
483
+ assert found.name == "Test User"
484
+ ```
485
+
486
+ ## Architecture Summary
487
+
488
+ 1. **API Layer** (routes, schemas) = HTTP concerns
489
+ - Parse requests, serialize responses
490
+ - Call services, map domain ↔ DTOs
491
+ - No business logic
492
+
493
+ 2. **Service Layer** (services) = Business logic orchestration
494
+ - Coordinate repositories and domain
495
+ - Transaction boundaries
496
+ - Application-specific workflows
497
+
498
+ 3. **Domain Layer** (models, value objects) = Business rules
499
+ - Entities with identity and behavior
500
+ - Value objects (immutable)
501
+ - Framework-agnostic
502
+
503
+ 4. **Infrastructure Layer** (repositories, database) = External concerns
504
+ - Database access
505
+ - External APIs
506
+ - File system
507
+
508
+ **Reference:** See `patterns/architecture/layered-architecture.md`, `patterns/architecture/repository-pattern.md`, and `patterns/architecture/domain-driven-design.md` for detailed architectural guidance.
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("../BasePlugin-odQJAKA-.cjs");class u extends r.BasePlugin{constructor(){super(...arguments),this.name="ruby",this.displayName="Ruby",this.version="3.0.0",this.priority=70}async detect(e){if(!this.fileExists(e,"Gemfile"))return null;const t=this.readFile(e,"Gemfile"),i={};if(t){const n=t.matchAll(/gem\s+['"]([^'"]+)['"](?:,\s*['"]([^'"]+)['"])?/g);for(const s of n)i[s[1]]=s[2]||"*"}return{language:"Ruby",name:this.getProjectName(e),dependencies:i,manifestFile:"Gemfile",projectPath:e}}getTemplates(){return[{name:"ruby-core",tags:["ruby"],content:`# Ruby Development Guidelines
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("node:path"),l=require("../BasePlugin-odQJAKA-.cjs");function o(i){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const t in i)if(t!=="default"){const n=Object.getOwnPropertyDescriptor(i,t);Object.defineProperty(e,t,n.get?n:{enumerable:!0,get:()=>i[t]})}}return e.default=i,Object.freeze(e)}const u=o(a);class c extends l.BasePlugin{constructor(){super(...arguments),this.name="ruby",this.displayName="Ruby",this.version="3.0.0",this.priority=70}async detect(e){if(!this.fileExists(e,"Gemfile"))return null;const t=this.readFile(e,"Gemfile"),n={};if(t){const r=t.matchAll(/gem\s+['"]([^'"]+)['"](?:,\s*['"]([^'"]+)['"])?/g);for(const s of r)n[s[1]]=s[2]||"*"}return{language:"Ruby",name:this.getProjectName(e),dependencies:n,manifestFile:"Gemfile",projectPath:e}}getTemplates(){return[{name:"ruby-core",tags:["ruby"],content:()=>this.loadTemplateFromFile(u.join(__dirname,"templates/lang-ruby.md")).then(e=>e||this.getRubyFallbackTemplate())}]}getRubyFallbackTemplate(){return`# Ruby Development Guidelines
2
2
 
3
3
  This project uses Ruby.
4
4
 
@@ -15,4 +15,4 @@ This project uses Ruby.
15
15
  ## Testing
16
16
  - Write tests with RSpec/Minitest
17
17
  - Use fixtures and factories
18
- - Aim for good coverage`}]}getDependencyTagMap(){return{rails:"rails",rspec:"rspec",sinatra:"sinatra"}}}exports.RubyPlugin=u;
18
+ - Aim for good coverage`}getDependencyTagMap(){return{rails:"rails",rspec:"rspec",sinatra:"sinatra"}}}exports.RubyPlugin=c;
@@ -1,21 +1,22 @@
1
+ import * as a from "node:path";
1
2
  import { B as r } from "../BasePlugin-6wv0hYJ9.js";
2
- class c extends r {
3
+ class m extends r {
3
4
  constructor() {
4
5
  super(...arguments), this.name = "ruby", this.displayName = "Ruby", this.version = "3.0.0", this.priority = 70;
5
6
  }
6
7
  async detect(e) {
7
8
  if (!this.fileExists(e, "Gemfile"))
8
9
  return null;
9
- const t = this.readFile(e, "Gemfile"), s = {};
10
+ const t = this.readFile(e, "Gemfile"), i = {};
10
11
  if (t) {
11
12
  const n = t.matchAll(/gem\s+['"]([^'"]+)['"](?:,\s*['"]([^'"]+)['"])?/g);
12
- for (const i of n)
13
- s[i[1]] = i[2] || "*";
13
+ for (const s of n)
14
+ i[s[1]] = s[2] || "*";
14
15
  }
15
16
  return {
16
17
  language: "Ruby",
17
18
  name: this.getProjectName(e),
18
- dependencies: s,
19
+ dependencies: i,
19
20
  manifestFile: "Gemfile",
20
21
  projectPath: e
21
22
  };
@@ -25,7 +26,14 @@ class c extends r {
25
26
  {
26
27
  name: "ruby-core",
27
28
  tags: ["ruby"],
28
- content: `# Ruby Development Guidelines
29
+ content: () => this.loadTemplateFromFile(
30
+ a.join(__dirname, "templates/lang-ruby.md")
31
+ ).then((e) => e || this.getRubyFallbackTemplate())
32
+ }
33
+ ];
34
+ }
35
+ getRubyFallbackTemplate() {
36
+ return `# Ruby Development Guidelines
29
37
 
30
38
  This project uses Ruby.
31
39
 
@@ -42,9 +50,7 @@ This project uses Ruby.
42
50
  ## Testing
43
51
  - Write tests with RSpec/Minitest
44
52
  - Use fixtures and factories
45
- - Aim for good coverage`
46
- }
47
- ];
53
+ - Aim for good coverage`;
48
54
  }
49
55
  getDependencyTagMap() {
50
56
  return {
@@ -55,5 +61,5 @@ This project uses Ruby.
55
61
  }
56
62
  }
57
63
  export {
58
- c as RubyPlugin
64
+ m as RubyPlugin
59
65
  };