@ryuenn3123/agentic-senior-core 1.8.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.
- package/.agent-context/blueprints/api-nextjs.md +184 -0
- package/.agent-context/blueprints/aspnet-api.md +247 -0
- package/.agent-context/blueprints/ci-github-actions.md +226 -0
- package/.agent-context/blueprints/ci-gitlab.md +200 -0
- package/.agent-context/blueprints/fastapi-service.md +210 -0
- package/.agent-context/blueprints/go-service.md +217 -0
- package/.agent-context/blueprints/graphql-grpc-api.md +51 -0
- package/.agent-context/blueprints/infrastructure-as-code.md +62 -0
- package/.agent-context/blueprints/kubernetes-manifests.md +76 -0
- package/.agent-context/blueprints/laravel-api.md +223 -0
- package/.agent-context/blueprints/nestjs-logic.md +247 -0
- package/.agent-context/blueprints/observability.md +227 -0
- package/.agent-context/blueprints/spring-boot-api.md +218 -0
- package/.agent-context/policies/llm-judge-threshold.json +20 -0
- package/.agent-context/profiles/platform.md +13 -0
- package/.agent-context/profiles/regulated.md +13 -0
- package/.agent-context/profiles/startup.md +13 -0
- package/.agent-context/prompts/init-project.md +86 -0
- package/.agent-context/prompts/refactor.md +45 -0
- package/.agent-context/prompts/review-code.md +47 -0
- package/.agent-context/review-checklists/architecture-review.md +70 -0
- package/.agent-context/review-checklists/frontend-usability.md +33 -0
- package/.agent-context/review-checklists/performance-audit.md +65 -0
- package/.agent-context/review-checklists/pr-checklist.md +97 -0
- package/.agent-context/review-checklists/release-operations.md +29 -0
- package/.agent-context/review-checklists/security-audit.md +113 -0
- package/.agent-context/rules/api-docs.md +186 -0
- package/.agent-context/rules/architecture.md +198 -0
- package/.agent-context/rules/database-design.md +202 -0
- package/.agent-context/rules/efficiency-vs-hype.md +143 -0
- package/.agent-context/rules/error-handling.md +234 -0
- package/.agent-context/rules/event-driven.md +226 -0
- package/.agent-context/rules/frontend-architecture.md +66 -0
- package/.agent-context/rules/git-workflow.md +200 -0
- package/.agent-context/rules/microservices.md +174 -0
- package/.agent-context/rules/naming-conv.md +141 -0
- package/.agent-context/rules/performance.md +168 -0
- package/.agent-context/rules/realtime.md +47 -0
- package/.agent-context/rules/security.md +195 -0
- package/.agent-context/rules/testing.md +178 -0
- package/.agent-context/stacks/csharp.md +149 -0
- package/.agent-context/stacks/go.md +181 -0
- package/.agent-context/stacks/java.md +135 -0
- package/.agent-context/stacks/php.md +178 -0
- package/.agent-context/stacks/python.md +153 -0
- package/.agent-context/stacks/ruby.md +80 -0
- package/.agent-context/stacks/rust.md +86 -0
- package/.agent-context/stacks/typescript.md +317 -0
- package/.agent-context/state/architecture-map.md +25 -0
- package/.agent-context/state/dependency-map.md +32 -0
- package/.agent-override.md +36 -0
- package/.agents/workflows/init-project.md +29 -0
- package/.agents/workflows/refactor.md +29 -0
- package/.agents/workflows/review-code.md +29 -0
- package/.cursorrules +140 -0
- package/.gemini/instructions.md +97 -0
- package/.github/ISSUE_TEMPLATE/v1.7-frontend-work-item.yml +54 -0
- package/.github/copilot-instructions.md +104 -0
- package/.github/workflows/benchmark-detection.yml +38 -0
- package/.github/workflows/frontend-usability-gate.yml +36 -0
- package/.github/workflows/release-gate.yml +32 -0
- package/.github/workflows/sbom-compliance.yml +32 -0
- package/.windsurfrules +106 -0
- package/AGENTS.md +131 -0
- package/CONTRIBUTING.md +136 -0
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/bin/agentic-senior-core.js +1147 -0
- package/mcp.json +29 -0
- package/package.json +50 -0
- package/scripts/detection-benchmark.mjs +138 -0
- package/scripts/frontend-usability-audit.mjs +87 -0
- package/scripts/generate-sbom.mjs +61 -0
- package/scripts/init-project.ps1 +105 -0
- package/scripts/init-project.sh +131 -0
- package/scripts/llm-judge.mjs +664 -0
- package/scripts/release-gate.mjs +116 -0
- package/scripts/validate.mjs +554 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Blueprint: GitLab CI/CD Pipeline
|
|
2
|
+
|
|
3
|
+
> Same principles as GitHub Actions, adapted for GitLab's pipeline model.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
| Layer | Tool |
|
|
8
|
+
|-------|------|
|
|
9
|
+
| CI/CD | GitLab CI/CD |
|
|
10
|
+
| Config | `.gitlab-ci.yml` |
|
|
11
|
+
| Runners | Shared runners or dedicated (Docker executor) |
|
|
12
|
+
| Registry | GitLab Container Registry |
|
|
13
|
+
| Caching | GitLab CI cache (per-branch, per-job) |
|
|
14
|
+
|
|
15
|
+
## Pipeline Architecture
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
stages:
|
|
19
|
+
- validate # Lint + type check
|
|
20
|
+
- test # Unit + integration tests
|
|
21
|
+
- security # Dependency audit + SAST
|
|
22
|
+
- build # Compile, bundle, containerize
|
|
23
|
+
- judge # LLM-as-a-Judge checklist gate
|
|
24
|
+
- deploy # Staging → Production
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Pipeline Template (`.gitlab-ci.yml`)
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
default:
|
|
31
|
+
image: node:22-alpine
|
|
32
|
+
cache:
|
|
33
|
+
key:
|
|
34
|
+
files: [package-lock.json]
|
|
35
|
+
paths: [node_modules/]
|
|
36
|
+
policy: pull-push
|
|
37
|
+
|
|
38
|
+
stages:
|
|
39
|
+
- validate
|
|
40
|
+
- test
|
|
41
|
+
- security
|
|
42
|
+
- build
|
|
43
|
+
- judge
|
|
44
|
+
- deploy
|
|
45
|
+
|
|
46
|
+
# ─── VALIDATE ───────────────────────────────────────────
|
|
47
|
+
lint:
|
|
48
|
+
stage: validate
|
|
49
|
+
script:
|
|
50
|
+
- npm ci --prefer-offline
|
|
51
|
+
- npm run lint
|
|
52
|
+
- npm run type-check
|
|
53
|
+
rules:
|
|
54
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
55
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
56
|
+
|
|
57
|
+
# ─── TEST ───────────────────────────────────────────────
|
|
58
|
+
test:unit:
|
|
59
|
+
stage: test
|
|
60
|
+
script:
|
|
61
|
+
- npm ci --prefer-offline
|
|
62
|
+
- npm run test -- --coverage
|
|
63
|
+
coverage: '/All files\s*\|\s*(\d+\.?\d*)\s*\|/'
|
|
64
|
+
artifacts:
|
|
65
|
+
reports:
|
|
66
|
+
coverage_report:
|
|
67
|
+
coverage_format: cobertura
|
|
68
|
+
path: coverage/cobertura-coverage.xml
|
|
69
|
+
paths: [coverage/]
|
|
70
|
+
expire_in: 7 days
|
|
71
|
+
|
|
72
|
+
test:integration:
|
|
73
|
+
stage: test
|
|
74
|
+
services:
|
|
75
|
+
- postgres:16-alpine
|
|
76
|
+
variables:
|
|
77
|
+
POSTGRES_DB: test_db
|
|
78
|
+
POSTGRES_USER: test_user
|
|
79
|
+
POSTGRES_PASSWORD: test_pass
|
|
80
|
+
DATABASE_URL: "postgresql://test_user:test_pass@postgres:5432/test_db"
|
|
81
|
+
script:
|
|
82
|
+
- npm ci --prefer-offline
|
|
83
|
+
- npm run test:integration
|
|
84
|
+
|
|
85
|
+
# ─── SECURITY ───────────────────────────────────────────
|
|
86
|
+
audit:
|
|
87
|
+
stage: security
|
|
88
|
+
script:
|
|
89
|
+
- npm audit --audit-level=high
|
|
90
|
+
allow_failure: false
|
|
91
|
+
|
|
92
|
+
sast:
|
|
93
|
+
stage: security
|
|
94
|
+
# GitLab's built-in SAST template
|
|
95
|
+
include:
|
|
96
|
+
- template: Security/SAST.gitlab-ci.yml
|
|
97
|
+
|
|
98
|
+
# ─── BUILD ──────────────────────────────────────────────
|
|
99
|
+
build:
|
|
100
|
+
stage: build
|
|
101
|
+
script:
|
|
102
|
+
- npm ci --prefer-offline
|
|
103
|
+
- npm run build
|
|
104
|
+
artifacts:
|
|
105
|
+
paths: [dist/]
|
|
106
|
+
expire_in: 1 day
|
|
107
|
+
rules:
|
|
108
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
109
|
+
|
|
110
|
+
# ─── LLM JUDGE ──────────────────────────────────────────
|
|
111
|
+
llm:judge:
|
|
112
|
+
stage: judge
|
|
113
|
+
image: node:22-alpine
|
|
114
|
+
variables:
|
|
115
|
+
OPENAI_API_KEY: $OPENAI_API_KEY
|
|
116
|
+
ANTHROPIC_API_KEY: $ANTHROPIC_API_KEY
|
|
117
|
+
GEMINI_API_KEY: $GEMINI_API_KEY
|
|
118
|
+
# CI_MERGE_REQUEST_DIFF_BASE_SHA and CI_COMMIT_SHA are set automatically
|
|
119
|
+
# by GitLab for merge request pipelines — no manual configuration needed.
|
|
120
|
+
before_script:
|
|
121
|
+
- git fetch --unshallow || true # Ensure full history for git diff
|
|
122
|
+
script:
|
|
123
|
+
- node scripts/llm-judge.mjs
|
|
124
|
+
artifacts:
|
|
125
|
+
when: always
|
|
126
|
+
paths:
|
|
127
|
+
- .agent-context/state/llm-judge-report.json
|
|
128
|
+
expire_in: 7 days
|
|
129
|
+
rules:
|
|
130
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
131
|
+
|
|
132
|
+
# ─── DEPLOY ─────────────────────────────────────────────
|
|
133
|
+
deploy:staging:
|
|
134
|
+
stage: deploy
|
|
135
|
+
environment:
|
|
136
|
+
name: staging
|
|
137
|
+
url: https://staging.example.com
|
|
138
|
+
script:
|
|
139
|
+
- echo "Deploy to staging"
|
|
140
|
+
# Add your deployment commands
|
|
141
|
+
rules:
|
|
142
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
143
|
+
|
|
144
|
+
deploy:production:
|
|
145
|
+
stage: deploy
|
|
146
|
+
environment:
|
|
147
|
+
name: production
|
|
148
|
+
url: https://example.com
|
|
149
|
+
script:
|
|
150
|
+
- echo "Deploy to production"
|
|
151
|
+
# Add your deployment commands
|
|
152
|
+
rules:
|
|
153
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
154
|
+
when: manual # Require manual approval
|
|
155
|
+
needs: [deploy:staging]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Key Differences from GitHub Actions
|
|
159
|
+
|
|
160
|
+
| Concern | GitHub Actions | GitLab CI |
|
|
161
|
+
|---------|---------------|-----------|
|
|
162
|
+
| Config file | `.github/workflows/*.yml` | `.gitlab-ci.yml` (single file) |
|
|
163
|
+
| Jobs linkage | `needs:` | `stages:` (sequential) + `needs:` (DAG) |
|
|
164
|
+
| Secrets | GitHub Secrets | CI/CD Variables (masked + protected) |
|
|
165
|
+
| Caching | `actions/cache` | Built-in `cache:` directive |
|
|
166
|
+
| Services | Docker Compose / service containers | `services:` directive |
|
|
167
|
+
| Environments | Environment protection rules | Environment + `when: manual` |
|
|
168
|
+
| Includes | Reusable workflows | `include:` with `template:` |
|
|
169
|
+
|
|
170
|
+
## Security Rules
|
|
171
|
+
|
|
172
|
+
1. **Protected variables** — mark secrets as Protected + Masked
|
|
173
|
+
2. **Protected branches** — only deploy from protected branches
|
|
174
|
+
3. **Include GitLab SAST/DAST** templates for automated scanning
|
|
175
|
+
4. **Limit runner access** — use tags to route jobs to appropriate runners
|
|
176
|
+
5. **Artifact expiration** — set `expire_in` on all artifacts
|
|
177
|
+
6. **Limit LLM input scope** — send only merge diff + checklist context
|
|
178
|
+
|
|
179
|
+
## Scaffolding Checklist
|
|
180
|
+
|
|
181
|
+
- [ ] Create `.gitlab-ci.yml` with validate, test, security, build, deploy stages
|
|
182
|
+
- [ ] Configure CI/CD variables for secrets (masked + protected)
|
|
183
|
+
- [ ] Set up caching for package manager lockfile
|
|
184
|
+
- [ ] Add coverage reporting with Cobertura format
|
|
185
|
+
- [ ] Configure environments (staging, production) with manual approval
|
|
186
|
+
- [ ] Include SAST template for security scanning
|
|
187
|
+
- [ ] Set up merge request pipelines with `rules:`
|
|
188
|
+
- [ ] Configure branch protection rules
|
|
189
|
+
- [ ] Add `timeout` to long-running jobs
|
|
190
|
+
- [ ] Use `needs:` for DAG optimization where possible
|
|
191
|
+
- [ ] Add `llm:judge` stage that enforces `pr-checklist.md`
|
|
192
|
+
|
|
193
|
+
## LLM Judge Annotation Contract
|
|
194
|
+
|
|
195
|
+
For MR annotations and dashboards, parse either:
|
|
196
|
+
|
|
197
|
+
- Log line: `JSON_REPORT: { ... }`
|
|
198
|
+
- Artifact: `.agent-context/state/llm-judge-report.json`
|
|
199
|
+
|
|
200
|
+
Severity values are normalized by the judge to: `critical`, `high`, `medium`, `low`.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Blueprint: FastAPI Service
|
|
2
|
+
|
|
3
|
+
> Python backend API service using FastAPI, Pydantic v2, SQLAlchemy 2.0, and Alembic.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
| Layer | Technology |
|
|
8
|
+
|-------|-----------|
|
|
9
|
+
| Framework | FastAPI |
|
|
10
|
+
| Validation | Pydantic v2 |
|
|
11
|
+
| ORM | SQLAlchemy 2.0 (async) |
|
|
12
|
+
| Migration | Alembic |
|
|
13
|
+
| Testing | pytest + pytest-asyncio + httpx |
|
|
14
|
+
| Logging | structlog |
|
|
15
|
+
| Linting | ruff (lint + format) |
|
|
16
|
+
| Type checking | pyright (strict) |
|
|
17
|
+
| Env config | pydantic-settings |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Project Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
project-name/
|
|
25
|
+
├── src/
|
|
26
|
+
│ ├── __init__.py
|
|
27
|
+
│ ├── main.py
|
|
28
|
+
│ ├── config.py
|
|
29
|
+
│ │
|
|
30
|
+
│ ├── modules/
|
|
31
|
+
│ │ └── user/
|
|
32
|
+
│ │ ├── __init__.py
|
|
33
|
+
│ │ ├── router.py
|
|
34
|
+
│ │ ├── service.py
|
|
35
|
+
│ │ ├── repository.py
|
|
36
|
+
│ │ ├── schemas.py
|
|
37
|
+
│ │ ├── models.py
|
|
38
|
+
│ │ └── exceptions.py
|
|
39
|
+
│ │
|
|
40
|
+
│ └── shared/
|
|
41
|
+
│ ├── __init__.py
|
|
42
|
+
│ ├── database.py
|
|
43
|
+
│ ├── errors.py
|
|
44
|
+
│ ├── logger.py
|
|
45
|
+
│ └── middleware.py
|
|
46
|
+
│
|
|
47
|
+
├── tests/
|
|
48
|
+
│ ├── conftest.py
|
|
49
|
+
│ ├── factories.py
|
|
50
|
+
│ └── modules/
|
|
51
|
+
│ └── user/
|
|
52
|
+
│ └── test_user_service.py
|
|
53
|
+
│
|
|
54
|
+
├── alembic/
|
|
55
|
+
│ ├── env.py
|
|
56
|
+
│ └── versions/
|
|
57
|
+
│
|
|
58
|
+
├── pyproject.toml
|
|
59
|
+
├── alembic.ini
|
|
60
|
+
├── .env.example
|
|
61
|
+
├── Dockerfile
|
|
62
|
+
└── Makefile
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## File Patterns
|
|
68
|
+
|
|
69
|
+
### config.py — Environment Validation
|
|
70
|
+
```python
|
|
71
|
+
from pydantic_settings import BaseSettings
|
|
72
|
+
from pydantic import Field
|
|
73
|
+
|
|
74
|
+
class Settings(BaseSettings):
|
|
75
|
+
database_url: str = Field(
|
|
76
|
+
description="PostgreSQL connection string"
|
|
77
|
+
)
|
|
78
|
+
jwt_secret: str = Field(min_length=32)
|
|
79
|
+
app_env: str = Field(default="development", pattern="^(development|staging|production)$")
|
|
80
|
+
cors_origins: list[str] = Field(default=["http://localhost:3000"])
|
|
81
|
+
log_level: str = Field(default="INFO")
|
|
82
|
+
|
|
83
|
+
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
|
|
84
|
+
|
|
85
|
+
settings = Settings()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### main.py — Application Entry
|
|
89
|
+
```python
|
|
90
|
+
from contextlib import asynccontextmanager
|
|
91
|
+
from fastapi import FastAPI
|
|
92
|
+
from src.config import settings
|
|
93
|
+
from src.shared.middleware import setup_middleware
|
|
94
|
+
from src.shared.logger import setup_logging
|
|
95
|
+
from src.modules.user.router import router as user_router
|
|
96
|
+
|
|
97
|
+
@asynccontextmanager
|
|
98
|
+
async def lifespan(app: FastAPI):
|
|
99
|
+
setup_logging(settings.log_level)
|
|
100
|
+
# startup: connect DB, warm caches
|
|
101
|
+
yield
|
|
102
|
+
# shutdown: close connections
|
|
103
|
+
|
|
104
|
+
app = FastAPI(
|
|
105
|
+
title="Service Name",
|
|
106
|
+
version="1.0.0",
|
|
107
|
+
lifespan=lifespan,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
setup_middleware(app, settings)
|
|
111
|
+
app.include_router(user_router, prefix="/api/v1")
|
|
112
|
+
|
|
113
|
+
@app.get("/health")
|
|
114
|
+
async def health() -> dict[str, str]:
|
|
115
|
+
return {"status": "ok"}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### router.py — Transport Layer
|
|
119
|
+
```python
|
|
120
|
+
from fastapi import APIRouter, HTTPException, status
|
|
121
|
+
from src.modules.user.schemas import CreateUserRequest, UserResponse
|
|
122
|
+
from src.modules.user.service import UserService
|
|
123
|
+
|
|
124
|
+
router = APIRouter(prefix="/users", tags=["users"])
|
|
125
|
+
|
|
126
|
+
@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
127
|
+
async def create_user(request: CreateUserRequest, service: UserService) -> UserResponse:
|
|
128
|
+
return await service.create(request)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### schemas.py — Validation Boundaries
|
|
132
|
+
```python
|
|
133
|
+
from pydantic import BaseModel, EmailStr, Field
|
|
134
|
+
from datetime import datetime
|
|
135
|
+
from uuid import UUID
|
|
136
|
+
|
|
137
|
+
class CreateUserRequest(BaseModel):
|
|
138
|
+
name: str = Field(min_length=1, max_length=100)
|
|
139
|
+
email: EmailStr
|
|
140
|
+
age: int = Field(ge=13, le=150)
|
|
141
|
+
|
|
142
|
+
model_config = {"strict": True}
|
|
143
|
+
|
|
144
|
+
class UserResponse(BaseModel):
|
|
145
|
+
id: UUID
|
|
146
|
+
name: str
|
|
147
|
+
email: str
|
|
148
|
+
created_at: datetime
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### service.py — Business Logic
|
|
152
|
+
```python
|
|
153
|
+
import structlog
|
|
154
|
+
from src.modules.user.schemas import CreateUserRequest, UserResponse
|
|
155
|
+
from src.modules.user.repository import UserRepository
|
|
156
|
+
from src.modules.user.exceptions import UserAlreadyExistsError
|
|
157
|
+
|
|
158
|
+
logger = structlog.get_logger()
|
|
159
|
+
|
|
160
|
+
class UserService:
|
|
161
|
+
def __init__(self, repo: UserRepository) -> None:
|
|
162
|
+
self._repo = repo
|
|
163
|
+
|
|
164
|
+
async def create(self, request: CreateUserRequest) -> UserResponse:
|
|
165
|
+
if await self._repo.exists_by_email(request.email):
|
|
166
|
+
raise UserAlreadyExistsError(request.email)
|
|
167
|
+
|
|
168
|
+
user = await self._repo.insert(request)
|
|
169
|
+
logger.info("user_created", user_id=str(user.id), email=user.email)
|
|
170
|
+
return UserResponse.model_validate(user)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### shared/errors.py — Error Foundation
|
|
174
|
+
```python
|
|
175
|
+
from fastapi import Request, HTTPException
|
|
176
|
+
from fastapi.responses import JSONResponse
|
|
177
|
+
|
|
178
|
+
class AppError(Exception):
|
|
179
|
+
def __init__(self, code: str, message: str, status_code: int = 400) -> None:
|
|
180
|
+
self.code = code
|
|
181
|
+
self.message = message
|
|
182
|
+
self.status_code = status_code
|
|
183
|
+
|
|
184
|
+
async def app_error_handler(request: Request, exc: AppError) -> JSONResponse:
|
|
185
|
+
return JSONResponse(
|
|
186
|
+
status_code=exc.status_code,
|
|
187
|
+
content={
|
|
188
|
+
"error": {
|
|
189
|
+
"code": exc.code,
|
|
190
|
+
"message": exc.message,
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Scaffolding Checklist
|
|
199
|
+
|
|
200
|
+
- [ ] Create project with `pyproject.toml` (ruff, pyright, pytest config)
|
|
201
|
+
- [ ] Set up `src/config.py` with Pydantic Settings
|
|
202
|
+
- [ ] Set up `src/shared/database.py` with async SQLAlchemy engine
|
|
203
|
+
- [ ] Set up `src/shared/errors.py` with base error class + handler
|
|
204
|
+
- [ ] Set up `src/shared/logger.py` with structlog
|
|
205
|
+
- [ ] Create first module following the pattern above
|
|
206
|
+
- [ ] Set up Alembic with initial migration
|
|
207
|
+
- [ ] Create `Makefile` with dev, test, lint, format commands
|
|
208
|
+
- [ ] Create `.env.example`
|
|
209
|
+
- [ ] Create `Dockerfile` (multi-stage)
|
|
210
|
+
- [ ] Run `ruff check` and `pyright` — zero errors
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Blueprint: Go HTTP Service (chi / stdlib)
|
|
2
|
+
|
|
3
|
+
> Go is not Java. Don't bring Spring patterns.
|
|
4
|
+
> Embrace simplicity, explicit errors, and small interfaces.
|
|
5
|
+
|
|
6
|
+
## Tech Stack
|
|
7
|
+
|
|
8
|
+
| Layer | Choice | Why |
|
|
9
|
+
|-------|--------|-----|
|
|
10
|
+
| Language | Go 1.23+ | Latest stable |
|
|
11
|
+
| Router | `net/http` (Go 1.22+ enhanced) + `chi` | stdlib-compatible, composable middleware |
|
|
12
|
+
| Validation | Custom + `go-playground/validator` | Type-safe, struct tags |
|
|
13
|
+
| Database | `database/sql` + `pgx` + `sqlc` | Type-safe queries from SQL |
|
|
14
|
+
| Migrations | `goose` or `golang-migrate` | Versioned, reversible |
|
|
15
|
+
| Logging | `log/slog` (stdlib) | Structured, zero-dependency |
|
|
16
|
+
| Config | `envconfig` or `koanf` | Env-first, typed config |
|
|
17
|
+
| Testing | stdlib `testing` + `testify` (assertions) | Community standard |
|
|
18
|
+
| OpenAPI | `swaggo/swag` or `ogen` | Generate spec from annotations |
|
|
19
|
+
| Telemetry | OpenTelemetry Go SDK | Vendor-neutral observability |
|
|
20
|
+
|
|
21
|
+
## Project Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
project-root/
|
|
25
|
+
├── cmd/
|
|
26
|
+
│ └── api/
|
|
27
|
+
│ └── main.go # Entry point: wire dependencies, start server
|
|
28
|
+
│
|
|
29
|
+
├── internal/ # Private application code
|
|
30
|
+
│ ├── config/
|
|
31
|
+
│ │ └── config.go # Env-based configuration struct
|
|
32
|
+
│ │
|
|
33
|
+
│ ├── domain/ # Core business types (no external deps)
|
|
34
|
+
│ │ ├── user.go # Entity: User
|
|
35
|
+
│ │ ├── order.go # Entity: Order
|
|
36
|
+
│ │ └── errors.go # Domain-specific error types
|
|
37
|
+
│ │
|
|
38
|
+
│ ├── service/ # Business logic (depends on domain + ports)
|
|
39
|
+
│ │ ├── user_service.go
|
|
40
|
+
│ │ └── order_service.go
|
|
41
|
+
│ │
|
|
42
|
+
│ ├── repository/ # Data access (implements domain ports)
|
|
43
|
+
│ │ ├── postgres/
|
|
44
|
+
│ │ │ ├── user_repo.go
|
|
45
|
+
│ │ │ └── queries/ # sqlc generated or raw SQL
|
|
46
|
+
│ │ └── repository.go # Interface definitions
|
|
47
|
+
│ │
|
|
48
|
+
│ ├── handler/ # HTTP handlers (chi routes)
|
|
49
|
+
│ │ ├── user_handler.go
|
|
50
|
+
│ │ ├── order_handler.go
|
|
51
|
+
│ │ ├── middleware.go # Custom middleware
|
|
52
|
+
│ │ └── response.go # Standardized JSON response helpers
|
|
53
|
+
│ │
|
|
54
|
+
│ └── platform/ # Infrastructure adapters
|
|
55
|
+
│ ├── database.go # Database connection setup
|
|
56
|
+
│ ├── logger.go # slog configuration
|
|
57
|
+
│ └── telemetry.go # OpenTelemetry setup
|
|
58
|
+
│
|
|
59
|
+
├── migrations/
|
|
60
|
+
│ ├── 001_create_users.sql
|
|
61
|
+
│ └── 002_create_orders.sql
|
|
62
|
+
│
|
|
63
|
+
├── go.mod
|
|
64
|
+
├── go.sum
|
|
65
|
+
├── Makefile # Standard commands
|
|
66
|
+
├── Dockerfile
|
|
67
|
+
└── .env.example
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Key Patterns
|
|
71
|
+
|
|
72
|
+
### Dependency Injection via Constructors
|
|
73
|
+
|
|
74
|
+
```go
|
|
75
|
+
// internal/service/user_service.go
|
|
76
|
+
type UserService struct {
|
|
77
|
+
repo UserRepository
|
|
78
|
+
logger *slog.Logger
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func NewUserService(repo UserRepository, logger *slog.Logger) *UserService {
|
|
82
|
+
return &UserService{repo: repo, logger: logger}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Interfaces in the Consumer Package
|
|
87
|
+
|
|
88
|
+
```go
|
|
89
|
+
// internal/service/user_service.go
|
|
90
|
+
// Interface defined HERE (consumer), not in the repository package
|
|
91
|
+
type UserRepository interface {
|
|
92
|
+
FindByID(ctx context.Context, id string) (*domain.User, error)
|
|
93
|
+
Create(ctx context.Context, user *domain.User) error
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Error Handling: Explicit, Always
|
|
98
|
+
|
|
99
|
+
```go
|
|
100
|
+
// ❌ BANNED in Go
|
|
101
|
+
result, _ := db.Query(ctx, sql) // Ignoring error
|
|
102
|
+
|
|
103
|
+
// ✅ REQUIRED
|
|
104
|
+
result, err := db.Query(ctx, sql)
|
|
105
|
+
if err != nil {
|
|
106
|
+
return fmt.Errorf("query users: %w", err)
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### HTTP Handler Pattern
|
|
111
|
+
|
|
112
|
+
```go
|
|
113
|
+
// internal/handler/user_handler.go
|
|
114
|
+
func (h *UserHandler) Create(w http.ResponseWriter, r *http.Request) {
|
|
115
|
+
var req CreateUserRequest
|
|
116
|
+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
117
|
+
respondError(w, http.StatusBadRequest, "invalid request body")
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if err := req.Validate(); err != nil {
|
|
122
|
+
respondError(w, http.StatusBadRequest, err.Error())
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
user, err := h.service.Create(r.Context(), req.ToDomain())
|
|
127
|
+
if err != nil {
|
|
128
|
+
h.logger.ErrorContext(r.Context(), "create user failed",
|
|
129
|
+
slog.String("error", err.Error()),
|
|
130
|
+
)
|
|
131
|
+
respondError(w, http.StatusInternalServerError, "internal error")
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
respondJSON(w, http.StatusCreated, user)
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Router Setup with chi
|
|
140
|
+
|
|
141
|
+
```go
|
|
142
|
+
// cmd/api/main.go
|
|
143
|
+
func setupRouter(h *handler.Handler) http.Handler {
|
|
144
|
+
r := chi.NewRouter()
|
|
145
|
+
|
|
146
|
+
// Global middleware
|
|
147
|
+
r.Use(middleware.RequestID)
|
|
148
|
+
r.Use(middleware.RealIP)
|
|
149
|
+
r.Use(middleware.Logger)
|
|
150
|
+
r.Use(middleware.Recoverer)
|
|
151
|
+
r.Use(middleware.Timeout(30 * time.Second))
|
|
152
|
+
|
|
153
|
+
// API routes
|
|
154
|
+
r.Route("/api/v1", func(r chi.Router) {
|
|
155
|
+
r.Route("/users", func(r chi.Router) {
|
|
156
|
+
r.Get("/", h.User.List)
|
|
157
|
+
r.Post("/", h.User.Create)
|
|
158
|
+
r.Route("/{userId}", func(r chi.Router) {
|
|
159
|
+
r.Get("/", h.User.GetByID)
|
|
160
|
+
r.Put("/", h.User.Update)
|
|
161
|
+
r.Delete("/", h.User.Delete)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// Health check
|
|
167
|
+
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
168
|
+
w.WriteHeader(http.StatusOK)
|
|
169
|
+
w.Write([]byte(`{"status":"ok"}`))
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
return r
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Makefile Commands
|
|
177
|
+
|
|
178
|
+
```makefile
|
|
179
|
+
.PHONY: run build test lint migrate
|
|
180
|
+
|
|
181
|
+
run:
|
|
182
|
+
go run ./cmd/api
|
|
183
|
+
|
|
184
|
+
build:
|
|
185
|
+
CGO_ENABLED=0 go build -o bin/api ./cmd/api
|
|
186
|
+
|
|
187
|
+
test:
|
|
188
|
+
go test ./... -v -race -coverprofile=coverage.out
|
|
189
|
+
|
|
190
|
+
lint:
|
|
191
|
+
golangci-lint run ./...
|
|
192
|
+
|
|
193
|
+
migrate-up:
|
|
194
|
+
goose -dir migrations postgres "$(DATABASE_URL)" up
|
|
195
|
+
|
|
196
|
+
migrate-down:
|
|
197
|
+
goose -dir migrations postgres "$(DATABASE_URL)" down
|
|
198
|
+
|
|
199
|
+
sqlc:
|
|
200
|
+
sqlc generate
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Scaffolding Checklist
|
|
204
|
+
|
|
205
|
+
- [ ] `go mod init` with proper module path
|
|
206
|
+
- [ ] Create `cmd/api/main.go` entry point
|
|
207
|
+
- [ ] Set up `internal/` structure (config, domain, service, repository, handler)
|
|
208
|
+
- [ ] Configure `chi` router with standard middleware stack
|
|
209
|
+
- [ ] Set up `slog` structured logging
|
|
210
|
+
- [ ] Configure database connection with `pgx` + connection pool
|
|
211
|
+
- [ ] Set up `sqlc` for type-safe SQL queries
|
|
212
|
+
- [ ] Create migration files with `goose`
|
|
213
|
+
- [ ] Write standardized JSON response helpers
|
|
214
|
+
- [ ] Add `Makefile` with run, build, test, lint, migrate commands
|
|
215
|
+
- [ ] Create `Dockerfile` (multi-stage, scratch base)
|
|
216
|
+
- [ ] Create `.env.example` with all required env vars
|
|
217
|
+
- [ ] Run `golangci-lint` with strict config
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# GraphQL & gRPC API Blueprint
|
|
2
|
+
|
|
3
|
+
> REST is for resources; GraphQL is for queries; gRPC is for actions. Choose the right tool, then design the contract before writing the code.
|
|
4
|
+
|
|
5
|
+
## 1. Schema-First Design (Mandatory)
|
|
6
|
+
Never rely on code-first generation for your public contracts.
|
|
7
|
+
- **Rule:** The schema (`.graphql` or `.proto`) is the single source of truth.
|
|
8
|
+
- **Why:** Schema-first forces API design discussions to happen independently of the implementation language. It allows frontend and backend teams to work in parallel.
|
|
9
|
+
- **Validation:** Your CI/CD MUST include a schema linter (e.g., `graphql-schema-linter` or `buf lint`).
|
|
10
|
+
|
|
11
|
+
## 2. GraphQL Standards
|
|
12
|
+
|
|
13
|
+
### A. The N+1 Problem (Dataloaders)
|
|
14
|
+
- **Rule:** EVERY resolver that fetches a relation MUST use a DataLoader (or equivalent batching/caching mechanism).
|
|
15
|
+
- **BANNED:** Resolving `author` inside a `posts` query by doing `SELECT * FROM users WHERE id = X` in a loop.
|
|
16
|
+
- **REQUIRED:** Batching IDs to fetch them all in one query `SELECT * FROM users WHERE id IN (X, Y, Z)`.
|
|
17
|
+
|
|
18
|
+
### B. Pagination
|
|
19
|
+
- **Rule:** Use cursor-based pagination (Relay Connection Specification) for all list endpoints.
|
|
20
|
+
- **BANNED:** Offset-based pagination (`limit`, `offset`) for large datasets (it becomes exponentially slower).
|
|
21
|
+
- **REQUIRED Structure:**
|
|
22
|
+
```graphql
|
|
23
|
+
type UserConnection {
|
|
24
|
+
edges: [UserEdge!]!
|
|
25
|
+
pageInfo: PageInfo!
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### C. Mutations
|
|
30
|
+
- **Rule:** Mutations MUST represent business actions, not CRUD database operations.
|
|
31
|
+
- **BANNED:** `updateUser`, `createPost`.
|
|
32
|
+
- **REQUIRED:** `suspendUserAccount`, `publishBlogPost`. Every mutation must take a single `input` object and return a `payload` object.
|
|
33
|
+
|
|
34
|
+
## 3. gRPC & Protobuf Standards
|
|
35
|
+
|
|
36
|
+
### A. Backwards Compatibility
|
|
37
|
+
- **Rule:** Never rename a field, never change a field's type, and never reuse a field tag number.
|
|
38
|
+
- **BANNED:** `int32 count = 1;` -> `int64 count = 1;`
|
|
39
|
+
- **REQUIRED:** If the type must change, deprecate the old field and create a new one:
|
|
40
|
+
```protobuf
|
|
41
|
+
int32 old_count = 1 [deprecated = true];
|
|
42
|
+
int64 count = 2;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### B. Structure & Organization
|
|
46
|
+
- **Rule:** Group proto files by domain/package (`package ecommerce.orders.v1;`).
|
|
47
|
+
- **Required Files:** Separate messages (`messages.proto`) from services (`service.proto`) if the domain is large.
|
|
48
|
+
|
|
49
|
+
### C. Error Handling
|
|
50
|
+
gRPC uses standard status codes.
|
|
51
|
+
- **Rule:** Map business errors to explicit `google.rpc.Status` codes. Do not return `UNKNOWN` or `INTERNAL` for business logic failures (like "Insufficient Funds"). Use `FAILED_PRECONDITION` or custom detail payloads.
|