원클릭으로
code-gen
Code generation quality principles — TDD, typing, error handling, logging, API integration, LLM integration.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Code generation quality principles — TDD, typing, error handling, logging, API integration, LLM integration.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Autonomous build loop with Karpathy ratcheting, GAN evaluator, and session chaining. Iterates story groups until all features pass or stopping criteria met.
Socratic interview to create a Business Requirements Document. First step in the SDLC pipeline.
Generate system architecture, machine-readable schemas, and UI mockups. Spawns planner + ui-designer concurrently.
Evaluation patterns — sprint contract format, three-layer verification, scoring rubric references.
Standard GitHub issue workflow. Branch, reproduce, fix, test, PR.
Generate production code and tests for a story group using agent teams for parallel execution.
| name | code-gen |
| description | Code generation quality principles — TDD, typing, error handling, logging, API integration, LLM integration. |
Reference skill for generator teammates. Read this before writing any code.
any. Use unknown + type guard if the shape is truly unknown.TypeVar, Generic, Protocol where appropriate.UserId = str, type OrderId = string).validateOrderItems, buildPaymentPayload.service.start(query) which also creates a Task → duplicate records.service.start(task_id) which operates on the existing record.assert db.query(Task).count() == 1).class OrderNotFoundError extends AppError).except Exception or catch (e: any).Result<T, E> or typed throws with JSDoc @throws.// TODO in submitted code — file a story instead.// Arrange
const order = buildOrder({ status: "pending" });
// Act
const result = processOrder(order);
// Assert
expect(result.status).toBe("confirmed");
class DomainError extends Error {
constructor(message: string, public readonly code: string) {
super(message);
this.name = this.constructor.name;
}
}
class OrderNotFoundError extends DomainError {
constructor(orderId: OrderId) {
super(`Order ${orderId} not found`, "ORDER_NOT_FOUND");
}
}
kebab-case.ts for TypeScript, snake_case.py for Python.camelCase (TS), snake_case (Python).PascalCase in both languages.UPPER_SNAKE_CASE.is, has, can, should.async_sessionmaker with FastAPI Depends() for dependency injection — never create sessions manually per request.lifespan, stored on app.state, disposed on shutdown.async with — never leave sessions open.# CORRECT — lifespan manages engine lifecycle
@asynccontextmanager
async def lifespan(app: FastAPI):
engine = create_async_engine(settings.DATABASE_URL)
app.state.session_factory = async_sessionmaker(engine)
yield
await engine.dispose()
# CORRECT — session via dependency injection
async def get_db(request: Request) -> AsyncGenerator[AsyncSession, None]:
async with request.app.state.session_factory() as session:
yield session
DbSession = Annotated[AsyncSession, Depends(get_db)]
pending, running, completed, failed).pending), then starts the background task with the record's ID.failed with error details and log the exception.# CORRECT — tracked background task
@router.post("/tasks")
async def create_task(request: TaskRequest, db: DbSession, background_tasks: BackgroundTasks):
task = Task(query=request.query, status="pending")
db.add(task)
await db.commit()
background_tasks.add_task(run_task, task.id) # pass ID, not the object
return {"task_id": task.id, "status": "pending"}
async def run_task(task_id: UUID) -> None:
async with get_session() as db:
task = await db.get(Task, task_id)
try:
task.status = "running"
await db.commit()
result = await do_work(task.query)
task.status = "completed"
task.result = result
except Exception as e:
logger.exception("Task failed", extra={"task_id": str(task_id)})
task.status = "failed"
task.error = str(e)
finally:
await db.commit()
allow_origins=["*"] with allow_credentials=True — this is a security vulnerability.localhost only.# CORRECT — env-configured CORS
allowed_origins = settings.ALLOWED_ORIGINS.split(",") # from .env
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
{"status": "ok"}.@router.get("/health")
async def health(db: DbSession) -> dict:
try:
await db.execute(text("SELECT 1"))
return {"status": "ok", "db": "connected"}
except Exception:
raise HTTPException(status_code=503, detail="Database unreachable")
request_id per request and includes it in all log entries and error responses.@app.middleware("http")
async def add_request_id(request: Request, call_next):
request_id = str(uuid4())
request.state.request_id = request_id
response = await call_next(request)
response.headers["X-Request-ID"] = request_id
return response
"Coverage isn't about bug prevention — it's about guaranteeing the agent has double-checked the behavior of every line of code it wrote." — Steve Krenzel
_env_file=None (pydantic) or mock dotenv.load_dotenv to prevent the developer's .env from leaking into tests. Tests must be self-contained — they must pass regardless of what's in the local .env.postgresql+asyncpg:// not postgresql://). The sync scheme will fail at runtime with a cryptic driver error."foo", 123, or "test"."returns 404 when order does not exist", not "test order".assert db.query(Task).count() == 1 after one API call.When generated code calls any LLM (Claude, GPT, or other), follow these rules:
Use tool_use / function_calling / response_format: { type: "json_schema", json_schema: ... } for every LLM call. Never parse free-text responses with regex or string splitting.
Every LLM call must have a typed model for the expected response:
from pydantic import BaseModel
from typing import Literal
class ClassificationResult(BaseModel):
category: str
confidence: Literal["high", "medium", "low"]
reasoning: str
interface ClassificationResult {
category: string;
confidence: "high" | "medium" | "low";
reasoning: string;
}
Parse the LLM response through the schema. If validation fails:
Never write:
# WRONG — hides bugs that compound
try:
result = await call_llm(prompt)
parsed = ResponseModel.model_validate_json(result)
except Exception:
parsed = ResponseModel(category="unknown", confidence="low", reasoning="")
Instead:
# CORRECT — caller decides how to handle failure
class LLMResponseError(Exception):
def __init__(self, raw_response: str, validation_error: str):
self.raw_response = raw_response
self.validation_error = validation_error
super().__init__(f"LLM response validation failed: {validation_error}")
try:
result = await call_llm(prompt)
parsed = ResponseModel.model_validate_json(result)
except ValidationError as e:
raise LLMResponseError(raw_response=result, validation_error=str(e))
Always log the raw LLM response at DEBUG level before parsing:
logger.debug(
"LLM response received",
extra={
"provider": self._provider_name,
"model": self._model,
"prompt_tokens": response.usage.input_tokens,
"completion_tokens": response.usage.output_tokens,
"raw_content": response.content[:1000],
"latency_ms": round(elapsed_ms, 2),
},
)
When generated code calls any external API (third-party services, partner APIs, cloud services), follow these rules. See .claude/skills/code-gen/references/api-integration-patterns.md for full templates.
Every external API gets a dedicated wrapper class. This is the ONLY file that imports the SDK or makes HTTP calls to that service.
Business Logic (process_service.py)
↓ calls typed methods
API Wrapper (external_client.py) ← only file that imports SDK / makes HTTP calls
↓ calls
External API
Rules:
Every wrapper classifies errors into typed categories:
class ApiTransientError(Exception):
"""Retryable: 429, 502, 503, timeout, connection reset."""
pass
class ApiPermanentError(Exception):
"""Not retryable: 400, 401, 403, 404, schema mismatch."""
pass
class ApiRateLimitError(ApiTransientError):
"""Rate limited with backoff hint."""
def __init__(self, message: str, retry_after: float | None = None):
super().__init__(message)
self.retry_after = retry_after
ApiTransientError to retry/degrade, ApiPermanentError to fail fastexcept Exception in any API-calling codeconfig.yml under external_apis.{service_name}.retry, not hardcodedRetry-After headers when presentWhen an SDK is synchronous but the backend is async:
asyncio.to_thread() only inside the wrapper class.env only, loaded via config layeros.environ directly.env.example committed with placeholder valuesThese standards apply to ALL generated code, not just API wrappers or LLM calls.
All generated services must use structured logging with extra dicts:
import logging
logger = logging.getLogger(__name__)
# CORRECT — structured fields for JSON log formatters
logger.info("Document processed", extra={
"document_id": doc.id,
"processing_time_ms": round(elapsed_ms, 2),
"output_size_bytes": len(result),
})
# WRONG — data interpolated into message string
logger.info(f"Document {doc.id} processed in {elapsed_ms}ms")
// CORRECT — structured logger
logger.info("Document processed", {
documentId: doc.id,
processingTimeMs: Math.round(elapsedMs),
outputSizeBytes: result.length,
});
// WRONG — template literal message
logger.info(`Document ${doc.id} processed in ${elapsedMs}ms`);
Rules:
logging.getLogger(__name__) (Python) or scoped logger (TypeScript) at module level# CORRECT — typed exception with context
class DocumentProcessingError(Exception):
def __init__(self, document_id: str, stage: str, cause: Exception):
self.document_id = document_id
self.stage = stage
self.cause = cause
super().__init__(f"Failed at {stage} for document {document_id}: {cause}")
# WRONG — bare except swallowing the error
try:
result = process(doc)
except Exception:
result = default_value
Rules:
Exception or BaseException unless re-raising or logging at a top-level boundaryAll API error responses follow a consistent envelope:
{
"error": {
"code": "DOCUMENT_NOT_FOUND",
"message": "Document with ID abc123 does not exist",
"details": {}
}
}
Rules:
code is a machine-readable UPPER_SNAKE_CASE string enummessage is human-readabledetails is optional structured contextconfig.yml or environment variablescomponent-map.md before touching any file.any, missing return types, unannotated parameters"test", 0, null as stand-ins for real domain values)except Exception: return default hides compounding bugs. Raise typed errors.ApiTransientError and ApiPermanentError specifically. Never except Exception.config.yml, not in code.extra dict for structured fields, not string interpolation. Structured logs are searchable; f-strings are not.config.yml..env has the value. Always pass _env_file=None in pydantic-settings tests.postgresql:// uses psycopg2 (sync). Async SQLAlchemy needs postgresql+asyncpg://. Always match the driver scheme to the engine type.assert count == 1 after one API call.Depends(get_db) with async_sessionmaker. Manual sessions leak connections.background_tasks.add_task(fn) without status tracking.allow_credentials=True. Read origins from env var, default to localhost.SELECT 1 against the database. A healthy HTTP server with a dead DB is not healthy.await engine.dispose() in the lifespan's teardown. Leaked connections exhaust the pool.@asynccontextmanager lifespan, not @app.on_event("startup"). The event-based API is deprecated in FastAPI.asyncio.to_thread() uses a default pool of ~5 workers. Under concurrent load, blocking SDK calls exhaust the pool. Set loop.set_default_executor(ThreadPoolExecutor(max_workers=20)) or use async clients.