@malamute/ai-rules 1.0.0 → 1.2.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 (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,284 @@
1
+ ---
2
+ paths:
3
+ - "tests/**/*.py"
4
+ - "**/test_*.py"
5
+ ---
6
+
7
+ # Flask Testing Patterns
8
+
9
+ ## Test Configuration
10
+
11
+ ```python
12
+ # conftest.py
13
+ import pytest
14
+ from app import create_app, db
15
+
16
+ @pytest.fixture(scope="session")
17
+ def app():
18
+ """Create application for testing."""
19
+ app = create_app("testing")
20
+ return app
21
+
22
+ @pytest.fixture
23
+ def client(app):
24
+ """Create test client."""
25
+ return app.test_client()
26
+
27
+ @pytest.fixture
28
+ def runner(app):
29
+ """Create CLI test runner."""
30
+ return app.test_cli_runner()
31
+
32
+ @pytest.fixture
33
+ def db_session(app):
34
+ """Create database session for testing."""
35
+ with app.app_context():
36
+ db.create_all()
37
+ yield db.session
38
+ db.session.rollback()
39
+ db.drop_all()
40
+ ```
41
+
42
+ ## Testing Config
43
+
44
+ ```python
45
+ # config.py
46
+ class TestingConfig:
47
+ TESTING = True
48
+ SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
49
+ WTF_CSRF_ENABLED = False
50
+ SECRET_KEY = "test-secret-key"
51
+ ```
52
+
53
+ ## API Endpoint Tests
54
+
55
+ ```python
56
+ class TestUsersAPI:
57
+ def test_create_user(self, client, db_session):
58
+ response = client.post("/api/v1/users", json={
59
+ "email": "test@example.com",
60
+ "password": "password123",
61
+ "name": "Test User",
62
+ })
63
+
64
+ assert response.status_code == 201
65
+ data = response.get_json()
66
+ assert data["email"] == "test@example.com"
67
+ assert "id" in data
68
+ assert "password" not in data
69
+
70
+ def test_create_user_duplicate_email(self, client, db_session):
71
+ # Create existing user
72
+ user = User(email="existing@example.com", name="Existing")
73
+ db_session.add(user)
74
+ db_session.commit()
75
+
76
+ response = client.post("/api/v1/users", json={
77
+ "email": "existing@example.com",
78
+ "password": "password123",
79
+ "name": "New User",
80
+ })
81
+
82
+ assert response.status_code == 409
83
+
84
+ def test_get_user_not_found(self, client):
85
+ response = client.get("/api/v1/users/99999")
86
+ assert response.status_code == 404
87
+
88
+ def test_list_users_pagination(self, client, db_session):
89
+ # Create users
90
+ for i in range(25):
91
+ db_session.add(User(email=f"user{i}@example.com", name=f"User {i}"))
92
+ db_session.commit()
93
+
94
+ response = client.get("/api/v1/users?page=2&size=10")
95
+
96
+ assert response.status_code == 200
97
+ data = response.get_json()
98
+ assert len(data["items"]) == 10
99
+ assert data["total"] == 25
100
+ ```
101
+
102
+ ## Authentication Tests
103
+
104
+ ```python
105
+ @pytest.fixture
106
+ def auth_headers(client, db_session):
107
+ """Create authenticated user and return headers."""
108
+ user = User(email="auth@example.com", name="Auth User")
109
+ user.set_password("password123")
110
+ db_session.add(user)
111
+ db_session.commit()
112
+
113
+ response = client.post("/api/v1/auth/login", json={
114
+ "email": "auth@example.com",
115
+ "password": "password123",
116
+ })
117
+ token = response.get_json()["access_token"]
118
+
119
+ return {"Authorization": f"Bearer {token}"}
120
+
121
+ class TestAuthenticatedEndpoints:
122
+ def test_get_me_unauthorized(self, client):
123
+ response = client.get("/api/v1/users/me")
124
+ assert response.status_code == 401
125
+
126
+ def test_get_me_authorized(self, client, auth_headers):
127
+ response = client.get("/api/v1/users/me", headers=auth_headers)
128
+ assert response.status_code == 200
129
+ assert response.get_json()["email"] == "auth@example.com"
130
+ ```
131
+
132
+ ## Form Tests
133
+
134
+ ```python
135
+ def test_login_form(client, db_session):
136
+ # Create user
137
+ user = User(email="test@example.com", name="Test")
138
+ user.set_password("password")
139
+ db_session.add(user)
140
+ db_session.commit()
141
+
142
+ response = client.post("/login", data={
143
+ "email": "test@example.com",
144
+ "password": "password",
145
+ }, follow_redirects=True)
146
+
147
+ assert response.status_code == 200
148
+ assert b"Dashboard" in response.data
149
+ ```
150
+
151
+ ## File Upload Tests
152
+
153
+ ```python
154
+ from io import BytesIO
155
+
156
+ def test_file_upload(client, auth_headers):
157
+ data = {
158
+ "file": (BytesIO(b"file content"), "test.txt"),
159
+ }
160
+
161
+ response = client.post(
162
+ "/api/v1/files/upload",
163
+ data=data,
164
+ content_type="multipart/form-data",
165
+ headers=auth_headers,
166
+ )
167
+
168
+ assert response.status_code == 201
169
+ assert response.get_json()["filename"] == "test.txt"
170
+ ```
171
+
172
+ ## CLI Command Tests
173
+
174
+ ```python
175
+ def test_init_db_command(runner):
176
+ result = runner.invoke(args=["init-db"])
177
+ assert result.exit_code == 0
178
+ assert "Database initialized" in result.output
179
+
180
+ def test_create_user_command(runner, db_session):
181
+ result = runner.invoke(args=[
182
+ "create-user",
183
+ "test@example.com",
184
+ "Test User",
185
+ "--admin",
186
+ ])
187
+
188
+ assert result.exit_code == 0
189
+ assert "Created user" in result.output
190
+
191
+ user = User.query.filter_by(email="test@example.com").first()
192
+ assert user is not None
193
+ assert user.is_admin is True
194
+ ```
195
+
196
+ ## Mocking External Services
197
+
198
+ ```python
199
+ from unittest.mock import patch, MagicMock
200
+
201
+ def test_send_email(client, db_session):
202
+ with patch("app.services.email.mail") as mock_mail:
203
+ response = client.post("/api/v1/users", json={
204
+ "email": "test@example.com",
205
+ "password": "password123",
206
+ "name": "Test User",
207
+ })
208
+
209
+ assert response.status_code == 201
210
+ mock_mail.send.assert_called_once()
211
+
212
+ def test_external_api_call(client):
213
+ with patch("app.services.external.requests") as mock_requests:
214
+ mock_requests.get.return_value = MagicMock(
215
+ status_code=200,
216
+ json=lambda: {"data": "mocked"},
217
+ )
218
+
219
+ response = client.get("/api/v1/external-data")
220
+
221
+ assert response.status_code == 200
222
+ assert response.get_json()["data"] == "mocked"
223
+ ```
224
+
225
+ ## Parametrized Tests
226
+
227
+ ```python
228
+ @pytest.mark.parametrize("email,expected_status", [
229
+ ("valid@example.com", 201),
230
+ ("also.valid@test.co.uk", 201),
231
+ ("invalid", 400),
232
+ ("missing@", 400),
233
+ ("@nodomain.com", 400),
234
+ ])
235
+ def test_email_validation(client, email: str, expected_status: int):
236
+ response = client.post("/api/v1/users", json={
237
+ "email": email,
238
+ "password": "password123",
239
+ "name": "Test",
240
+ })
241
+
242
+ assert response.status_code == expected_status
243
+ ```
244
+
245
+ ## Test Markers
246
+
247
+ ```python
248
+ # pytest.ini or pyproject.toml
249
+ [tool.pytest.ini_options]
250
+ markers = [
251
+ "slow: marks tests as slow",
252
+ "integration: requires database",
253
+ ]
254
+
255
+ # Usage
256
+ @pytest.mark.slow
257
+ def test_heavy_computation():
258
+ ...
259
+
260
+ @pytest.mark.integration
261
+ def test_database_query(db_session):
262
+ ...
263
+
264
+ # Run specific markers
265
+ # pytest -m "not slow"
266
+ # pytest -m integration
267
+ ```
268
+
269
+ ## Coverage Configuration
270
+
271
+ ```toml
272
+ # pyproject.toml
273
+ [tool.coverage.run]
274
+ source = ["app"]
275
+ omit = ["*/tests/*", "*/__init__.py"]
276
+
277
+ [tool.coverage.report]
278
+ exclude_lines = [
279
+ "pragma: no cover",
280
+ "if TYPE_CHECKING:",
281
+ "raise NotImplementedError",
282
+ ]
283
+ fail_under = 80
284
+ ```
@@ -0,0 +1,33 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(python *)",
5
+ "Bash(python3 *)",
6
+ "Bash(flask *)",
7
+ "Bash(gunicorn *)",
8
+ "Bash(pytest *)",
9
+ "Bash(ruff *)",
10
+ "Bash(mypy *)",
11
+ "Bash(alembic *)",
12
+ "Bash(uv *)",
13
+ "Bash(poetry *)",
14
+ "Bash(pip *)",
15
+ "Bash(pip3 *)",
16
+ "Read",
17
+ "Edit",
18
+ "Write"
19
+ ],
20
+ "deny": [
21
+ "Bash(rm -rf *)",
22
+ "Read(.env)",
23
+ "Read(.env.*)",
24
+ "Read(**/secrets/**)",
25
+ "Read(**/*.pem)"
26
+ ]
27
+ },
28
+ "env": {
29
+ "FLASK_DEBUG": "1",
30
+ "PYTHONDONTWRITEBYTECODE": "1",
31
+ "PYTHONUNBUFFERED": "1"
32
+ }
33
+ }
@@ -0,0 +1,166 @@
1
+ # Flask Project Guidelines
2
+
3
+ @../_shared/CLAUDE.md
4
+
5
+ ## Stack
6
+
7
+ - Python 3.12+
8
+ - Flask 3.0+
9
+ - SQLAlchemy 2.0+ (sync or async)
10
+ - Marshmallow for validation
11
+ - pytest for testing
12
+ - uv or poetry for dependencies
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ src/app/
18
+ ├── __init__.py # App factory
19
+ ├── config.py # Configuration classes
20
+ ├── extensions.py # Flask extensions (db, migrate, etc.)
21
+ ├── [domain]/ # Feature blueprints
22
+ │ ├── __init__.py # Blueprint registration
23
+ │ ├── routes.py # Route handlers
24
+ │ ├── schemas.py # Marshmallow schemas
25
+ │ ├── models.py # SQLAlchemy models
26
+ │ ├── services.py # Business logic
27
+ │ └── repository.py # Data access
28
+ ├── core/ # Shared utilities
29
+ │ ├── exceptions.py
30
+ │ └── security.py
31
+ └── common/
32
+ ├── models.py # Base models
33
+ └── schemas.py # Shared schemas
34
+ ```
35
+
36
+ ## Flask Patterns
37
+
38
+ ### Application Factory
39
+
40
+ ```python
41
+ from flask import Flask
42
+ from app.extensions import db, migrate
43
+ from app.config import config
44
+
45
+ def create_app(config_name: str = "development") -> Flask:
46
+ app = Flask(__name__)
47
+ app.config.from_object(config[config_name])
48
+
49
+ # Initialize extensions
50
+ db.init_app(app)
51
+ migrate.init_app(app, db)
52
+
53
+ # Register blueprints
54
+ from app.users import users_bp
55
+ app.register_blueprint(users_bp, url_prefix="/api/v1/users")
56
+
57
+ # Register error handlers
58
+ register_error_handlers(app)
59
+
60
+ return app
61
+ ```
62
+
63
+ ### Blueprints
64
+
65
+ ```python
66
+ from flask import Blueprint, request, jsonify
67
+ from app.users.schemas import UserCreateSchema, UserResponseSchema
68
+ from app.users.services import UserService
69
+
70
+ users_bp = Blueprint("users", __name__)
71
+
72
+ @users_bp.route("/", methods=["POST"])
73
+ def create_user():
74
+ schema = UserCreateSchema()
75
+ data = schema.load(request.get_json())
76
+
77
+ user = UserService.create(data)
78
+
79
+ return jsonify(UserResponseSchema().dump(user)), 201
80
+
81
+ @users_bp.route("/<int:user_id>")
82
+ def get_user(user_id: int):
83
+ user = UserService.get_by_id(user_id)
84
+ if not user:
85
+ return jsonify({"error": "User not found"}), 404
86
+ return jsonify(UserResponseSchema().dump(user))
87
+ ```
88
+
89
+ ### Extensions
90
+
91
+ ```python
92
+ # extensions.py
93
+ from flask_sqlalchemy import SQLAlchemy
94
+ from flask_migrate import Migrate
95
+ from flask_jwt_extended import JWTManager
96
+
97
+ db = SQLAlchemy()
98
+ migrate = Migrate()
99
+ jwt = JWTManager()
100
+ ```
101
+
102
+ ## Marshmallow Schemas
103
+
104
+ ```python
105
+ from marshmallow import Schema, fields, validate, post_load
106
+
107
+ class UserCreateSchema(Schema):
108
+ email = fields.Email(required=True)
109
+ password = fields.Str(required=True, validate=validate.Length(min=8))
110
+ name = fields.Str(required=True, validate=validate.Length(min=1, max=100))
111
+
112
+ class UserResponseSchema(Schema):
113
+ id = fields.Int(dump_only=True)
114
+ email = fields.Email()
115
+ name = fields.Str()
116
+ created_at = fields.DateTime(dump_only=True)
117
+ ```
118
+
119
+ ## SQLAlchemy 2.0
120
+
121
+ ```python
122
+ from sqlalchemy.orm import Mapped, mapped_column
123
+ from app.extensions import db
124
+
125
+ class User(db.Model):
126
+ __tablename__ = "users"
127
+
128
+ id: Mapped[int] = mapped_column(primary_key=True)
129
+ email: Mapped[str] = mapped_column(unique=True, index=True)
130
+ hashed_password: Mapped[str]
131
+ name: Mapped[str] = mapped_column(db.String(100))
132
+ is_active: Mapped[bool] = mapped_column(default=True)
133
+ ```
134
+
135
+ ## Error Handling
136
+
137
+ ```python
138
+ from flask import jsonify
139
+ from werkzeug.exceptions import HTTPException
140
+
141
+ def register_error_handlers(app):
142
+ @app.errorhandler(HTTPException)
143
+ def handle_http_error(error):
144
+ return jsonify({
145
+ "error": error.name,
146
+ "message": error.description,
147
+ }), error.code
148
+
149
+ @app.errorhandler(ValidationError)
150
+ def handle_validation_error(error):
151
+ return jsonify({
152
+ "error": "Validation Error",
153
+ "details": error.messages,
154
+ }), 400
155
+ ```
156
+
157
+ ## Commands
158
+
159
+ ```bash
160
+ flask run # Dev server
161
+ flask db upgrade # Run migrations
162
+ flask db migrate -m "message" # Generate migration
163
+ pytest # Run tests
164
+ pytest --cov=app # Coverage
165
+ ruff check . && ruff format . # Lint + format
166
+ ```