autoworkflow 3.1.5 → 3.6.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/post-edit.sh +190 -17
- package/.claude/hooks/pre-edit.sh +221 -0
- package/.claude/hooks/session-check.sh +90 -0
- package/.claude/settings.json +56 -6
- package/.claude/settings.local.json +5 -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 +163 -52
- package/package.json +1 -1
- package/system/triggers.md +256 -17
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Gin Framework Skill
|
|
2
|
+
|
|
3
|
+
## Application Setup
|
|
4
|
+
\`\`\`go
|
|
5
|
+
package main
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
"log"
|
|
9
|
+
"net/http"
|
|
10
|
+
"os"
|
|
11
|
+
|
|
12
|
+
"github.com/gin-gonic/gin"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func main() {
|
|
16
|
+
// Set mode from environment
|
|
17
|
+
if os.Getenv("GIN_MODE") == "release" {
|
|
18
|
+
gin.SetMode(gin.ReleaseMode)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
r := gin.New()
|
|
22
|
+
|
|
23
|
+
// Global middleware
|
|
24
|
+
r.Use(gin.Logger())
|
|
25
|
+
r.Use(gin.Recovery())
|
|
26
|
+
r.Use(CORSMiddleware())
|
|
27
|
+
|
|
28
|
+
// Health check
|
|
29
|
+
r.GET("/health", func(c *gin.Context) {
|
|
30
|
+
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// API routes
|
|
34
|
+
setupRoutes(r)
|
|
35
|
+
|
|
36
|
+
log.Fatal(r.Run(":8080"))
|
|
37
|
+
}
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
## Route Groups and Handlers
|
|
41
|
+
\`\`\`go
|
|
42
|
+
func setupRoutes(r *gin.Engine) {
|
|
43
|
+
// Public routes
|
|
44
|
+
public := r.Group("/api/v1")
|
|
45
|
+
{
|
|
46
|
+
public.POST("/auth/login", loginHandler)
|
|
47
|
+
public.POST("/auth/register", registerHandler)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Protected routes
|
|
51
|
+
protected := r.Group("/api/v1")
|
|
52
|
+
protected.Use(AuthMiddleware())
|
|
53
|
+
{
|
|
54
|
+
// Users
|
|
55
|
+
users := protected.Group("/users")
|
|
56
|
+
{
|
|
57
|
+
users.GET("", listUsersHandler)
|
|
58
|
+
users.GET("/:id", getUserHandler)
|
|
59
|
+
users.PUT("/:id", updateUserHandler)
|
|
60
|
+
users.DELETE("/:id", deleteUserHandler)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Posts (with additional middleware)
|
|
64
|
+
posts := protected.Group("/posts")
|
|
65
|
+
posts.Use(RateLimitMiddleware())
|
|
66
|
+
{
|
|
67
|
+
posts.GET("", listPostsHandler)
|
|
68
|
+
posts.POST("", createPostHandler)
|
|
69
|
+
posts.GET("/:id", getPostHandler)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
\`\`\`
|
|
74
|
+
|
|
75
|
+
## Request Binding and Validation
|
|
76
|
+
\`\`\`go
|
|
77
|
+
// Request DTOs with validation tags
|
|
78
|
+
type CreateUserRequest struct {
|
|
79
|
+
Email string \`json:"email" binding:"required,email"\`
|
|
80
|
+
Name string \`json:"name" binding:"required,min=2,max=100"\`
|
|
81
|
+
Password string \`json:"password" binding:"required,min=8"\`
|
|
82
|
+
Age int \`json:"age" binding:"omitempty,gte=0,lte=150"\`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type UpdateUserRequest struct {
|
|
86
|
+
Name *string \`json:"name" binding:"omitempty,min=2,max=100"\`
|
|
87
|
+
Age *int \`json:"age" binding:"omitempty,gte=0,lte=150"\`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type PaginationQuery struct {
|
|
91
|
+
Page int \`form:"page" binding:"omitempty,min=1"\`
|
|
92
|
+
PerPage int \`form:"per_page" binding:"omitempty,min=1,max=100"\`
|
|
93
|
+
Sort string \`form:"sort" binding:"omitempty,oneof=asc desc"\`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func createUserHandler(c *gin.Context) {
|
|
97
|
+
var req CreateUserRequest
|
|
98
|
+
if err := c.ShouldBindJSON(&req); err != nil {
|
|
99
|
+
c.JSON(http.StatusBadRequest, gin.H{
|
|
100
|
+
"error": "validation_error",
|
|
101
|
+
"details": formatValidationErrors(err),
|
|
102
|
+
})
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
user, err := userService.Create(c.Request.Context(), req)
|
|
107
|
+
if err != nil {
|
|
108
|
+
handleError(c, err)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
c.JSON(http.StatusCreated, user)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func listUsersHandler(c *gin.Context) {
|
|
116
|
+
var query PaginationQuery
|
|
117
|
+
if err := c.ShouldBindQuery(&query); err != nil {
|
|
118
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Set defaults
|
|
123
|
+
if query.Page == 0 {
|
|
124
|
+
query.Page = 1
|
|
125
|
+
}
|
|
126
|
+
if query.PerPage == 0 {
|
|
127
|
+
query.PerPage = 20
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
users, total, err := userService.List(c.Request.Context(), query)
|
|
131
|
+
if err != nil {
|
|
132
|
+
handleError(c, err)
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
c.JSON(http.StatusOK, gin.H{
|
|
137
|
+
"data": users,
|
|
138
|
+
"total": total,
|
|
139
|
+
"page": query.Page,
|
|
140
|
+
"per_page": query.PerPage,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
\`\`\`
|
|
144
|
+
|
|
145
|
+
## Custom Middleware
|
|
146
|
+
\`\`\`go
|
|
147
|
+
// CORS Middleware
|
|
148
|
+
func CORSMiddleware() gin.HandlerFunc {
|
|
149
|
+
return func(c *gin.Context) {
|
|
150
|
+
c.Header("Access-Control-Allow-Origin", "*")
|
|
151
|
+
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
|
|
152
|
+
c.Header("Access-Control-Allow-Headers", "Authorization,Content-Type")
|
|
153
|
+
|
|
154
|
+
if c.Request.Method == "OPTIONS" {
|
|
155
|
+
c.AbortWithStatus(http.StatusNoContent)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
c.Next()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Auth Middleware with JWT
|
|
164
|
+
func AuthMiddleware() gin.HandlerFunc {
|
|
165
|
+
return func(c *gin.Context) {
|
|
166
|
+
authHeader := c.GetHeader("Authorization")
|
|
167
|
+
if authHeader == "" {
|
|
168
|
+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
169
|
+
"error": "missing authorization header",
|
|
170
|
+
})
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
|
175
|
+
claims, err := validateToken(tokenString)
|
|
176
|
+
if err != nil {
|
|
177
|
+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
178
|
+
"error": "invalid token",
|
|
179
|
+
})
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Store user info in context
|
|
184
|
+
c.Set("userID", claims.UserID)
|
|
185
|
+
c.Set("userRole", claims.Role)
|
|
186
|
+
c.Next()
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Rate Limiting Middleware
|
|
191
|
+
func RateLimitMiddleware() gin.HandlerFunc {
|
|
192
|
+
limiter := rate.NewLimiter(rate.Every(time.Second), 10)
|
|
193
|
+
|
|
194
|
+
return func(c *gin.Context) {
|
|
195
|
+
if !limiter.Allow() {
|
|
196
|
+
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
|
|
197
|
+
"error": "rate limit exceeded",
|
|
198
|
+
})
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
c.Next()
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Request ID Middleware
|
|
206
|
+
func RequestIDMiddleware() gin.HandlerFunc {
|
|
207
|
+
return func(c *gin.Context) {
|
|
208
|
+
requestID := c.GetHeader("X-Request-ID")
|
|
209
|
+
if requestID == "" {
|
|
210
|
+
requestID = uuid.New().String()
|
|
211
|
+
}
|
|
212
|
+
c.Set("requestID", requestID)
|
|
213
|
+
c.Header("X-Request-ID", requestID)
|
|
214
|
+
c.Next()
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
\`\`\`
|
|
218
|
+
|
|
219
|
+
## Error Handling
|
|
220
|
+
\`\`\`go
|
|
221
|
+
// Centralized error handling
|
|
222
|
+
func handleError(c *gin.Context, err error) {
|
|
223
|
+
switch {
|
|
224
|
+
case errors.Is(err, ErrNotFound):
|
|
225
|
+
c.JSON(http.StatusNotFound, gin.H{"error": "resource not found"})
|
|
226
|
+
case errors.Is(err, ErrUnauthorized):
|
|
227
|
+
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
228
|
+
case errors.Is(err, ErrForbidden):
|
|
229
|
+
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
230
|
+
case errors.Is(err, ErrConflict):
|
|
231
|
+
c.JSON(http.StatusConflict, gin.H{"error": "resource already exists"})
|
|
232
|
+
default:
|
|
233
|
+
log.Printf("internal error: %v", err)
|
|
234
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Format validation errors
|
|
239
|
+
func formatValidationErrors(err error) map[string]string {
|
|
240
|
+
errors := make(map[string]string)
|
|
241
|
+
|
|
242
|
+
var ve validator.ValidationErrors
|
|
243
|
+
if stderrors.As(err, &ve) {
|
|
244
|
+
for _, e := range ve {
|
|
245
|
+
field := strings.ToLower(e.Field())
|
|
246
|
+
switch e.Tag() {
|
|
247
|
+
case "required":
|
|
248
|
+
errors[field] = "this field is required"
|
|
249
|
+
case "email":
|
|
250
|
+
errors[field] = "invalid email format"
|
|
251
|
+
case "min":
|
|
252
|
+
errors[field] = fmt.Sprintf("must be at least %s characters", e.Param())
|
|
253
|
+
case "max":
|
|
254
|
+
errors[field] = fmt.Sprintf("must be at most %s characters", e.Param())
|
|
255
|
+
default:
|
|
256
|
+
errors[field] = "invalid value"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return errors
|
|
262
|
+
}
|
|
263
|
+
\`\`\`
|
|
264
|
+
|
|
265
|
+
## Testing
|
|
266
|
+
\`\`\`go
|
|
267
|
+
package handler_test
|
|
268
|
+
|
|
269
|
+
import (
|
|
270
|
+
"bytes"
|
|
271
|
+
"encoding/json"
|
|
272
|
+
"net/http"
|
|
273
|
+
"net/http/httptest"
|
|
274
|
+
"testing"
|
|
275
|
+
|
|
276
|
+
"github.com/gin-gonic/gin"
|
|
277
|
+
"github.com/stretchr/testify/assert"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
func setupTestRouter() *gin.Engine {
|
|
281
|
+
gin.SetMode(gin.TestMode)
|
|
282
|
+
r := gin.New()
|
|
283
|
+
setupRoutes(r)
|
|
284
|
+
return r
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
func TestCreateUser(t *testing.T) {
|
|
288
|
+
router := setupTestRouter()
|
|
289
|
+
|
|
290
|
+
tests := []struct {
|
|
291
|
+
name string
|
|
292
|
+
body map[string]interface{}
|
|
293
|
+
wantStatus int
|
|
294
|
+
}{
|
|
295
|
+
{
|
|
296
|
+
name: "valid user",
|
|
297
|
+
body: map[string]interface{}{
|
|
298
|
+
"email": "test@example.com",
|
|
299
|
+
"name": "Test User",
|
|
300
|
+
"password": "password123",
|
|
301
|
+
},
|
|
302
|
+
wantStatus: http.StatusCreated,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: "missing email",
|
|
306
|
+
body: map[string]interface{}{
|
|
307
|
+
"name": "Test User",
|
|
308
|
+
"password": "password123",
|
|
309
|
+
},
|
|
310
|
+
wantStatus: http.StatusBadRequest,
|
|
311
|
+
},
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
for _, tt := range tests {
|
|
315
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
316
|
+
body, _ := json.Marshal(tt.body)
|
|
317
|
+
req := httptest.NewRequest("POST", "/api/v1/users", bytes.NewBuffer(body))
|
|
318
|
+
req.Header.Set("Content-Type", "application/json")
|
|
319
|
+
|
|
320
|
+
w := httptest.NewRecorder()
|
|
321
|
+
router.ServeHTTP(w, req)
|
|
322
|
+
|
|
323
|
+
assert.Equal(t, tt.wantStatus, w.Code)
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
\`\`\`
|
|
328
|
+
|
|
329
|
+
## ✅ DO
|
|
330
|
+
- Use route groups for organization
|
|
331
|
+
- Use \`ShouldBindJSON\` (returns error) not \`BindJSON\` (aborts on error)
|
|
332
|
+
- Use context from \`c.Request.Context()\` for database calls
|
|
333
|
+
- Set up proper validation tags on request structs
|
|
334
|
+
- Create centralized error handling
|
|
335
|
+
- Use middleware for cross-cutting concerns
|
|
336
|
+
|
|
337
|
+
## ❌ DON'T
|
|
338
|
+
- Don't use \`c.Abort()\` without setting response
|
|
339
|
+
- Don't ignore validation errors
|
|
340
|
+
- Don't store sensitive data in \`gin.Context\` (use request context)
|
|
341
|
+
- Don't forget to call \`c.Next()\` in middleware when continuing chain
|
|
342
|
+
- Don't use global variables for dependencies
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Git Skill
|
|
2
|
+
|
|
3
|
+
## Conventional Commits
|
|
4
|
+
\`\`\`
|
|
5
|
+
<type>(<scope>): <description>
|
|
6
|
+
|
|
7
|
+
[optional body]
|
|
8
|
+
|
|
9
|
+
[optional footer(s)]
|
|
10
|
+
\`\`\`
|
|
11
|
+
|
|
12
|
+
**Types:**
|
|
13
|
+
- \`feat\`: New feature (MINOR version bump)
|
|
14
|
+
- \`fix\`: Bug fix (PATCH version bump)
|
|
15
|
+
- \`docs\`: Documentation only
|
|
16
|
+
- \`style\`: Formatting, no code change
|
|
17
|
+
- \`refactor\`: Code change, no feature/fix
|
|
18
|
+
- \`test\`: Adding/updating tests
|
|
19
|
+
- \`chore\`: Build, deps, configs
|
|
20
|
+
|
|
21
|
+
**Examples:**
|
|
22
|
+
\`\`\`bash
|
|
23
|
+
feat(auth): add password reset flow
|
|
24
|
+
fix(api): handle null response from user endpoint
|
|
25
|
+
docs(readme): update installation steps
|
|
26
|
+
refactor(utils): extract date formatting helpers
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
## Branch Strategy
|
|
30
|
+
\`\`\`
|
|
31
|
+
main # Production-ready
|
|
32
|
+
├── develop # Integration branch
|
|
33
|
+
├── feature/auth-login # New features
|
|
34
|
+
├── fix/user-loading # Bug fixes
|
|
35
|
+
└── refactor/api-cleanup # Refactoring
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
## Git Stash Workflow
|
|
39
|
+
\`\`\`bash
|
|
40
|
+
# Save work in progress
|
|
41
|
+
git stash push -m "WIP: feature description"
|
|
42
|
+
|
|
43
|
+
# List stashes
|
|
44
|
+
git stash list
|
|
45
|
+
|
|
46
|
+
# Apply and keep stash
|
|
47
|
+
git stash apply stash@{0}
|
|
48
|
+
|
|
49
|
+
# Apply and remove stash
|
|
50
|
+
git stash pop
|
|
51
|
+
|
|
52
|
+
# Create branch from stash
|
|
53
|
+
git stash branch new-branch stash@{0}
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
## Interactive Rebase
|
|
57
|
+
\`\`\`bash
|
|
58
|
+
# Rebase last 3 commits
|
|
59
|
+
git rebase -i HEAD~3
|
|
60
|
+
|
|
61
|
+
# Rebase onto main
|
|
62
|
+
git rebase -i main
|
|
63
|
+
|
|
64
|
+
# Commands in interactive mode:
|
|
65
|
+
# pick = use commit
|
|
66
|
+
# reword = edit commit message
|
|
67
|
+
# squash = combine with previous commit
|
|
68
|
+
# fixup = squash but discard message
|
|
69
|
+
# drop = remove commit
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
## Merge Conflict Resolution
|
|
73
|
+
\`\`\`bash
|
|
74
|
+
# 1. Start merge/rebase
|
|
75
|
+
git merge feature-branch
|
|
76
|
+
|
|
77
|
+
# 2. View conflicts
|
|
78
|
+
git status
|
|
79
|
+
|
|
80
|
+
# 3. After resolving in editor
|
|
81
|
+
git add <resolved-files>
|
|
82
|
+
git merge --continue
|
|
83
|
+
# OR for rebase:
|
|
84
|
+
git rebase --continue
|
|
85
|
+
|
|
86
|
+
# Abort if needed
|
|
87
|
+
git merge --abort
|
|
88
|
+
git rebase --abort
|
|
89
|
+
\`\`\`
|
|
90
|
+
|
|
91
|
+
## Pre-commit Hooks (with Husky)
|
|
92
|
+
\`\`\`bash
|
|
93
|
+
# Install husky
|
|
94
|
+
npx husky init
|
|
95
|
+
|
|
96
|
+
# Add pre-commit hook
|
|
97
|
+
echo "npm run lint && npm run test" > .husky/pre-commit
|
|
98
|
+
|
|
99
|
+
# Add commit-msg hook for conventional commits
|
|
100
|
+
echo 'npx commitlint --edit "$1"' > .husky/commit-msg
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
## ❌ DON'T
|
|
104
|
+
- Commit secrets or .env files
|
|
105
|
+
- Force push to main/develop
|
|
106
|
+
- Commit large generated files
|
|
107
|
+
- Write vague commit messages
|
|
108
|
+
- Rebase public/shared branches
|
|
109
|
+
|
|
110
|
+
## ✅ DO
|
|
111
|
+
- Commit often, push regularly
|
|
112
|
+
- Write descriptive commit messages
|
|
113
|
+
- Use .gitignore properly
|
|
114
|
+
- Review diff before committing
|
|
115
|
+
- Use stash for quick context switches
|
|
116
|
+
- Squash WIP commits before merging
|