fix(skeleton): enforce chi {param} URL syntax in agent guidance
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Agents were generating `:id` (Echo/Gin style) instead of `{id}` (chi style),
causing routes to not match. Updated api-designer, go-specialist agents and
skeleton CLAUDE.md with explicit CRITICAL notes about brace syntax.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
863dfd3214
commit
9085965864
@ -465,6 +465,7 @@ Progressive complexity paths for building Slack-like platforms:
|
||||
| `slackpath-2-async-worker-pipeline` | Background jobs: Producer/consumer with Redis | Redis |
|
||||
| `slackpath-3-realtime-chat` | WebSockets: Pub/sub broadcasting | Redis |
|
||||
| `slackpath-4-microservice-constellation` | Service mesh: Auth + Chat + Worker coordination | CockroachDB + Redis |
|
||||
| `slackpath-5-full-lifecycle` | Full SDLC: All 10 phases with explicit artifact approvals | CockroachDB |
|
||||
|
||||
**Running a slackpath:**
|
||||
```bash
|
||||
@ -499,7 +500,8 @@ cookbooks/
|
||||
├── slackpath-1-authenticated-service.yaml
|
||||
├── slackpath-2-async-worker-pipeline.yaml
|
||||
├── slackpath-3-realtime-chat.yaml
|
||||
└── slackpath-4-microservice-constellation.yaml
|
||||
├── slackpath-4-microservice-constellation.yaml
|
||||
└── slackpath-5-full-lifecycle.yaml
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
@ -76,6 +76,7 @@ When discussing code: "add to **platform**" = edit rdev; "add to **skeleton**" =
|
||||
- **Validation:** Use `validate.New()` accumulator for 2+ field checks in handlers: `v := validate.New(); v.Required(req.Name, "name"); v.Required(req.Type, "type"); if err := v.Error() { ... }`. Single-field checks can stay inline. NEVER duplicate validation logic that exists in `internal/validate`.
|
||||
- **Error wrapping:** ALWAYS use `%w` (not `%v`) when wrapping errors in `fmt.Errorf`. Using `%v` stringifies the error and breaks `errors.Is`/`errors.As` chains. For non-error types (structs, slices), create a typed error implementing `error` instead of stringifying with `%v`.
|
||||
- **Context propagation:** NEVER use `context.Background()` in handlers, services, or adapters that receive a context parameter. Always derive from parent context. Use `context.WithoutCancel(ctx)` for fire-and-forget goroutines that need tracing but independent cancellation.
|
||||
- **Cookbooks:** Load `.claude/skills/cookbook-scripts/SKILL.md` before writing/modifying any cookbook script or tree.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
|
||||
@ -82,30 +82,61 @@ List responses:
|
||||
|
||||
## Handler Pattern
|
||||
|
||||
Handlers return `error` and are wrapped with `app.Wrap()`:
|
||||
|
||||
```go
|
||||
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Parse request
|
||||
// Handler returns error - wrapped with app.Wrap() in routes.go
|
||||
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) error {
|
||||
// 1. Parse and validate request
|
||||
var req CreateUserRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
httpresponse.BadRequest(w, "invalid request body")
|
||||
return
|
||||
if err := app.BindAndValidate(r, &req); err != nil {
|
||||
return err // HTTPError - mapped to status code
|
||||
}
|
||||
|
||||
// 2. Validate
|
||||
if err := req.Validate(); err != nil {
|
||||
httpresponse.ValidationError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Call service
|
||||
// 2. Call service
|
||||
user, err := h.service.CreateUser(r.Context(), req.ToDomain())
|
||||
if err != nil {
|
||||
httpresponse.HandleError(w, err)
|
||||
return
|
||||
return mapDomainError(err) // Convert domain errors to HTTPErrors
|
||||
}
|
||||
|
||||
// 4. Respond
|
||||
httpresponse.Created(w, user)
|
||||
// 3. Respond
|
||||
httpresponse.Created(w, r, user)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Route registration with `app.Wrap()`:
|
||||
|
||||
```go
|
||||
// routes.go
|
||||
application.Route("/api/users", func(r chi.Router) {
|
||||
r.Get("/", app.Wrap(h.List))
|
||||
r.Post("/", app.Wrap(h.Create))
|
||||
r.Get("/{id}", app.Wrap(h.Get)) // Use {id} for chi URL params
|
||||
r.Put("/{id}", app.Wrap(h.Update))
|
||||
r.Delete("/{id}", app.Wrap(h.Delete))
|
||||
})
|
||||
```
|
||||
|
||||
## Chi URL Parameters
|
||||
|
||||
**CRITICAL:** Use brace syntax `{param}` for URL parameters. chi does NOT support colon syntax `:param`.
|
||||
|
||||
```go
|
||||
// CORRECT - chi uses braces
|
||||
r.Get("/users/{id}", handler) // chi.URLParam(r, "id")
|
||||
r.Get("/users/{id}/posts/{postID}", handler) // chi.URLParam(r, "postID")
|
||||
|
||||
// WRONG - will not work with chi
|
||||
r.Get("/users/:id", handler) // This registers literal "/:id" path!
|
||||
```
|
||||
|
||||
Extracting URL parameters:
|
||||
|
||||
```go
|
||||
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) error {
|
||||
id := chi.URLParam(r, "id") // Extract from {id} in route pattern
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -10,13 +10,38 @@ You are a Go expert for the {{PROJECT_NAME}} monorepo. You write idiomatic, prod
|
||||
|
||||
## Stack
|
||||
|
||||
- **Router:** chi/v5
|
||||
- **Router:** chi/v5 (CRITICAL: Use `{param}` syntax for URL params, never `:param`)
|
||||
- **Database:** sqlx (no GORM)
|
||||
- **Logging:** slog
|
||||
- **Config:** environment variables
|
||||
- **Architecture:** Hexagonal (ports & adapters)
|
||||
- **Workspace:** go.work with shared pkg/
|
||||
|
||||
## Chi Router Patterns
|
||||
|
||||
```go
|
||||
// Route registration
|
||||
application.Route("/api/users", func(r chi.Router) {
|
||||
r.Get("/", handler.List)
|
||||
r.Post("/", handler.Create)
|
||||
r.Get("/{id}", handler.Get) // Use {id}, never :id
|
||||
r.Put("/{id}", handler.Update)
|
||||
r.Delete("/{id}", handler.Delete)
|
||||
|
||||
// Protected routes with middleware
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(auth.Middleware(...))
|
||||
r.Post("/{id}/admin", handler.Admin)
|
||||
})
|
||||
})
|
||||
|
||||
// Extract URL params with chi.URLParam
|
||||
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) error {
|
||||
id := chi.URLParam(r, "id")
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Patterns
|
||||
|
||||
### Service Structure
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
|
||||
- **Handler pattern:** All handlers return `error`, wrapped with `app.Wrap()`. HTTPErrors map to status codes; raw errors become 500.
|
||||
- **Request binding:** Always use `app.Bind()` or `app.BindAndValidate()`. Never use raw `json.NewDecoder`.
|
||||
- **URL parameters:** Use brace syntax `{param}` for chi URL parameters. NEVER use colon syntax `:param` - it won't work and will cause 404s. Extract with `chi.URLParam(r, "param")`.
|
||||
- **Error types:** Use `httperror.BadRequest`, `httperror.NotFound`, etc. Never bare `http.Error()`.
|
||||
- **Response envelope:** Use `httpresponse.OK`, `httpresponse.Created`, `httpresponse.NoContent`. All responses use `{data, meta}` envelope.
|
||||
- **Auth middleware:** Auth is opt-in. Use `auth.Middleware()` in route groups for protected endpoints.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user