red64-cli 0.1.0 → 0.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 (103) hide show
  1. package/dist/cli/parseArgs.d.ts.map +1 -1
  2. package/dist/cli/parseArgs.js +5 -0
  3. package/dist/cli/parseArgs.js.map +1 -1
  4. package/dist/components/init/CompleteStep.d.ts.map +1 -1
  5. package/dist/components/init/CompleteStep.js +2 -2
  6. package/dist/components/init/CompleteStep.js.map +1 -1
  7. package/dist/components/init/TestCheckStep.d.ts +16 -0
  8. package/dist/components/init/TestCheckStep.d.ts.map +1 -0
  9. package/dist/components/init/TestCheckStep.js +120 -0
  10. package/dist/components/init/TestCheckStep.js.map +1 -0
  11. package/dist/components/init/index.d.ts +1 -0
  12. package/dist/components/init/index.d.ts.map +1 -1
  13. package/dist/components/init/index.js +1 -0
  14. package/dist/components/init/index.js.map +1 -1
  15. package/dist/components/init/types.d.ts +9 -0
  16. package/dist/components/init/types.d.ts.map +1 -1
  17. package/dist/components/screens/InitScreen.d.ts.map +1 -1
  18. package/dist/components/screens/InitScreen.js +69 -6
  19. package/dist/components/screens/InitScreen.js.map +1 -1
  20. package/dist/components/screens/StartScreen.d.ts.map +1 -1
  21. package/dist/components/screens/StartScreen.js +89 -3
  22. package/dist/components/screens/StartScreen.js.map +1 -1
  23. package/dist/services/ConfigService.d.ts +1 -0
  24. package/dist/services/ConfigService.d.ts.map +1 -1
  25. package/dist/services/ConfigService.js.map +1 -1
  26. package/dist/services/ProjectDetector.d.ts +28 -0
  27. package/dist/services/ProjectDetector.d.ts.map +1 -0
  28. package/dist/services/ProjectDetector.js +236 -0
  29. package/dist/services/ProjectDetector.js.map +1 -0
  30. package/dist/services/TestRunner.d.ts +46 -0
  31. package/dist/services/TestRunner.d.ts.map +1 -0
  32. package/dist/services/TestRunner.js +85 -0
  33. package/dist/services/TestRunner.js.map +1 -0
  34. package/dist/services/index.d.ts +2 -0
  35. package/dist/services/index.d.ts.map +1 -1
  36. package/dist/services/index.js +2 -0
  37. package/dist/services/index.js.map +1 -1
  38. package/dist/types/index.d.ts +1 -0
  39. package/dist/types/index.d.ts.map +1 -1
  40. package/dist/types/index.js.map +1 -1
  41. package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
  42. package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
  43. package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
  44. package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
  45. package/framework/stacks/generic/feedback.md +80 -0
  46. package/framework/stacks/nextjs/accessibility.md +437 -0
  47. package/framework/stacks/nextjs/api.md +431 -0
  48. package/framework/stacks/nextjs/coding-style.md +282 -0
  49. package/framework/stacks/nextjs/commenting.md +226 -0
  50. package/framework/stacks/nextjs/components.md +411 -0
  51. package/framework/stacks/nextjs/conventions.md +333 -0
  52. package/framework/stacks/nextjs/css.md +310 -0
  53. package/framework/stacks/nextjs/error-handling.md +442 -0
  54. package/framework/stacks/nextjs/feedback.md +124 -0
  55. package/framework/stacks/nextjs/migrations.md +332 -0
  56. package/framework/stacks/nextjs/models.md +362 -0
  57. package/framework/stacks/nextjs/queries.md +410 -0
  58. package/framework/stacks/nextjs/responsive.md +338 -0
  59. package/framework/stacks/nextjs/tech-stack.md +177 -0
  60. package/framework/stacks/nextjs/test-writing.md +475 -0
  61. package/framework/stacks/nextjs/validation.md +467 -0
  62. package/framework/stacks/python/api.md +468 -0
  63. package/framework/stacks/python/authentication.md +342 -0
  64. package/framework/stacks/python/code-quality.md +283 -0
  65. package/framework/stacks/python/code-refactoring.md +315 -0
  66. package/framework/stacks/python/coding-style.md +462 -0
  67. package/framework/stacks/python/conventions.md +399 -0
  68. package/framework/stacks/python/error-handling.md +512 -0
  69. package/framework/stacks/python/feedback.md +92 -0
  70. package/framework/stacks/python/implement-ai-llm.md +468 -0
  71. package/framework/stacks/python/migrations.md +388 -0
  72. package/framework/stacks/python/models.md +399 -0
  73. package/framework/stacks/python/python.md +232 -0
  74. package/framework/stacks/python/queries.md +451 -0
  75. package/framework/stacks/python/structure.md +245 -58
  76. package/framework/stacks/python/tech.md +92 -35
  77. package/framework/stacks/python/testing.md +380 -0
  78. package/framework/stacks/python/validation.md +471 -0
  79. package/framework/stacks/rails/authentication.md +176 -0
  80. package/framework/stacks/rails/code-quality.md +287 -0
  81. package/framework/stacks/rails/code-refactoring.md +299 -0
  82. package/framework/stacks/rails/feedback.md +130 -0
  83. package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
  84. package/framework/stacks/rails/rails.md +301 -0
  85. package/framework/stacks/rails/rails8-best-practices.md +498 -0
  86. package/framework/stacks/rails/rails8-css.md +573 -0
  87. package/framework/stacks/rails/structure.md +140 -0
  88. package/framework/stacks/rails/tech.md +108 -0
  89. package/framework/stacks/react/code-quality.md +521 -0
  90. package/framework/stacks/react/components.md +625 -0
  91. package/framework/stacks/react/data-fetching.md +586 -0
  92. package/framework/stacks/react/feedback.md +110 -0
  93. package/framework/stacks/react/forms.md +694 -0
  94. package/framework/stacks/react/performance.md +640 -0
  95. package/framework/stacks/react/product.md +22 -9
  96. package/framework/stacks/react/state-management.md +472 -0
  97. package/framework/stacks/react/structure.md +351 -44
  98. package/framework/stacks/react/tech.md +219 -30
  99. package/framework/stacks/react/testing.md +690 -0
  100. package/package.json +1 -1
  101. package/framework/stacks/node/product.md +0 -27
  102. package/framework/stacks/node/structure.md +0 -82
  103. package/framework/stacks/node/tech.md +0 -63
@@ -0,0 +1,512 @@
1
+ # Error Handling Patterns
2
+
3
+ Structured error handling for Python applications with FastAPI, custom exceptions, and observability.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Fail fast**: Validate inputs early, raise immediately on invalid state
10
+ - **Typed exceptions**: Custom hierarchy over generic `Exception`
11
+ - **Centralized handling**: Exception handlers at API boundary, not scattered try/except
12
+ - **Structured logging**: Machine-readable logs with context, not print statements
13
+ - **User-safe messages**: Never expose stack traces or internal details to clients
14
+
15
+ ---
16
+
17
+ ## Custom Exception Hierarchy
18
+
19
+ ### Base Exceptions
20
+
21
+ ```python
22
+ # app/exceptions.py
23
+ from typing import Any
24
+
25
+
26
+ class AppError(Exception):
27
+ """Base exception for all application errors."""
28
+
29
+ def __init__(
30
+ self,
31
+ message: str,
32
+ code: str = "INTERNAL_ERROR",
33
+ status_code: int = 500,
34
+ details: dict[str, Any] | None = None,
35
+ ):
36
+ super().__init__(message)
37
+ self.message = message
38
+ self.code = code
39
+ self.status_code = status_code
40
+ self.details = details or {}
41
+
42
+
43
+ class NotFoundError(AppError):
44
+ def __init__(self, resource: str, identifier: str):
45
+ super().__init__(
46
+ message=f"{resource} not found: {identifier}",
47
+ code="NOT_FOUND",
48
+ status_code=404,
49
+ details={"resource": resource, "identifier": identifier},
50
+ )
51
+
52
+
53
+ class ConflictError(AppError):
54
+ def __init__(self, message: str, details: dict[str, Any] | None = None):
55
+ super().__init__(message=message, code="CONFLICT", status_code=409, details=details)
56
+
57
+
58
+ class ValidationError(AppError):
59
+ def __init__(self, message: str, field_errors: dict[str, str] | None = None):
60
+ super().__init__(
61
+ message=message,
62
+ code="VALIDATION_ERROR",
63
+ status_code=422,
64
+ details={"field_errors": field_errors or {}},
65
+ )
66
+
67
+
68
+ class AuthenticationError(AppError):
69
+ def __init__(self, message: str = "Authentication required"):
70
+ super().__init__(message=message, code="UNAUTHENTICATED", status_code=401)
71
+
72
+
73
+ class AuthorizationError(AppError):
74
+ def __init__(self, message: str = "Insufficient permissions"):
75
+ super().__init__(message=message, code="FORBIDDEN", status_code=403)
76
+
77
+
78
+ class ExternalServiceError(AppError):
79
+ def __init__(self, service: str, message: str):
80
+ super().__init__(
81
+ message=f"External service error ({service}): {message}",
82
+ code="EXTERNAL_SERVICE_ERROR",
83
+ status_code=502,
84
+ details={"service": service},
85
+ )
86
+ ```
87
+
88
+ ### Usage in Services
89
+
90
+ ```python
91
+ class UserService:
92
+ async def get_user(self, user_id: int) -> User:
93
+ user = await self.repo.get(user_id)
94
+ if user is None:
95
+ raise NotFoundError("User", str(user_id))
96
+ return user
97
+
98
+ async def create_user(self, data: CreateUserRequest) -> User:
99
+ existing = await self.repo.get_by_email(data.email)
100
+ if existing:
101
+ raise ConflictError(
102
+ "Email already registered",
103
+ details={"email": data.email},
104
+ )
105
+ return await self.repo.save(User(**data.model_dump()))
106
+ ```
107
+
108
+ ---
109
+
110
+ ## FastAPI Exception Handlers
111
+
112
+ ### Centralized Handler Registration
113
+
114
+ ```python
115
+ # app/middleware/error_handler.py
116
+ import structlog
117
+ from fastapi import FastAPI, Request
118
+ from fastapi.responses import JSONResponse
119
+ from pydantic import ValidationError as PydanticValidationError
120
+
121
+ from app.exceptions import AppError
122
+
123
+ logger = structlog.get_logger()
124
+
125
+
126
+ def register_error_handlers(app: FastAPI) -> None:
127
+ @app.exception_handler(AppError)
128
+ async def app_error_handler(request: Request, exc: AppError) -> JSONResponse:
129
+ logger.warning(
130
+ "app_error",
131
+ code=exc.code,
132
+ message=exc.message,
133
+ status_code=exc.status_code,
134
+ path=request.url.path,
135
+ **exc.details,
136
+ )
137
+ return JSONResponse(
138
+ status_code=exc.status_code,
139
+ content={
140
+ "error": {
141
+ "code": exc.code,
142
+ "message": exc.message,
143
+ "details": exc.details,
144
+ },
145
+ },
146
+ )
147
+
148
+ @app.exception_handler(PydanticValidationError)
149
+ async def pydantic_error_handler(
150
+ request: Request, exc: PydanticValidationError,
151
+ ) -> JSONResponse:
152
+ return JSONResponse(
153
+ status_code=422,
154
+ content={
155
+ "error": {
156
+ "code": "VALIDATION_ERROR",
157
+ "message": "Request validation failed",
158
+ "details": {"errors": exc.errors()},
159
+ },
160
+ },
161
+ )
162
+
163
+ @app.exception_handler(Exception)
164
+ async def unhandled_error_handler(request: Request, exc: Exception) -> JSONResponse:
165
+ logger.exception(
166
+ "unhandled_error",
167
+ path=request.url.path,
168
+ method=request.method,
169
+ error=str(exc),
170
+ )
171
+ return JSONResponse(
172
+ status_code=500,
173
+ content={
174
+ "error": {
175
+ "code": "INTERNAL_ERROR",
176
+ "message": "An unexpected error occurred",
177
+ },
178
+ },
179
+ )
180
+ ```
181
+
182
+ ### Registration in App
183
+
184
+ ```python
185
+ # app/main.py
186
+ from app.middleware.error_handler import register_error_handlers
187
+
188
+ app = FastAPI()
189
+ register_error_handlers(app)
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Structured Error Response
195
+
196
+ ### Consistent Format
197
+
198
+ All error responses follow the same structure:
199
+
200
+ ```json
201
+ {
202
+ "error": {
203
+ "code": "NOT_FOUND",
204
+ "message": "User not found: 42",
205
+ "details": {
206
+ "resource": "User",
207
+ "identifier": "42"
208
+ }
209
+ }
210
+ }
211
+ ```
212
+
213
+ ### Error Response Schema
214
+
215
+ ```python
216
+ # app/schemas/error.py
217
+ from pydantic import BaseModel
218
+
219
+
220
+ class ErrorDetail(BaseModel):
221
+ code: str
222
+ message: str
223
+ details: dict | None = None
224
+
225
+
226
+ class ErrorResponse(BaseModel):
227
+ error: ErrorDetail
228
+ ```
229
+
230
+ Use in OpenAPI docs:
231
+
232
+ ```python
233
+ @router.get(
234
+ "/users/{user_id}",
235
+ responses={
236
+ 404: {"model": ErrorResponse, "description": "User not found"},
237
+ },
238
+ )
239
+ async def get_user(user_id: int) -> UserResponse:
240
+ ...
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Fail-Fast Validation
246
+
247
+ ### Early Returns
248
+
249
+ ```python
250
+ async def publish_post(self, post_id: int, user: User) -> Post:
251
+ post = await self.repo.get(post_id)
252
+ if post is None:
253
+ raise NotFoundError("Post", str(post_id))
254
+ if post.user_id != user.id:
255
+ raise AuthorizationError("Cannot publish another user's post")
256
+ if post.status == "published":
257
+ raise ConflictError("Post is already published")
258
+ if not post.title or not post.body:
259
+ raise ValidationError("Post must have title and body to publish")
260
+
261
+ post.status = "published"
262
+ post.published_at = datetime.now(timezone.utc)
263
+ await self.repo.save(post)
264
+ return post
265
+ ```
266
+
267
+ ### Guard Clauses in Utilities
268
+
269
+ ```python
270
+ def parse_pagination(page: int, per_page: int) -> tuple[int, int]:
271
+ if page < 1:
272
+ raise ValidationError("Page must be >= 1", field_errors={"page": "Must be positive"})
273
+ if per_page < 1 or per_page > 100:
274
+ raise ValidationError(
275
+ "per_page must be between 1 and 100",
276
+ field_errors={"per_page": "Must be 1-100"},
277
+ )
278
+ return page, per_page
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Retry Strategies with tenacity
284
+
285
+ ### External Service Calls
286
+
287
+ ```python
288
+ from tenacity import (
289
+ retry,
290
+ stop_after_attempt,
291
+ wait_exponential,
292
+ retry_if_exception_type,
293
+ )
294
+ import httpx
295
+
296
+
297
+ class PaymentClient:
298
+ @retry(
299
+ stop=stop_after_attempt(3),
300
+ wait=wait_exponential(multiplier=1, min=1, max=10),
301
+ retry=retry_if_exception_type((httpx.TimeoutException, httpx.HTTPStatusError)),
302
+ reraise=True,
303
+ )
304
+ async def charge(self, amount_cents: int, token: str) -> dict:
305
+ async with httpx.AsyncClient(timeout=10.0) as client:
306
+ response = await client.post(
307
+ "https://api.payments.example.com/charges",
308
+ json={"amount": amount_cents, "token": token},
309
+ )
310
+ response.raise_for_status()
311
+ return response.json()
312
+ ```
313
+
314
+ ### Custom Retry with Logging
315
+
316
+ ```python
317
+ from tenacity import before_sleep_log, after_log
318
+ import logging
319
+
320
+ logger = logging.getLogger(__name__)
321
+
322
+ @retry(
323
+ stop=stop_after_attempt(3),
324
+ wait=wait_exponential(multiplier=1, min=2, max=30),
325
+ before_sleep=before_sleep_log(logger, logging.WARNING),
326
+ after=after_log(logger, logging.INFO),
327
+ )
328
+ async def fetch_external_data(url: str) -> dict:
329
+ ...
330
+ ```
331
+
332
+ ### When NOT to Retry
333
+
334
+ | Scenario | Retry? | Reason |
335
+ |---|---|---|
336
+ | Network timeout | Yes | Transient failure |
337
+ | 5xx server error | Yes | Server may recover |
338
+ | 4xx client error | No | Request is wrong, retrying won't help |
339
+ | Authentication failure | No | Credentials are invalid |
340
+ | Validation error | No | Input is invalid |
341
+ | Database constraint violation | No | Data conflict, not transient |
342
+
343
+ ---
344
+
345
+ ## Structured Logging with structlog
346
+
347
+ ### Configuration
348
+
349
+ ```python
350
+ # app/logging_config.py
351
+ import structlog
352
+
353
+ structlog.configure(
354
+ processors=[
355
+ structlog.contextvars.merge_contextvars,
356
+ structlog.stdlib.filter_by_level,
357
+ structlog.stdlib.add_logger_name,
358
+ structlog.stdlib.add_log_level,
359
+ structlog.stdlib.PositionalArgumentsFormatter(),
360
+ structlog.processors.TimeStamper(fmt="iso"),
361
+ structlog.processors.StackInfoRenderer(),
362
+ structlog.processors.format_exc_info,
363
+ structlog.processors.UnicodeDecoder(),
364
+ structlog.processors.JSONRenderer(), # JSON in production
365
+ ],
366
+ wrapper_class=structlog.stdlib.BoundLogger,
367
+ context_class=dict,
368
+ logger_factory=structlog.stdlib.LoggerFactory(),
369
+ )
370
+ ```
371
+
372
+ ### Logging with Context
373
+
374
+ ```python
375
+ import structlog
376
+
377
+ logger = structlog.get_logger()
378
+
379
+ async def create_order(self, user_id: int, items: list[dict]) -> Order:
380
+ log = logger.bind(user_id=user_id, item_count=len(items))
381
+ log.info("creating_order")
382
+
383
+ try:
384
+ order = await self._build_order(user_id, items)
385
+ log.info("order_created", order_id=order.id, total=order.total_cents)
386
+ return order
387
+ except ExternalServiceError:
388
+ log.error("order_creation_failed", reason="payment_service_unavailable")
389
+ raise
390
+ ```
391
+
392
+ ### Request Context Middleware
393
+
394
+ ```python
395
+ import uuid
396
+ import structlog
397
+ from starlette.middleware.base import BaseHTTPMiddleware
398
+
399
+ class RequestContextMiddleware(BaseHTTPMiddleware):
400
+ async def dispatch(self, request, call_next):
401
+ request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
402
+ structlog.contextvars.clear_contextvars()
403
+ structlog.contextvars.bind_contextvars(
404
+ request_id=request_id,
405
+ method=request.method,
406
+ path=request.url.path,
407
+ )
408
+ response = await call_next(request)
409
+ response.headers["X-Request-ID"] = request_id
410
+ return response
411
+ ```
412
+
413
+ ---
414
+
415
+ ## Error Context Propagation
416
+
417
+ ### Chaining Exceptions
418
+
419
+ ```python
420
+ try:
421
+ result = await external_client.fetch(resource_id)
422
+ except httpx.HTTPError as exc:
423
+ raise ExternalServiceError("resource-api", str(exc)) from exc
424
+ ```
425
+
426
+ The `from exc` preserves the original traceback for debugging while presenting a clean error to the caller.
427
+
428
+ ### Adding Context to Errors
429
+
430
+ ```python
431
+ async def process_batch(items: list[dict]) -> list[Result]:
432
+ results = []
433
+ for i, item in enumerate(items):
434
+ try:
435
+ result = await process_item(item)
436
+ results.append(result)
437
+ except AppError as exc:
438
+ exc.details["batch_index"] = i
439
+ exc.details["item_id"] = item.get("id")
440
+ raise
441
+ return results
442
+ ```
443
+
444
+ ---
445
+
446
+ ## Result Pattern (Alternative to Exceptions)
447
+
448
+ ### For Expected Failures
449
+
450
+ ```python
451
+ from dataclasses import dataclass
452
+ from typing import Generic, TypeVar
453
+
454
+ T = TypeVar("T")
455
+
456
+ @dataclass(frozen=True)
457
+ class Ok(Generic[T]):
458
+ value: T
459
+ is_ok: bool = True
460
+
461
+ @dataclass(frozen=True)
462
+ class Err:
463
+ error: str
464
+ code: str = "ERROR"
465
+ is_ok: bool = False
466
+
467
+ type Result[T] = Ok[T] | Err
468
+ ```
469
+
470
+ ### Usage
471
+
472
+ ```python
473
+ async def create_user(self, data: CreateUserRequest) -> Result[User]:
474
+ existing = await self.repo.get_by_email(data.email)
475
+ if existing:
476
+ return Err("Email already registered", code="DUPLICATE_EMAIL")
477
+
478
+ user = User(**data.model_dump())
479
+ await self.repo.save(user)
480
+ return Ok(user)
481
+
482
+ # Caller
483
+ result = await service.create_user(data)
484
+ if result.is_ok:
485
+ return UserResponse.model_validate(result.value)
486
+ else:
487
+ raise ConflictError(result.error)
488
+ ```
489
+
490
+ ### When to Use Each
491
+
492
+ | Pattern | Use Case |
493
+ |---|---|
494
+ | Exceptions | Unexpected failures, infrastructure errors, auth failures |
495
+ | Result | Expected business logic outcomes (duplicate email, insufficient funds) |
496
+
497
+ ---
498
+
499
+ ## Anti-Patterns
500
+
501
+ | Anti-Pattern | Problem | Correct Approach |
502
+ |---|---|---|
503
+ | Bare `except:` | Catches `SystemExit`, `KeyboardInterrupt` | Catch specific exceptions |
504
+ | `except Exception: pass` | Silently swallows errors | Log and re-raise or handle |
505
+ | Returning error strings | No type safety, easy to ignore | Use Result type or raise |
506
+ | Stack traces in API responses | Security risk, bad UX | Return error codes and messages |
507
+ | Try/except around every function | Hard to read, hides flow | Centralized handlers |
508
+ | Generic `HTTPException(500)` | No error classification | Use typed exception hierarchy |
509
+
510
+ ---
511
+
512
+ _Errors are data. Classify them, log them with context, and present them consistently. Never swallow exceptions silently._
@@ -0,0 +1,92 @@
1
+ # Feedback Configuration
2
+
3
+ Project-specific commands for automated feedback during Python implementation.
4
+
5
+ ---
6
+
7
+ ## Test Commands
8
+
9
+ Commands to run tests during implementation. The agent will use these to verify code changes.
10
+
11
+ ```yaml
12
+ # Primary test command (REQUIRED)
13
+ test: uv run pytest
14
+
15
+ # Test with coverage report
16
+ test_coverage: uv run pytest --cov=src --cov-report=term-missing
17
+
18
+ # Run specific test file (use {file} as placeholder)
19
+ test_file: uv run pytest {file} -v
20
+
21
+ # Run tests matching pattern
22
+ test_pattern: uv run pytest -k "{pattern}"
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Linting Commands
28
+
29
+ Commands for code quality checks.
30
+
31
+ ```yaml
32
+ # Primary lint command (Ruff for speed)
33
+ lint: uv run ruff check .
34
+
35
+ # Lint with auto-fix
36
+ lint_fix: uv run ruff check . --fix
37
+
38
+ # Type checking
39
+ type_check: uv run mypy src/
40
+
41
+ # Format check
42
+ format_check: uv run ruff format --check .
43
+
44
+ # Format fix
45
+ format_fix: uv run ruff format .
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Development Server
51
+
52
+ Commands for starting the development server (required for UI verification).
53
+
54
+ ```yaml
55
+ # Start dev server (FastAPI)
56
+ dev_server: uv run uvicorn src.app.main:app --reload
57
+
58
+ # Dev server port
59
+ dev_port: 8000
60
+
61
+ # Dev server base URL
62
+ dev_url: http://localhost:8000
63
+ ```
64
+
65
+ ---
66
+
67
+ ## UI Verification
68
+
69
+ Settings for agent-browser UI verification.
70
+
71
+ ```yaml
72
+ # Enable UI verification (typically false for API-only projects)
73
+ ui_verification_enabled: false
74
+
75
+ # Default wait time after navigation (milliseconds)
76
+ navigation_wait: 2000
77
+
78
+ # Screenshot directory
79
+ screenshot_dir: /tmp/ui-captures
80
+
81
+ # API documentation URL (for API projects)
82
+ api_docs_url: http://localhost:8000/docs
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Notes
88
+
89
+ - Uses `uv` as the package manager (faster than pip)
90
+ - Ruff replaces flake8, isort, and black for linting/formatting
91
+ - pytest-asyncio runs async tests automatically with `asyncio_mode = "auto"`
92
+ - For API-only projects, UI verification is disabled by default