autoworkflow 3.1.5 → 3.5.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/.claude/commands/analyze.md +19 -0
- package/.claude/commands/audit.md +26 -0
- package/.claude/commands/build.md +39 -0
- package/.claude/commands/commit.md +25 -0
- package/.claude/commands/fix.md +23 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/suggest.md +23 -0
- package/.claude/commands/verify.md +18 -0
- package/.claude/hooks/post-bash-router.sh +20 -0
- package/.claude/hooks/post-commit.sh +140 -0
- package/.claude/hooks/pre-edit.sh +129 -0
- package/.claude/hooks/session-check.sh +79 -0
- package/.claude/settings.json +40 -6
- package/.claude/settings.local.json +3 -1
- package/.claude/skills/actix.md +337 -0
- package/.claude/skills/alembic.md +504 -0
- package/.claude/skills/angular.md +237 -0
- package/.claude/skills/api-design.md +187 -0
- package/.claude/skills/aspnet-core.md +377 -0
- package/.claude/skills/astro.md +245 -0
- package/.claude/skills/auth-clerk.md +327 -0
- package/.claude/skills/auth-firebase.md +367 -0
- package/.claude/skills/auth-nextauth.md +359 -0
- package/.claude/skills/auth-supabase.md +368 -0
- package/.claude/skills/axum.md +386 -0
- package/.claude/skills/blazor.md +456 -0
- package/.claude/skills/chi.md +348 -0
- package/.claude/skills/code-review.md +133 -0
- package/.claude/skills/csharp.md +296 -0
- package/.claude/skills/css-modules.md +325 -0
- package/.claude/skills/cypress.md +343 -0
- package/.claude/skills/debugging.md +133 -0
- package/.claude/skills/diesel.md +392 -0
- package/.claude/skills/django.md +301 -0
- package/.claude/skills/docker.md +319 -0
- package/.claude/skills/doctrine.md +473 -0
- package/.claude/skills/documentation.md +182 -0
- package/.claude/skills/dotnet.md +409 -0
- package/.claude/skills/drizzle.md +293 -0
- package/.claude/skills/echo.md +321 -0
- package/.claude/skills/eloquent.md +256 -0
- package/.claude/skills/emotion.md +426 -0
- package/.claude/skills/entity-framework.md +370 -0
- package/.claude/skills/express.md +316 -0
- package/.claude/skills/fastapi.md +329 -0
- package/.claude/skills/fastify.md +299 -0
- package/.claude/skills/fiber.md +315 -0
- package/.claude/skills/flask.md +322 -0
- package/.claude/skills/gin.md +342 -0
- package/.claude/skills/git.md +116 -0
- package/.claude/skills/github-actions.md +353 -0
- package/.claude/skills/go.md +377 -0
- package/.claude/skills/gorm.md +409 -0
- package/.claude/skills/graphql.md +478 -0
- package/.claude/skills/hibernate.md +379 -0
- package/.claude/skills/hono.md +306 -0
- package/.claude/skills/java.md +400 -0
- package/.claude/skills/jest.md +313 -0
- package/.claude/skills/jpa.md +282 -0
- package/.claude/skills/kotlin.md +347 -0
- package/.claude/skills/kubernetes.md +363 -0
- package/.claude/skills/laravel.md +414 -0
- package/.claude/skills/mcp-browser.md +320 -0
- package/.claude/skills/mcp-database.md +219 -0
- package/.claude/skills/mcp-fetch.md +241 -0
- package/.claude/skills/mcp-filesystem.md +204 -0
- package/.claude/skills/mcp-github.md +217 -0
- package/.claude/skills/mcp-memory.md +240 -0
- package/.claude/skills/mcp-search.md +218 -0
- package/.claude/skills/mcp-slack.md +262 -0
- package/.claude/skills/micronaut.md +388 -0
- package/.claude/skills/mongodb.md +319 -0
- package/.claude/skills/mongoose.md +355 -0
- package/.claude/skills/mysql.md +281 -0
- package/.claude/skills/nestjs.md +335 -0
- package/.claude/skills/nextjs-app-router.md +260 -0
- package/.claude/skills/nextjs-pages.md +172 -0
- package/.claude/skills/nuxt.md +202 -0
- package/.claude/skills/openapi.md +489 -0
- package/.claude/skills/performance.md +199 -0
- package/.claude/skills/php.md +398 -0
- package/.claude/skills/playwright.md +371 -0
- package/.claude/skills/postgresql.md +257 -0
- package/.claude/skills/prisma.md +293 -0
- package/.claude/skills/pydantic.md +304 -0
- package/.claude/skills/pytest.md +313 -0
- package/.claude/skills/python.md +272 -0
- package/.claude/skills/quarkus.md +377 -0
- package/.claude/skills/react.md +230 -0
- package/.claude/skills/redis.md +391 -0
- package/.claude/skills/refactoring.md +143 -0
- package/.claude/skills/remix.md +246 -0
- package/.claude/skills/rest-api.md +490 -0
- package/.claude/skills/rocket.md +366 -0
- package/.claude/skills/rust.md +341 -0
- package/.claude/skills/sass.md +380 -0
- package/.claude/skills/sea-orm.md +382 -0
- package/.claude/skills/security.md +167 -0
- package/.claude/skills/sequelize.md +395 -0
- package/.claude/skills/spring-boot.md +416 -0
- package/.claude/skills/sqlalchemy.md +269 -0
- package/.claude/skills/sqlx-rust.md +408 -0
- package/.claude/skills/state-jotai.md +346 -0
- package/.claude/skills/state-mobx.md +353 -0
- package/.claude/skills/state-pinia.md +431 -0
- package/.claude/skills/state-redux.md +337 -0
- package/.claude/skills/state-tanstack-query.md +434 -0
- package/.claude/skills/state-zustand.md +340 -0
- package/.claude/skills/styled-components.md +403 -0
- package/.claude/skills/svelte.md +238 -0
- package/.claude/skills/sveltekit.md +207 -0
- package/.claude/skills/symfony.md +437 -0
- package/.claude/skills/tailwind.md +279 -0
- package/.claude/skills/terraform.md +394 -0
- package/.claude/skills/testing-library.md +371 -0
- package/.claude/skills/trpc.md +426 -0
- package/.claude/skills/typeorm.md +368 -0
- package/.claude/skills/vitest.md +330 -0
- package/.claude/skills/vue.md +202 -0
- package/.claude/skills/warp.md +365 -0
- package/README.md +135 -52
- package/package.json +1 -1
- package/system/triggers.md +152 -11
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Chi Router Skill
|
|
2
|
+
|
|
3
|
+
## Application Setup
|
|
4
|
+
\`\`\`go
|
|
5
|
+
package main
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
"context"
|
|
9
|
+
"encoding/json"
|
|
10
|
+
"log"
|
|
11
|
+
"net/http"
|
|
12
|
+
"time"
|
|
13
|
+
|
|
14
|
+
"github.com/go-chi/chi/v5"
|
|
15
|
+
"github.com/go-chi/chi/v5/middleware"
|
|
16
|
+
"github.com/go-chi/cors"
|
|
17
|
+
"github.com/go-chi/httprate"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
func main() {
|
|
21
|
+
r := chi.NewRouter()
|
|
22
|
+
|
|
23
|
+
// Global middleware stack
|
|
24
|
+
r.Use(middleware.RequestID)
|
|
25
|
+
r.Use(middleware.RealIP)
|
|
26
|
+
r.Use(middleware.Logger)
|
|
27
|
+
r.Use(middleware.Recoverer)
|
|
28
|
+
r.Use(middleware.Timeout(30 * time.Second))
|
|
29
|
+
r.Use(cors.Handler(cors.Options{
|
|
30
|
+
AllowedOrigins: []string{"*"},
|
|
31
|
+
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
32
|
+
AllowedHeaders: []string{"Authorization", "Content-Type"},
|
|
33
|
+
AllowCredentials: true,
|
|
34
|
+
MaxAge: 300,
|
|
35
|
+
}))
|
|
36
|
+
|
|
37
|
+
// Health check
|
|
38
|
+
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
39
|
+
respondJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Mount routes
|
|
43
|
+
r.Mount("/api/v1", apiRouter())
|
|
44
|
+
|
|
45
|
+
log.Println("Server starting on :8080")
|
|
46
|
+
log.Fatal(http.ListenAndServe(":8080", r))
|
|
47
|
+
}
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
## Route Groups and Subrouters
|
|
51
|
+
\`\`\`go
|
|
52
|
+
func apiRouter() http.Handler {
|
|
53
|
+
r := chi.NewRouter()
|
|
54
|
+
|
|
55
|
+
// Public routes
|
|
56
|
+
r.Group(func(r chi.Router) {
|
|
57
|
+
r.Post("/auth/login", loginHandler)
|
|
58
|
+
r.Post("/auth/register", registerHandler)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Protected routes
|
|
62
|
+
r.Group(func(r chi.Router) {
|
|
63
|
+
r.Use(AuthMiddleware)
|
|
64
|
+
|
|
65
|
+
// Users routes
|
|
66
|
+
r.Route("/users", func(r chi.Router) {
|
|
67
|
+
r.Get("/", listUsersHandler)
|
|
68
|
+
r.Post("/", createUserHandler)
|
|
69
|
+
|
|
70
|
+
r.Route("/{userID}", func(r chi.Router) {
|
|
71
|
+
r.Use(UserCtx) // Load user into context
|
|
72
|
+
r.Get("/", getUserHandler)
|
|
73
|
+
r.Put("/", updateUserHandler)
|
|
74
|
+
r.Delete("/", deleteUserHandler)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Posts routes with rate limiting
|
|
79
|
+
r.Route("/posts", func(r chi.Router) {
|
|
80
|
+
r.Use(httprate.LimitByIP(10, time.Minute))
|
|
81
|
+
r.Get("/", listPostsHandler)
|
|
82
|
+
r.Post("/", createPostHandler)
|
|
83
|
+
|
|
84
|
+
r.Route("/{postID}", func(r chi.Router) {
|
|
85
|
+
r.Use(PostCtx)
|
|
86
|
+
r.Get("/", getPostHandler)
|
|
87
|
+
r.Put("/", updatePostHandler)
|
|
88
|
+
r.Delete("/", deletePostHandler)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return r
|
|
94
|
+
}
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
## Context Middleware Pattern
|
|
98
|
+
\`\`\`go
|
|
99
|
+
// Context keys
|
|
100
|
+
type contextKey string
|
|
101
|
+
|
|
102
|
+
const (
|
|
103
|
+
userCtxKey contextKey = "user"
|
|
104
|
+
postCtxKey contextKey = "post"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// UserCtx loads user from URL param into context
|
|
108
|
+
func UserCtx(next http.Handler) http.Handler {
|
|
109
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
110
|
+
userID := chi.URLParam(r, "userID")
|
|
111
|
+
|
|
112
|
+
user, err := userService.GetByID(r.Context(), userID)
|
|
113
|
+
if err != nil {
|
|
114
|
+
if errors.Is(err, ErrNotFound) {
|
|
115
|
+
respondError(w, http.StatusNotFound, "user not found")
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
respondError(w, http.StatusInternalServerError, "internal error")
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ctx := context.WithValue(r.Context(), userCtxKey, user)
|
|
123
|
+
next.ServeHTTP(w, r.WithContext(ctx))
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Helper to get user from context
|
|
128
|
+
func UserFromContext(ctx context.Context) *model.User {
|
|
129
|
+
user, _ := ctx.Value(userCtxKey).(*model.User)
|
|
130
|
+
return user
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Handler using context
|
|
134
|
+
func getUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
135
|
+
user := UserFromContext(r.Context())
|
|
136
|
+
respondJSON(w, http.StatusOK, user)
|
|
137
|
+
}
|
|
138
|
+
\`\`\`
|
|
139
|
+
|
|
140
|
+
## Request Handling
|
|
141
|
+
\`\`\`go
|
|
142
|
+
type CreateUserRequest struct {
|
|
143
|
+
Email string \`json:"email" validate:"required,email"\`
|
|
144
|
+
Name string \`json:"name" validate:"required,min=2,max=100"\`
|
|
145
|
+
Password string \`json:"password" validate:"required,min=8"\`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
type QueryParams struct {
|
|
149
|
+
Page int \`json:"page"\`
|
|
150
|
+
PerPage int \`json:"per_page"\`
|
|
151
|
+
Sort string \`json:"sort"\`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
func createUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
155
|
+
var req CreateUserRequest
|
|
156
|
+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
157
|
+
respondError(w, http.StatusBadRequest, "invalid request body")
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if err := validate.Struct(&req); err != nil {
|
|
162
|
+
respondError(w, http.StatusBadRequest, formatValidationErrors(err))
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
user, err := userService.Create(r.Context(), req)
|
|
167
|
+
if err != nil {
|
|
168
|
+
handleError(w, err)
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
respondJSON(w, http.StatusCreated, user)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
func listUsersHandler(w http.ResponseWriter, r *http.Request) {
|
|
176
|
+
// Parse query params
|
|
177
|
+
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
178
|
+
perPage, _ := strconv.Atoi(r.URL.Query().Get("per_page"))
|
|
179
|
+
|
|
180
|
+
if page == 0 {
|
|
181
|
+
page = 1
|
|
182
|
+
}
|
|
183
|
+
if perPage == 0 {
|
|
184
|
+
perPage = 20
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
users, total, err := userService.List(r.Context(), page, perPage)
|
|
188
|
+
if err != nil {
|
|
189
|
+
handleError(w, err)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
respondJSON(w, http.StatusOK, map[string]interface{}{
|
|
194
|
+
"data": users,
|
|
195
|
+
"total": total,
|
|
196
|
+
"page": page,
|
|
197
|
+
"per_page": perPage,
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
\`\`\`
|
|
201
|
+
|
|
202
|
+
## Auth Middleware
|
|
203
|
+
\`\`\`go
|
|
204
|
+
type authContextKey struct{}
|
|
205
|
+
|
|
206
|
+
type AuthInfo struct {
|
|
207
|
+
UserID string
|
|
208
|
+
Role string
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func AuthMiddleware(next http.Handler) http.Handler {
|
|
212
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
213
|
+
authHeader := r.Header.Get("Authorization")
|
|
214
|
+
if authHeader == "" {
|
|
215
|
+
respondError(w, http.StatusUnauthorized, "missing authorization header")
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
|
220
|
+
claims, err := validateToken(tokenString)
|
|
221
|
+
if err != nil {
|
|
222
|
+
respondError(w, http.StatusUnauthorized, "invalid token")
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Store auth info in context
|
|
227
|
+
authInfo := &AuthInfo{
|
|
228
|
+
UserID: claims.UserID,
|
|
229
|
+
Role: claims.Role,
|
|
230
|
+
}
|
|
231
|
+
ctx := context.WithValue(r.Context(), authContextKey{}, authInfo)
|
|
232
|
+
next.ServeHTTP(w, r.WithContext(ctx))
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
func AuthFromContext(ctx context.Context) *AuthInfo {
|
|
237
|
+
auth, _ := ctx.Value(authContextKey{}).(*AuthInfo)
|
|
238
|
+
return auth
|
|
239
|
+
}
|
|
240
|
+
\`\`\`
|
|
241
|
+
|
|
242
|
+
## Response Helpers
|
|
243
|
+
\`\`\`go
|
|
244
|
+
func respondJSON(w http.ResponseWriter, status int, data interface{}) {
|
|
245
|
+
w.Header().Set("Content-Type", "application/json")
|
|
246
|
+
w.WriteHeader(status)
|
|
247
|
+
if data != nil {
|
|
248
|
+
json.NewEncoder(w).Encode(data)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func respondError(w http.ResponseWriter, status int, message interface{}) {
|
|
253
|
+
respondJSON(w, status, map[string]interface{}{"error": message})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
func handleError(w http.ResponseWriter, err error) {
|
|
257
|
+
switch {
|
|
258
|
+
case errors.Is(err, ErrNotFound):
|
|
259
|
+
respondError(w, http.StatusNotFound, "resource not found")
|
|
260
|
+
case errors.Is(err, ErrUnauthorized):
|
|
261
|
+
respondError(w, http.StatusUnauthorized, "unauthorized")
|
|
262
|
+
case errors.Is(err, ErrForbidden):
|
|
263
|
+
respondError(w, http.StatusForbidden, "forbidden")
|
|
264
|
+
case errors.Is(err, ErrConflict):
|
|
265
|
+
respondError(w, http.StatusConflict, "resource already exists")
|
|
266
|
+
default:
|
|
267
|
+
log.Printf("internal error: %v", err)
|
|
268
|
+
respondError(w, http.StatusInternalServerError, "internal server error")
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
\`\`\`
|
|
272
|
+
|
|
273
|
+
## Testing
|
|
274
|
+
\`\`\`go
|
|
275
|
+
package handler_test
|
|
276
|
+
|
|
277
|
+
import (
|
|
278
|
+
"bytes"
|
|
279
|
+
"encoding/json"
|
|
280
|
+
"net/http"
|
|
281
|
+
"net/http/httptest"
|
|
282
|
+
"testing"
|
|
283
|
+
|
|
284
|
+
"github.com/go-chi/chi/v5"
|
|
285
|
+
"github.com/stretchr/testify/assert"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
func setupTestRouter() *chi.Mux {
|
|
289
|
+
r := chi.NewRouter()
|
|
290
|
+
r.Mount("/api/v1", apiRouter())
|
|
291
|
+
return r
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
func TestCreateUser(t *testing.T) {
|
|
295
|
+
router := setupTestRouter()
|
|
296
|
+
|
|
297
|
+
tests := []struct {
|
|
298
|
+
name string
|
|
299
|
+
body map[string]interface{}
|
|
300
|
+
wantStatus int
|
|
301
|
+
}{
|
|
302
|
+
{
|
|
303
|
+
name: "valid user",
|
|
304
|
+
body: map[string]interface{}{
|
|
305
|
+
"email": "test@example.com",
|
|
306
|
+
"name": "Test User",
|
|
307
|
+
"password": "password123",
|
|
308
|
+
},
|
|
309
|
+
wantStatus: http.StatusCreated,
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "missing email",
|
|
313
|
+
body: map[string]interface{}{
|
|
314
|
+
"name": "Test User",
|
|
315
|
+
"password": "password123",
|
|
316
|
+
},
|
|
317
|
+
wantStatus: http.StatusBadRequest,
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
for _, tt := range tests {
|
|
322
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
323
|
+
body, _ := json.Marshal(tt.body)
|
|
324
|
+
req := httptest.NewRequest("POST", "/api/v1/users", bytes.NewBuffer(body))
|
|
325
|
+
req.Header.Set("Content-Type", "application/json")
|
|
326
|
+
|
|
327
|
+
rec := httptest.NewRecorder()
|
|
328
|
+
router.ServeHTTP(rec, req)
|
|
329
|
+
|
|
330
|
+
assert.Equal(t, tt.wantStatus, rec.Code)
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
\`\`\`
|
|
335
|
+
|
|
336
|
+
## ✅ DO
|
|
337
|
+
- Use \`chi.URLParam()\` to get URL parameters
|
|
338
|
+
- Use context middleware pattern for loading resources
|
|
339
|
+
- Use \`r.Group()\` for applying middleware to route groups
|
|
340
|
+
- Use \`r.Mount()\` for modular subrouters
|
|
341
|
+
- Use \`middleware.Timeout\` to prevent hanging requests
|
|
342
|
+
- Pass \`r.Context()\` to service layer
|
|
343
|
+
|
|
344
|
+
## ❌ DON'T
|
|
345
|
+
- Don't forget to call \`next.ServeHTTP()\` in middleware
|
|
346
|
+
- Don't use global state - pass dependencies through context or closures
|
|
347
|
+
- Don't ignore errors from \`json.NewDecoder().Decode()\`
|
|
348
|
+
- Don't write to response after calling \`next.ServeHTTP()\`
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Code Review Skill
|
|
2
|
+
|
|
3
|
+
## PR Size Guidelines
|
|
4
|
+
- **Ideal**: < 400 lines changed
|
|
5
|
+
- **Acceptable**: 400-800 lines
|
|
6
|
+
- **Too Large**: > 800 lines (request split)
|
|
7
|
+
- **Exception**: Generated files, migrations, dependencies
|
|
8
|
+
|
|
9
|
+
## Review Checklist
|
|
10
|
+
|
|
11
|
+
### Correctness
|
|
12
|
+
- [ ] Does the code do what it's supposed to?
|
|
13
|
+
- [ ] Are edge cases handled?
|
|
14
|
+
- [ ] Are error cases handled?
|
|
15
|
+
- [ ] Does it match the requirements/ticket?
|
|
16
|
+
|
|
17
|
+
### Security (OWASP Top 10)
|
|
18
|
+
- [ ] Input validation present? (Injection prevention)
|
|
19
|
+
- [ ] Authentication/authorization correct?
|
|
20
|
+
- [ ] No sensitive data exposure? (logs, errors, responses)
|
|
21
|
+
- [ ] No SQL injection risks? (parameterized queries)
|
|
22
|
+
- [ ] No XSS vulnerabilities? (output encoding)
|
|
23
|
+
- [ ] Secrets not hardcoded? (use env vars)
|
|
24
|
+
- [ ] CSRF protection for state-changing operations?
|
|
25
|
+
- [ ] Dependencies up to date? (known vulnerabilities)
|
|
26
|
+
|
|
27
|
+
### Performance
|
|
28
|
+
- [ ] No N+1 queries?
|
|
29
|
+
- [ ] Large lists paginated?
|
|
30
|
+
- [ ] Expensive operations cached?
|
|
31
|
+
- [ ] No blocking operations in hot paths?
|
|
32
|
+
- [ ] Indexes added for new queries?
|
|
33
|
+
|
|
34
|
+
### Maintainability
|
|
35
|
+
- [ ] Code is readable?
|
|
36
|
+
- [ ] Functions are focused (single responsibility)?
|
|
37
|
+
- [ ] Names are meaningful?
|
|
38
|
+
- [ ] No magic numbers?
|
|
39
|
+
- [ ] Complex logic has comments explaining "why"?
|
|
40
|
+
|
|
41
|
+
### Testing
|
|
42
|
+
- [ ] Tests cover happy path?
|
|
43
|
+
- [ ] Tests cover error cases?
|
|
44
|
+
- [ ] Tests are maintainable?
|
|
45
|
+
- [ ] No flaky tests introduced?
|
|
46
|
+
|
|
47
|
+
## Automation (Let Tools Handle These)
|
|
48
|
+
\`\`\`yaml
|
|
49
|
+
# .github/workflows/pr-checks.yml
|
|
50
|
+
name: PR Checks
|
|
51
|
+
on: [pull_request]
|
|
52
|
+
jobs:
|
|
53
|
+
checks:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
- run: npm ci
|
|
58
|
+
- run: npm run lint # Code style
|
|
59
|
+
- run: npm run typecheck # Type errors
|
|
60
|
+
- run: npm run test # Unit tests
|
|
61
|
+
- run: npm audit # Security vulnerabilities
|
|
62
|
+
\`\`\`
|
|
63
|
+
|
|
64
|
+
## Giving Feedback
|
|
65
|
+
|
|
66
|
+
### Feedback Prefixes
|
|
67
|
+
\`\`\`markdown
|
|
68
|
+
[MUST] → Required change, blocking
|
|
69
|
+
[SHOULD] → Strong recommendation
|
|
70
|
+
[NIT] → Minor suggestion, non-blocking
|
|
71
|
+
[Q] → Question for clarification
|
|
72
|
+
[PRAISE] → Highlight good code
|
|
73
|
+
\`\`\`
|
|
74
|
+
|
|
75
|
+
### Good Feedback Examples
|
|
76
|
+
\`\`\`markdown
|
|
77
|
+
# ✅ GOOD FEEDBACK
|
|
78
|
+
"[SHOULD] Consider using early return here to reduce nesting - it makes the happy path clearer"
|
|
79
|
+
|
|
80
|
+
"[NIT] This could be extracted to a constant, but not blocking"
|
|
81
|
+
|
|
82
|
+
"[Q] What happens if the user array is empty here?"
|
|
83
|
+
|
|
84
|
+
"[PRAISE] Nice use of the builder pattern here!"
|
|
85
|
+
|
|
86
|
+
# ❌ BAD FEEDBACK
|
|
87
|
+
"This code is bad"
|
|
88
|
+
"You should know better"
|
|
89
|
+
"Why didn't you...?"
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
## Response Templates
|
|
93
|
+
|
|
94
|
+
### Requesting Changes
|
|
95
|
+
\`\`\`markdown
|
|
96
|
+
Thanks for the PR! I have a few suggestions:
|
|
97
|
+
|
|
98
|
+
1. **Security**: [specific issue]
|
|
99
|
+
2. **Performance**: [specific issue]
|
|
100
|
+
|
|
101
|
+
Happy to discuss any of these!
|
|
102
|
+
\`\`\`
|
|
103
|
+
|
|
104
|
+
### Approving with Comments
|
|
105
|
+
\`\`\`markdown
|
|
106
|
+
LGTM! 🚀
|
|
107
|
+
|
|
108
|
+
A few optional suggestions for future consideration:
|
|
109
|
+
- [non-blocking suggestion]
|
|
110
|
+
|
|
111
|
+
Nice work on [specific positive aspect].
|
|
112
|
+
\`\`\`
|
|
113
|
+
|
|
114
|
+
## Review Time Guidelines
|
|
115
|
+
- Respond within 24 hours (business days)
|
|
116
|
+
- Small PRs: Review immediately if possible
|
|
117
|
+
- Large PRs: Schedule dedicated time
|
|
118
|
+
- Time-box reviews: 30-60 min max per session
|
|
119
|
+
|
|
120
|
+
## ❌ DON'T
|
|
121
|
+
- Be harsh or personal
|
|
122
|
+
- Nitpick style (use linters)
|
|
123
|
+
- Approve without reading
|
|
124
|
+
- Let PRs sit for days
|
|
125
|
+
- Request changes without explanation
|
|
126
|
+
|
|
127
|
+
## ✅ DO
|
|
128
|
+
- Be specific and constructive
|
|
129
|
+
- Explain the "why"
|
|
130
|
+
- Suggest alternatives with code examples
|
|
131
|
+
- Acknowledge good code
|
|
132
|
+
- Use automation for style/formatting
|
|
133
|
+
- Prioritize blocking issues first
|