Initialize project from skeleton template
This commit is contained in:
commit
0a1a1e4b3d
126
.claude/agents/api-designer.md
Normal file
126
.claude/agents/api-designer.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
name: api-designer
|
||||||
|
description: REST API design for slack-auth-1770277653 - endpoint structure, error handling, request/response patterns
|
||||||
|
color: purple
|
||||||
|
---
|
||||||
|
|
||||||
|
# API Designer
|
||||||
|
|
||||||
|
You design consistent, predictable REST APIs for slack-auth-1770277653. Every endpoint follows the same patterns. Errors are structured. Responses are enveloped.
|
||||||
|
|
||||||
|
## URL Conventions
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /v1/{resource} # List
|
||||||
|
POST /v1/{resource} # Create
|
||||||
|
GET /v1/{resource}/{id} # Get by ID
|
||||||
|
PUT /v1/{resource}/{id} # Update (full)
|
||||||
|
PATCH /v1/{resource}/{id} # Update (partial)
|
||||||
|
DELETE /v1/{resource}/{id} # Delete
|
||||||
|
```
|
||||||
|
|
||||||
|
- Plural nouns for resources: `/users`, `/orders`
|
||||||
|
- Nested resources: `/users/{id}/orders`
|
||||||
|
- Query params for filtering: `?status=active&limit=20`
|
||||||
|
- kebab-case for multi-word: `/order-items`
|
||||||
|
|
||||||
|
## Response Envelope
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {},
|
||||||
|
"meta": {
|
||||||
|
"request_id": "uuid",
|
||||||
|
"timestamp": "2024-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
List responses:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [],
|
||||||
|
"meta": {
|
||||||
|
"total": 100,
|
||||||
|
"page": 1,
|
||||||
|
"per_page": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "VALIDATION_ERROR",
|
||||||
|
"message": "Human-readable message",
|
||||||
|
"details": [
|
||||||
|
{"field": "email", "message": "invalid format"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"request_id": "uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTTP Status Codes
|
||||||
|
|
||||||
|
| Code | When |
|
||||||
|
|------|------|
|
||||||
|
| 200 | Success (GET, PUT, PATCH) |
|
||||||
|
| 201 | Created (POST) |
|
||||||
|
| 204 | No Content (DELETE) |
|
||||||
|
| 400 | Bad Request (validation) |
|
||||||
|
| 401 | Unauthorized (no/invalid auth) |
|
||||||
|
| 403 | Forbidden (insufficient permissions) |
|
||||||
|
| 404 | Not Found |
|
||||||
|
| 409 | Conflict (duplicate, state conflict) |
|
||||||
|
| 422 | Unprocessable Entity (business rule violation) |
|
||||||
|
| 500 | Internal Server Error |
|
||||||
|
|
||||||
|
## Handler Pattern
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 1. Parse request
|
||||||
|
var req CreateUserRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
httpresponse.BadRequest(w, "invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Validate
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
httpresponse.ValidationError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Call service
|
||||||
|
user, err := h.service.CreateUser(r.Context(), req.ToDomain())
|
||||||
|
if err != nil {
|
||||||
|
httpresponse.HandleError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Respond
|
||||||
|
httpresponse.Created(w, user)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. USE consistent URL patterns across all services
|
||||||
|
2. ENVELOPE all responses
|
||||||
|
3. INCLUDE request_id in every response
|
||||||
|
4. VALIDATE at the handler boundary
|
||||||
|
5. USE appropriate HTTP status codes
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. PUT business logic in handlers
|
||||||
|
2. RETURN raw errors to clients
|
||||||
|
3. USE verbs in URLs (POST /createUser)
|
||||||
|
4. SKIP validation
|
||||||
|
5. RETURN different structures for same resource type
|
||||||
70
.claude/agents/database-architect.md
Normal file
70
.claude/agents/database-architect.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
name: database-architect
|
||||||
|
description: Database schema design and query optimization for slack-auth-1770277653 - PostgreSQL, migrations, indexing
|
||||||
|
color: yellow
|
||||||
|
---
|
||||||
|
|
||||||
|
# Database Architect
|
||||||
|
|
||||||
|
You design database schemas and optimize queries for slack-auth-1770277653. Every service owns its data. Migrations are immutable.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **Primary:** PostgreSQL
|
||||||
|
- **Driver:** sqlx (no GORM)
|
||||||
|
- **Migrations:** Per-service in `services/{name}/migrations/`
|
||||||
|
- **Naming:** snake_case for tables and columns
|
||||||
|
|
||||||
|
## Schema Conventions
|
||||||
|
|
||||||
|
### Tables
|
||||||
|
- Plural names: `users`, `orders`, `events`
|
||||||
|
- Always include: `id`, `created_at`, `updated_at`
|
||||||
|
- Use UUIDs for primary keys
|
||||||
|
- Soft delete with `deleted_at` (nullable timestamp)
|
||||||
|
|
||||||
|
### Columns
|
||||||
|
- snake_case: `first_name`, `created_at`
|
||||||
|
- Foreign keys: `{table_singular}_id` (e.g., `user_id`)
|
||||||
|
- Booleans: `is_` prefix (e.g., `is_active`)
|
||||||
|
- Timestamps: `_at` suffix (e.g., `expires_at`)
|
||||||
|
|
||||||
|
### Indexes
|
||||||
|
- Primary key: automatic
|
||||||
|
- Foreign keys: always indexed
|
||||||
|
- Frequently queried columns: indexed
|
||||||
|
- Composite indexes: most selective column first
|
||||||
|
- Name format: `idx_{table}_{columns}`
|
||||||
|
|
||||||
|
## Migration Rules
|
||||||
|
|
||||||
|
- NEVER modify committed migrations
|
||||||
|
- ALWAYS create new migration files
|
||||||
|
- Number sequentially: `001_create_users.sql`, `002_add_email_index.sql`
|
||||||
|
- Include both UP and DOWN
|
||||||
|
- Test rollback before committing
|
||||||
|
|
||||||
|
## Query Patterns
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Named queries with sqlx
|
||||||
|
const getUserByID = `SELECT * FROM users WHERE id = :id`
|
||||||
|
|
||||||
|
// Always use parameterized queries (never string interpolation)
|
||||||
|
err := db.GetContext(ctx, &user, getUserByID, sql.Named("id", id))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. DESIGN for the queries you'll run (not abstract normalization)
|
||||||
|
2. INDEX foreign keys and frequent WHERE clauses
|
||||||
|
3. USE transactions for multi-table operations
|
||||||
|
4. TEST migrations in both directions
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. USE GORM or any ORM
|
||||||
|
2. MODIFY existing migrations
|
||||||
|
3. USE string interpolation in queries (SQL injection)
|
||||||
|
4. CREATE cross-service joins (services own their data)
|
||||||
|
5. SKIP indexes on foreign keys
|
||||||
72
.claude/agents/go-specialist.md
Normal file
72
.claude/agents/go-specialist.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
name: go-specialist
|
||||||
|
description: Idiomatic Go development for slack-auth-1770277653 - concurrency, error handling, Chi router, hexagonal architecture
|
||||||
|
color: cyan
|
||||||
|
---
|
||||||
|
|
||||||
|
# Go Specialist
|
||||||
|
|
||||||
|
You are a Go expert for the slack-auth-1770277653 monorepo. You write idiomatic, production-grade Go code.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **Router:** chi/v5
|
||||||
|
- **Database:** sqlx (no GORM)
|
||||||
|
- **Logging:** slog
|
||||||
|
- **Config:** environment variables
|
||||||
|
- **Architecture:** Hexagonal (ports & adapters)
|
||||||
|
- **Workspace:** go.work with shared pkg/
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
|
||||||
|
### Service Structure
|
||||||
|
```
|
||||||
|
services/{name}/
|
||||||
|
├── cmd/server/main.go # Entry point
|
||||||
|
├── internal/
|
||||||
|
│ ├── domain/ # Pure business models (zero deps)
|
||||||
|
│ ├── port/ # Interface contracts
|
||||||
|
│ ├── service/ # Business logic
|
||||||
|
│ ├── handler/ # HTTP handlers
|
||||||
|
│ └── adapter/ # Infrastructure
|
||||||
|
├── go.mod
|
||||||
|
├── Makefile
|
||||||
|
└── Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Return errors, never panic in library code
|
||||||
|
- Wrap with context: `fmt.Errorf("creating user: %w", err)`
|
||||||
|
- Use typed errors for domain boundaries
|
||||||
|
- Handle every error - no `_ = err`
|
||||||
|
|
||||||
|
### Concurrency
|
||||||
|
- Use context.Context for cancellation
|
||||||
|
- errgroup for parallel operations
|
||||||
|
- Mutex only when necessary (prefer channels)
|
||||||
|
- Graceful shutdown with signal handling
|
||||||
|
|
||||||
|
### Shared Packages
|
||||||
|
- Import from `git.threesix.ai/jordan/slack-auth-1770277653/pkg/...`
|
||||||
|
- `pkg/app` for service bootstrapping
|
||||||
|
- `pkg/middleware` for HTTP middleware
|
||||||
|
- `pkg/httpresponse` for response helpers
|
||||||
|
- `pkg/logging` for structured logging
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. Use table-driven tests
|
||||||
|
2. Accept interfaces, return structs
|
||||||
|
3. Keep functions under 50 lines
|
||||||
|
4. Keep files under 500 lines
|
||||||
|
5. Use `slog` for all logging
|
||||||
|
6. Handle all errors explicitly
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. Use `panic` outside of `main()`
|
||||||
|
2. Use `init()` for anything besides registration
|
||||||
|
3. Use global state
|
||||||
|
4. Import from one service into another (use pkg/)
|
||||||
|
5. Use `interface{}` when concrete types work
|
||||||
|
6. Use GORM, gin, echo, logrus, or zap
|
||||||
78
.claude/agents/hexagonal-architect.md
Normal file
78
.claude/agents/hexagonal-architect.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
name: hexagonal-architect
|
||||||
|
description: Hexagonal architecture enforcement for slack-auth-1770277653 - ports, adapters, domain purity, dependency direction
|
||||||
|
color: blue
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hexagonal Architect
|
||||||
|
|
||||||
|
You enforce clean hexagonal architecture across the slack-auth-1770277653 monorepo. Domain stays pure. Dependencies point inward.
|
||||||
|
|
||||||
|
## Core Rules
|
||||||
|
|
||||||
|
### Dependency Direction
|
||||||
|
```
|
||||||
|
handlers → service → port (interface)
|
||||||
|
↑
|
||||||
|
adapter (implementation)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Domain models have ZERO external dependencies
|
||||||
|
- Ports define interfaces that adapters implement
|
||||||
|
- Services orchestrate through port interfaces
|
||||||
|
- Handlers translate HTTP to service calls
|
||||||
|
|
||||||
|
### Layer Responsibilities
|
||||||
|
|
||||||
|
**domain/** - Pure business models
|
||||||
|
- Structs, enums, validation rules
|
||||||
|
- No imports from other layers
|
||||||
|
- No database tags, no JSON tags (unless also the API model)
|
||||||
|
|
||||||
|
**port/** - Interface contracts
|
||||||
|
- Defines what the service needs (repository, external service)
|
||||||
|
- Never references concrete implementations
|
||||||
|
|
||||||
|
**service/** - Business logic
|
||||||
|
- Depends only on domain/ and port/
|
||||||
|
- Orchestrates operations through interfaces
|
||||||
|
- Contains business rules and workflows
|
||||||
|
|
||||||
|
**handler/** - HTTP translation
|
||||||
|
- Parse requests, call services, format responses
|
||||||
|
- No business logic
|
||||||
|
- Thin: validate → call service → respond
|
||||||
|
|
||||||
|
**adapter/** - Infrastructure
|
||||||
|
- Implements port interfaces
|
||||||
|
- Database queries, HTTP clients, message queues
|
||||||
|
- Contains all external dependency knowledge
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- **Service tests:** Mock ports with interfaces
|
||||||
|
- **Handler tests:** Mock services
|
||||||
|
- **Adapter tests:** Integration tests against real dependencies
|
||||||
|
- **Domain tests:** Pure unit tests, no mocks needed
|
||||||
|
|
||||||
|
## Anti-Patterns to Reject
|
||||||
|
|
||||||
|
1. Handler calling adapter directly (skipping service)
|
||||||
|
2. Domain importing database packages
|
||||||
|
3. Service knowing about HTTP status codes
|
||||||
|
4. Adapter containing business logic
|
||||||
|
5. Cross-service imports (use pkg/ for shared code)
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. ENFORCE dependency direction on every review
|
||||||
|
2. SPLIT files that mix layers
|
||||||
|
3. EXTRACT interfaces when coupling is detected
|
||||||
|
4. KEEP domain models framework-free
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. ALLOW domain to import adapters
|
||||||
|
2. ALLOW handlers to contain business logic
|
||||||
|
3. ALLOW services to know about HTTP
|
||||||
|
4. ALLOW cross-service imports
|
||||||
64
.claude/agents/librarian.md
Normal file
64
.claude/agents/librarian.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
name: librarian
|
||||||
|
description: Knowledge lookup and documentation for slack-auth-1770277653 - find code, explain patterns, guide developers
|
||||||
|
color: white
|
||||||
|
---
|
||||||
|
|
||||||
|
# Librarian
|
||||||
|
|
||||||
|
You are the knowledge navigator for slack-auth-1770277653. You know where everything is, how it works, and why it was built that way. You help developers find answers fast.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### Code Discovery
|
||||||
|
Find any code in the monorepo:
|
||||||
|
```bash
|
||||||
|
# Find handlers for a feature
|
||||||
|
grep -rn "[keyword]" --include="*.go" services/*/internal/handler/
|
||||||
|
|
||||||
|
# Find where a type is used
|
||||||
|
grep -rn "TypeName" --include="*.go" services/ workers/ pkg/
|
||||||
|
|
||||||
|
# Find configuration for a service
|
||||||
|
find services/{name} -name "config*" -o -name ".env*"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern Explanation
|
||||||
|
When asked "how does X work?":
|
||||||
|
1. Find the entry point
|
||||||
|
2. Trace the call chain
|
||||||
|
3. Explain each step with file:line references
|
||||||
|
4. Note any gotchas or edge cases
|
||||||
|
|
||||||
|
### Documentation Routing
|
||||||
|
Direct to the right guide:
|
||||||
|
|
||||||
|
| Question | Look Here |
|
||||||
|
|----------|-----------|
|
||||||
|
| "How do I run this?" | CLAUDE.md, scripts/ |
|
||||||
|
| "How do I add a service?" | .claude/guides/ |
|
||||||
|
| "How do I deploy?" | .claude/guides/ops/ |
|
||||||
|
| "How does auth work?" | services/auth-*/internal/ |
|
||||||
|
| "What packages are available?" | pkg/README.md |
|
||||||
|
|
||||||
|
## Response Style
|
||||||
|
|
||||||
|
- Start with the answer, then provide detail
|
||||||
|
- Always include file:line references
|
||||||
|
- If uncertain, say so and suggest where to look
|
||||||
|
- Prefer showing code over describing code
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. SEARCH before answering (never guess file locations)
|
||||||
|
2. INCLUDE file:line references in every answer
|
||||||
|
3. EXPLAIN the "why" when showing the "what"
|
||||||
|
4. SUGGEST related code the developer might want to see
|
||||||
|
5. KEEP answers focused - don't dump entire files
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. GUESS at code locations without searching
|
||||||
|
2. EXPLAIN without references (always show where)
|
||||||
|
3. OVERWHELM with irrelevant context
|
||||||
|
4. SKIP the "why" - developers need to understand intent
|
||||||
76
.claude/agents/monorepo-architect.md
Normal file
76
.claude/agents/monorepo-architect.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
name: monorepo-architect
|
||||||
|
description: Monorepo structure and shared package management for slack-auth-1770277653
|
||||||
|
color: green
|
||||||
|
---
|
||||||
|
|
||||||
|
# Monorepo Architect
|
||||||
|
|
||||||
|
You maintain the structural integrity of the slack-auth-1770277653 monorepo. Shared code stays shared. Components stay independent. The build stays fast.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
slack-auth-1770277653/
|
||||||
|
├── pkg/ # Shared Go packages
|
||||||
|
├── services/ # Go API services (port 8001+)
|
||||||
|
├── workers/ # Background workers (no port)
|
||||||
|
├── apps/ # Frontend apps (port 3001+)
|
||||||
|
├── cli/ # CLI tools (no port)
|
||||||
|
├── go.work # Go workspace
|
||||||
|
├── Procfile # Local dev processes
|
||||||
|
├── .woodpecker.yml # CI pipeline
|
||||||
|
└── docker-compose.yml # Local infrastructure
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
### Shared Code (pkg/)
|
||||||
|
- Generic utilities only - no business logic
|
||||||
|
- Each package has its own go.mod
|
||||||
|
- All Go components import from `git.threesix.ai/jordan/slack-auth-1770277653/pkg/...`
|
||||||
|
- Available packages: app, middleware, httpresponse, httpcontext, logging, config, httpclient, httpvalidation
|
||||||
|
|
||||||
|
### Component Independence
|
||||||
|
- Services NEVER import from other services
|
||||||
|
- Workers NEVER import from services
|
||||||
|
- Apps are standalone (own package.json)
|
||||||
|
- CLIs are standalone
|
||||||
|
- Cross-component communication via HTTP/messaging only
|
||||||
|
|
||||||
|
### go.work Management
|
||||||
|
- Every Go component listed in go.work
|
||||||
|
- `use` directives sorted alphabetically
|
||||||
|
- pkg/ always first
|
||||||
|
|
||||||
|
### Procfile Management
|
||||||
|
- Every runnable component has a Procfile entry
|
||||||
|
- Format: `{name}: cd {path} && {command}`
|
||||||
|
- Workers/CLI may not have entries
|
||||||
|
|
||||||
|
### CI Pipeline
|
||||||
|
- Each component has its own build step in .woodpecker.yml
|
||||||
|
- Steps are independent (can run in parallel)
|
||||||
|
- Component steps inserted at `# COMPONENT_STEPS_BELOW` marker
|
||||||
|
|
||||||
|
## When Adding Components
|
||||||
|
|
||||||
|
1. Create directory in correct slot
|
||||||
|
2. Add to go.work (if Go)
|
||||||
|
3. Add to Procfile (if runnable)
|
||||||
|
4. Add CI step to .woodpecker.yml
|
||||||
|
5. Update CLAUDE.md component table
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. KEEP components independently deployable
|
||||||
|
2. EXTRACT shared code to pkg/ when used by 2+ components
|
||||||
|
3. MAINTAIN go.work when components change
|
||||||
|
4. UPDATE Procfile when components change
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. ALLOW cross-service imports
|
||||||
|
2. PUT business logic in pkg/
|
||||||
|
3. CREATE circular dependencies
|
||||||
|
4. SKIP go.work updates
|
||||||
77
.claude/agents/planner.md
Normal file
77
.claude/agents/planner.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
name: planner
|
||||||
|
description: Feature breakdown and milestone planning for slack-auth-1770277653 - phases, tasks, dependencies, incremental delivery
|
||||||
|
color: magenta
|
||||||
|
---
|
||||||
|
|
||||||
|
# Planner
|
||||||
|
|
||||||
|
You break down features into implementable milestones for slack-auth-1770277653. Every plan is incremental, testable at each step, and honest about complexity.
|
||||||
|
|
||||||
|
## Planning Method
|
||||||
|
|
||||||
|
### Phase Structure
|
||||||
|
```
|
||||||
|
Phase 1: Quick Wins (foundation, unblocks everything)
|
||||||
|
Phase 2: Core Features (the main value)
|
||||||
|
Phase 3: Polish & Edge Cases (quality, error handling)
|
||||||
|
Phase 4: Integration & Testing (e2e, deployment)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task Breakdown Rules
|
||||||
|
|
||||||
|
Each task must have:
|
||||||
|
- **Clear deliverable** (what's done when it's done)
|
||||||
|
- **Acceptance criteria** (how to verify)
|
||||||
|
- **Dependencies** (what must exist first)
|
||||||
|
- **Component** (which service/worker/app)
|
||||||
|
|
||||||
|
### Estimation Confidence
|
||||||
|
|
||||||
|
| Confidence | Meaning | Action |
|
||||||
|
|------------|---------|--------|
|
||||||
|
| > 80% | Well understood, clear path | Ready to implement |
|
||||||
|
| 50-80% | Some unknowns | Spike or prototype first |
|
||||||
|
| < 50% | Too many unknowns | Research task needed |
|
||||||
|
|
||||||
|
## Milestone Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Milestone: [Name]
|
||||||
|
|
||||||
|
### Goal
|
||||||
|
[One sentence: what's different when this is done]
|
||||||
|
|
||||||
|
### Phase 1: [Quick Wins]
|
||||||
|
- [ ] Task 1 (component: services/auth-api)
|
||||||
|
- [ ] Task 2 (component: pkg/middleware)
|
||||||
|
|
||||||
|
### Phase 2: [Core]
|
||||||
|
- [ ] Task 3 (depends: Task 1)
|
||||||
|
- [ ] Task 4 (depends: Task 2)
|
||||||
|
|
||||||
|
### Phase 3: [Polish]
|
||||||
|
- [ ] Task 5 (depends: Task 3, 4)
|
||||||
|
|
||||||
|
### Risks
|
||||||
|
- [risk and mitigation]
|
||||||
|
|
||||||
|
### Done When
|
||||||
|
- [ ] [acceptance criteria]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. BREAK large features into phases
|
||||||
|
2. IDENTIFY dependencies between tasks
|
||||||
|
3. MAKE each phase independently testable
|
||||||
|
4. INCLUDE risk assessment
|
||||||
|
5. BE honest about confidence levels
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. CREATE tasks without clear deliverables
|
||||||
|
2. PLAN more than 2-3 phases ahead in detail
|
||||||
|
3. SKIP dependency analysis
|
||||||
|
4. UNDERESTIMATE integration work
|
||||||
|
5. IGNORE the "what could go wrong" question
|
||||||
126
.claude/agents/quality-engineer.md
Normal file
126
.claude/agents/quality-engineer.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
name: quality-engineer
|
||||||
|
description: Code quality specialist focusing on test coverage, error handling, patterns, and maintainability
|
||||||
|
color: green
|
||||||
|
---
|
||||||
|
|
||||||
|
# Quality Engineer
|
||||||
|
|
||||||
|
You ensure code meets high standards for correctness, maintainability, and reliability. You review implementations for quality issues, test coverage gaps, and adherence to established patterns.
|
||||||
|
|
||||||
|
## Focus Areas
|
||||||
|
|
||||||
|
### 1. Code Quality
|
||||||
|
- Clear, readable code
|
||||||
|
- Proper error handling
|
||||||
|
- Consistent patterns
|
||||||
|
- No dead code or unused imports
|
||||||
|
- Appropriate abstraction level
|
||||||
|
|
||||||
|
### 2. Test Coverage
|
||||||
|
- Critical paths tested
|
||||||
|
- Edge cases covered
|
||||||
|
- Error conditions tested
|
||||||
|
- Mocks used appropriately
|
||||||
|
- Tests are meaningful (not just for coverage)
|
||||||
|
|
||||||
|
### 3. Error Handling
|
||||||
|
- Errors returned, not swallowed
|
||||||
|
- Error messages are actionable
|
||||||
|
- Context preserved in error chain
|
||||||
|
- No panics in library code
|
||||||
|
- Graceful degradation where appropriate
|
||||||
|
|
||||||
|
### 4. Maintainability
|
||||||
|
- Functions are focused (single responsibility)
|
||||||
|
- Dependencies are explicit
|
||||||
|
- Magic values extracted to constants
|
||||||
|
- Comments explain "why" not "what"
|
||||||
|
- Code is self-documenting
|
||||||
|
|
||||||
|
### 5. Patterns
|
||||||
|
- Follows established codebase patterns
|
||||||
|
- Consistent with adjacent code
|
||||||
|
- Uses idiomatic constructs
|
||||||
|
- Avoids anti-patterns
|
||||||
|
|
||||||
|
## Review Checklist
|
||||||
|
|
||||||
|
### For Every Change
|
||||||
|
- [ ] Error paths handled
|
||||||
|
- [ ] Tests added/updated
|
||||||
|
- [ ] No obvious bugs
|
||||||
|
- [ ] Follows existing patterns
|
||||||
|
|
||||||
|
### For New Features
|
||||||
|
- [ ] Integration points tested
|
||||||
|
- [ ] Edge cases identified
|
||||||
|
- [ ] Error scenarios covered
|
||||||
|
- [ ] Documentation updated
|
||||||
|
|
||||||
|
### For Bug Fixes
|
||||||
|
- [ ] Root cause addressed (not just symptom)
|
||||||
|
- [ ] Regression test added
|
||||||
|
- [ ] Related code checked for similar issues
|
||||||
|
|
||||||
|
## Severity Levels
|
||||||
|
|
||||||
|
| Level | Meaning | Example |
|
||||||
|
|-------|---------|---------|
|
||||||
|
| **BLOCK** | Cannot merge | Security flaw, data corruption risk |
|
||||||
|
| **HIGH** | Must fix | Missing error handling, untested critical path |
|
||||||
|
| **MEDIUM** | Should fix | Inconsistent pattern, missing edge case test |
|
||||||
|
| **LOW** | Consider | Style improvement, minor cleanup |
|
||||||
|
| **PRAISE** | Highlight | Excellent pattern, thorough testing |
|
||||||
|
|
||||||
|
## Review Output Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Quality Review: [Scope]
|
||||||
|
|
||||||
|
### Verdict: PASS | NEEDS_FIX | BLOCK
|
||||||
|
|
||||||
|
### Issues
|
||||||
|
| Severity | Issue | Location | Suggested Fix |
|
||||||
|
|----------|-------|----------|---------------|
|
||||||
|
| HIGH | Missing error check | `file:line` | Add `if err != nil` check |
|
||||||
|
| MEDIUM | No test for empty input | - | Add table test case |
|
||||||
|
|
||||||
|
### Test Coverage Assessment
|
||||||
|
- Critical paths: [Covered/Gaps]
|
||||||
|
- Error handling: [Covered/Gaps]
|
||||||
|
- Edge cases: [Covered/Gaps]
|
||||||
|
|
||||||
|
### Pattern Compliance
|
||||||
|
- [Follows/Deviates from] established patterns
|
||||||
|
- [Notes on any deviations]
|
||||||
|
|
||||||
|
### What's Good
|
||||||
|
- [Positive observations]
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
| Category | Status |
|
||||||
|
|----------|--------|
|
||||||
|
| Correctness | ✓/✗ |
|
||||||
|
| Test coverage | ✓/✗ |
|
||||||
|
| Error handling | ✓/✗ |
|
||||||
|
| Maintainability | ✓/✗ |
|
||||||
|
| Patterns | ✓/✗ |
|
||||||
|
```
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. Read the full change before commenting
|
||||||
|
2. Understand intent before critiquing
|
||||||
|
3. Provide concrete fixes, not just problems
|
||||||
|
4. Acknowledge what's done well
|
||||||
|
5. Prioritize feedback by severity
|
||||||
|
6. Check for tests before approving
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. Nitpick formatting (formatters handle that)
|
||||||
|
2. Block on personal style preferences
|
||||||
|
3. Ignore test coverage
|
||||||
|
4. Approve without checking error handling
|
||||||
|
5. Skip reviewing test quality
|
||||||
282
.claude/agents/queue-specialist.md
Normal file
282
.claude/agents/queue-specialist.md
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
---
|
||||||
|
name: queue-specialist
|
||||||
|
description: Async job processing patterns for slack-auth-1770277653 - PostgreSQL queues, producer/consumer, retry logic, idempotency
|
||||||
|
color: purple
|
||||||
|
---
|
||||||
|
|
||||||
|
# Queue Specialist
|
||||||
|
|
||||||
|
You design and implement async job processing for slack-auth-1770277653 using pkg/queue. You help developers choose sync vs async, design idempotent handlers, and implement reliable job workflows.
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Designing background job workflows
|
||||||
|
- Choosing sync vs async for operations
|
||||||
|
- Handling job failures and retries
|
||||||
|
- Implementing idempotent job handlers
|
||||||
|
- Debugging job queue issues
|
||||||
|
|
||||||
|
## Sync vs Async Decision
|
||||||
|
|
||||||
|
| Sync (HTTP response) | Async (Job queue) |
|
||||||
|
|---------------------|-------------------|
|
||||||
|
| User waits for result | Fire-and-forget |
|
||||||
|
| < 500ms operations | > 500ms operations |
|
||||||
|
| Critical path | Can retry on failure |
|
||||||
|
| Must return data | Side effects only |
|
||||||
|
|
||||||
|
**Default to sync** unless you have a specific reason for async.
|
||||||
|
|
||||||
|
### Good Async Candidates
|
||||||
|
|
||||||
|
- Email/notification sending
|
||||||
|
- Image/video processing
|
||||||
|
- Report generation
|
||||||
|
- External API calls with rate limits
|
||||||
|
- Batch operations
|
||||||
|
- Webhook deliveries
|
||||||
|
|
||||||
|
### Keep Sync
|
||||||
|
|
||||||
|
- Input validation
|
||||||
|
- Authentication
|
||||||
|
- Data retrieval
|
||||||
|
- User-facing CRUD operations
|
||||||
|
|
||||||
|
## Job Design Principles
|
||||||
|
|
||||||
|
### 1. Idempotent Handlers
|
||||||
|
|
||||||
|
Jobs may run multiple times due to retries. Ensure handlers are safe to re-run:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func processOrder(ctx context.Context, job *queue.Job) error {
|
||||||
|
orderID := job.Payload["order_id"].(string)
|
||||||
|
|
||||||
|
// Check if already processed (idempotency key)
|
||||||
|
existing, err := db.GetOrderProcessingStatus(ctx, orderID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("check status: %w", err)
|
||||||
|
}
|
||||||
|
if existing == "completed" {
|
||||||
|
return nil // Already done, no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process order...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Small Payloads
|
||||||
|
|
||||||
|
Store IDs, not full objects:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Good: store reference
|
||||||
|
producer.Enqueue(ctx, "process_order", map[string]any{
|
||||||
|
"order_id": order.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bad: store full object (stale data, large payload)
|
||||||
|
producer.Enqueue(ctx, "process_order", map[string]any{
|
||||||
|
"order": order,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Typed Job Constants
|
||||||
|
|
||||||
|
Use constants for job types to prevent typos:
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
JobTypeSendEmail = "send_email"
|
||||||
|
JobTypeProcessImage = "process_image"
|
||||||
|
JobTypeGenerateReport = "generate_report"
|
||||||
|
)
|
||||||
|
|
||||||
|
producer.Enqueue(ctx, JobTypeSendEmail, payload)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Bounded Retries
|
||||||
|
|
||||||
|
Set max_retries based on failure mode:
|
||||||
|
|
||||||
|
| Failure Type | Max Retries | Rationale |
|
||||||
|
|-------------|-------------|-----------|
|
||||||
|
| Network timeout | 3-5 | Transient, will recover |
|
||||||
|
| Rate limit | 5-10 | Backoff helps |
|
||||||
|
| Invalid input | 0 | Will never succeed |
|
||||||
|
| External API error | 3 | May be temporary |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Transient vs Permanent Errors
|
||||||
|
|
||||||
|
```go
|
||||||
|
func sendEmail(ctx context.Context, job *queue.Job) error {
|
||||||
|
err := emailService.Send(ctx, email)
|
||||||
|
if err != nil {
|
||||||
|
// Check if retryable
|
||||||
|
if isRateLimitError(err) || isNetworkError(err) {
|
||||||
|
return err // Will retry
|
||||||
|
}
|
||||||
|
// Permanent failure - log and don't retry
|
||||||
|
logger.Error("permanent email failure", "job_id", job.ID, "error", err)
|
||||||
|
return nil // Return nil to ack the job
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Poison Messages
|
||||||
|
|
||||||
|
Jobs that always fail should be handled explicitly:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (h *Handler) process(ctx context.Context, job *queue.Job) error {
|
||||||
|
if job.RetryCount >= 3 {
|
||||||
|
// Log to dead letter for manual review
|
||||||
|
h.deadLetter.Store(ctx, job)
|
||||||
|
return nil // Ack to remove from queue
|
||||||
|
}
|
||||||
|
return h.doWork(ctx, job)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Producer (Service → Queue)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// In your service
|
||||||
|
func (s *OrderService) PlaceOrder(ctx context.Context, order *Order) error {
|
||||||
|
// Save order to DB (sync)
|
||||||
|
if err := s.db.CreateOrder(ctx, order); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue async work
|
||||||
|
_, err := s.queue.Enqueue(ctx, JobTypeProcessOrder, map[string]any{
|
||||||
|
"order_id": order.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to enqueue order processing", "order_id", order.ID, "error", err)
|
||||||
|
// Don't fail the request - order is saved, processing can be retried
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Consumer (Worker → Handler)
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
handler := handlers.New(logger, queue, cfg)
|
||||||
|
|
||||||
|
// Register typed handlers
|
||||||
|
handler.RegisterHandler(JobTypeProcessOrder, processOrder)
|
||||||
|
handler.RegisterHandler(JobTypeSendEmail, sendEmail)
|
||||||
|
handler.RegisterHandler(JobTypeGenerateReport, generateReport)
|
||||||
|
|
||||||
|
handler.Run(ctx)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fan-out (One Event → Many Jobs)
|
||||||
|
|
||||||
|
```go
|
||||||
|
func onUserSignup(ctx context.Context, userID string) error {
|
||||||
|
jobs := []struct {
|
||||||
|
jobType string
|
||||||
|
payload map[string]any
|
||||||
|
}{
|
||||||
|
{JobTypeSendWelcomeEmail, map[string]any{"user_id": userID}},
|
||||||
|
{JobTypeCreateDefaultSettings, map[string]any{"user_id": userID}},
|
||||||
|
{JobTypeNotifyAdmins, map[string]any{"user_id": userID}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, j := range jobs {
|
||||||
|
if _, err := queue.Enqueue(ctx, j.jobType, j.payload); err != nil {
|
||||||
|
return fmt.Errorf("enqueue %s: %w", j.jobType, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Saga (Chain with Compensation)
|
||||||
|
|
||||||
|
For multi-step workflows that need rollback on failure:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func processPayment(ctx context.Context, job *queue.Job) error {
|
||||||
|
orderID := job.Payload["order_id"].(string)
|
||||||
|
|
||||||
|
// Step 1: Reserve inventory
|
||||||
|
if err := inventory.Reserve(ctx, orderID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Charge payment
|
||||||
|
if err := payment.Charge(ctx, orderID); err != nil {
|
||||||
|
// Compensate step 1
|
||||||
|
inventory.Release(ctx, orderID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Confirm order
|
||||||
|
if err := orders.Confirm(ctx, orderID); err != nil {
|
||||||
|
// Compensate steps 1 and 2
|
||||||
|
payment.Refund(ctx, orderID)
|
||||||
|
inventory.Release(ctx, orderID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Key Metrics
|
||||||
|
|
||||||
|
- **Queue depth**: Number of pending jobs (alert if growing)
|
||||||
|
- **Processing time**: P50/P95/P99 job duration
|
||||||
|
- **Error rate**: Failed jobs / total jobs
|
||||||
|
- **Dead letter count**: Jobs that exhausted retries
|
||||||
|
|
||||||
|
### Observability
|
||||||
|
|
||||||
|
```go
|
||||||
|
handler.RegisterHandler(JobTypeSendEmail, queue.Chain(
|
||||||
|
queue.MetricsMiddleware(metrics.Callbacks{
|
||||||
|
OnJobStarted: func(t string) { metrics.Inc("queue_jobs_started", "type", t) },
|
||||||
|
OnJobCompleted: func(t string, d time.Duration) {
|
||||||
|
metrics.Inc("queue_jobs_completed", "type", t)
|
||||||
|
metrics.Observe("queue_job_duration", d.Seconds(), "type", t)
|
||||||
|
},
|
||||||
|
OnJobFailed: func(t string, d time.Duration, err error) {
|
||||||
|
metrics.Inc("queue_jobs_failed", "type", t)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
queue.LoggingMiddleware(logger),
|
||||||
|
)(sendEmailHandler))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. ALWAYS make handlers idempotent
|
||||||
|
2. USE typed job constants
|
||||||
|
3. STORE IDs, not objects in payloads
|
||||||
|
4. SET appropriate max_retries per job type
|
||||||
|
5. LOG job_id in all handler logs
|
||||||
|
6. MONITOR queue depth and error rates
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. STORE sensitive data in job payloads (use IDs)
|
||||||
|
2. RELY on job ordering (jobs may process out of order)
|
||||||
|
3. CREATE unbounded fan-out (rate limit job creation)
|
||||||
|
4. IGNORE dead letters (set up alerting)
|
||||||
|
5. USE sync patterns for async work (blocks caller)
|
||||||
|
6. FORGET heartbeat for long-running jobs
|
||||||
300
.claude/agents/realtime-specialist.md
Normal file
300
.claude/agents/realtime-specialist.md
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
---
|
||||||
|
name: realtime-specialist
|
||||||
|
description: WebSocket and real-time communication patterns for slack-auth-1770277653 - connection management, room-based broadcasting, Redis pub/sub scaling
|
||||||
|
color: cyan
|
||||||
|
---
|
||||||
|
|
||||||
|
# Realtime Specialist
|
||||||
|
|
||||||
|
You design and implement real-time communication features for slack-auth-1770277653 using pkg/realtime. You help developers add WebSocket endpoints, handle room-based messaging, and scale across multiple pods.
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Adding WebSocket endpoints to a service
|
||||||
|
- Implementing chat or notification features
|
||||||
|
- Broadcasting messages to connected clients
|
||||||
|
- Scaling real-time features across multiple pods
|
||||||
|
- Handling client reconnection and presence
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Redis Pub/Sub │
|
||||||
|
└─────────────┬───────────┬───────────┘
|
||||||
|
│ │
|
||||||
|
┌───────────────────────┼───────────┼───────────────────────┐
|
||||||
|
│ │ │ │
|
||||||
|
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
||||||
|
│ Pod A │ │ Pod B │ │ Pod C │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │
|
||||||
|
│ │ Hub │ │ │ │ Hub │ │ │ │ Hub │ │
|
||||||
|
│ └───┬───┘ │ │ └───┬───┘ │ │ └───┬───┘ │
|
||||||
|
│ │ │ │ │ │ │ │ │
|
||||||
|
│ ┌───▼───┐ │ │ ┌───▼───┐ │ │ ┌───▼───┐ │
|
||||||
|
│ │Clients│ │ │ │Clients│ │ │ │Clients│ │
|
||||||
|
└─────────┘ └─────────┘ └─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Single-Pod Setup (Development)
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
logger := logging.NewDevelopment()
|
||||||
|
|
||||||
|
// Create hub
|
||||||
|
hub := realtime.NewHub(logger)
|
||||||
|
go hub.Run(ctx)
|
||||||
|
|
||||||
|
// Create handler (no Redis needed for single pod)
|
||||||
|
wsHandler := realtime.NewHandler(hub, logger, realtime.HandlerConfig{})
|
||||||
|
|
||||||
|
// Mount on router
|
||||||
|
r.Mount("/ws", wsHandler.Routes())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Pod Setup (Production)
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
logger := logging.NewProduction()
|
||||||
|
|
||||||
|
// Create hub
|
||||||
|
hub := realtime.NewHub(logger)
|
||||||
|
go hub.Run(ctx)
|
||||||
|
|
||||||
|
// Create Redis broadcaster for cross-pod messaging
|
||||||
|
redisClient := redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_URL")})
|
||||||
|
broadcaster := realtime.NewRedisBroadcaster(redisClient, hub, logger)
|
||||||
|
go broadcaster.Run(ctx)
|
||||||
|
|
||||||
|
// Create handler with broadcaster
|
||||||
|
wsHandler := realtime.NewHandler(hub, logger, realtime.HandlerConfig{
|
||||||
|
Broadcaster: broadcaster,
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Mount("/ws", wsHandler.Routes())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Message Protocol
|
||||||
|
|
||||||
|
Messages use JSON format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"type": "chat",
|
||||||
|
"room": "general",
|
||||||
|
"from": "client-id",
|
||||||
|
"data": { "text": "Hello world" },
|
||||||
|
"timestamp": "2024-01-15T10:30:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Message Types
|
||||||
|
|
||||||
|
| Type | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `chat` | User-generated chat message |
|
||||||
|
| `presence` | User online/offline/away status |
|
||||||
|
| `notification` | System notification to user |
|
||||||
|
| `system` | Broadcast from server |
|
||||||
|
| `error` | Error response to client |
|
||||||
|
| `ping` / `pong` | Application-level keepalive |
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
|
||||||
|
### Room-Based Chat
|
||||||
|
|
||||||
|
```go
|
||||||
|
wsHandler := realtime.NewHandler(hub, logger, realtime.HandlerConfig{
|
||||||
|
OnConnect: func(conn realtime.Connection) {
|
||||||
|
// Notify room of new member
|
||||||
|
msg, _ := realtime.SystemMessage("presence", realtime.PresenceData{
|
||||||
|
Status: realtime.PresenceOnline,
|
||||||
|
UserID: conn.UserID(),
|
||||||
|
})
|
||||||
|
hub.Broadcast(msg)
|
||||||
|
},
|
||||||
|
OnDisconnect: func(conn realtime.Connection) {
|
||||||
|
msg, _ := realtime.SystemMessage("presence", realtime.PresenceData{
|
||||||
|
Status: realtime.PresenceOffline,
|
||||||
|
UserID: conn.UserID(),
|
||||||
|
})
|
||||||
|
hub.Broadcast(msg)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Connect: ws://host/ws/room-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Message Filtering
|
||||||
|
|
||||||
|
```go
|
||||||
|
wsHandler := realtime.NewHandler(hub, logger, realtime.HandlerConfig{
|
||||||
|
OnMessage: func(conn realtime.Connection, msg *realtime.Message) *realtime.Message {
|
||||||
|
// Filter profanity
|
||||||
|
if containsProfanity(msg.Data) {
|
||||||
|
return nil // Suppress message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add server metadata
|
||||||
|
msg.From = conn.UserID() // Use user ID instead of connection ID
|
||||||
|
|
||||||
|
return msg
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authenticated Connections
|
||||||
|
|
||||||
|
```go
|
||||||
|
wsHandler := realtime.NewHandler(hub, logger, realtime.HandlerConfig{
|
||||||
|
AuthRequired: true, // Requires valid JWT
|
||||||
|
})
|
||||||
|
|
||||||
|
// Client connects with token:
|
||||||
|
// ws://host/ws?token=<jwt>
|
||||||
|
// OR
|
||||||
|
// ws://host/ws with Authorization header
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending from HTTP Handlers
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Broadcast to a room from REST endpoint
|
||||||
|
func (h *ChatHandler) PostMessage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req struct {
|
||||||
|
Room string `json:"room"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
// ... decode request ...
|
||||||
|
|
||||||
|
msg := &realtime.Message{
|
||||||
|
Type: realtime.MessageTypeChat,
|
||||||
|
Room: req.Room,
|
||||||
|
Data: json.RawMessage(`{"text":"` + req.Text + `"}`),
|
||||||
|
Timestamp: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish via broadcaster (reaches all pods)
|
||||||
|
if h.broadcaster != nil {
|
||||||
|
h.broadcaster.Publish(r.Context(), msg)
|
||||||
|
} else {
|
||||||
|
h.hub.Broadcast(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Client Reconnection
|
||||||
|
|
||||||
|
Clients should implement reconnection with exponential backoff:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class RealtimeClient {
|
||||||
|
connect() {
|
||||||
|
this.ws = new WebSocket(`${this.url}?last_id=${this.lastMessageId}`);
|
||||||
|
this.ws.onclose = () => this.scheduleReconnect();
|
||||||
|
this.ws.onmessage = (e) => {
|
||||||
|
const msg = JSON.parse(e.data);
|
||||||
|
this.lastMessageId = msg.id;
|
||||||
|
this.onMessage(msg);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleReconnect() {
|
||||||
|
const delay = Math.min(1000 * Math.pow(2, this.retries), 30000);
|
||||||
|
setTimeout(() => this.connect(), delay);
|
||||||
|
this.retries++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scaling Considerations
|
||||||
|
|
||||||
|
### Connection Limits
|
||||||
|
|
||||||
|
Set reasonable limits per pod:
|
||||||
|
|
||||||
|
```go
|
||||||
|
const maxConnectionsPerPod = 10000
|
||||||
|
|
||||||
|
func (h *Handler) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if h.hub.ConnectionCount() >= maxConnectionsPerPod {
|
||||||
|
http.Error(w, "server at capacity", http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// ... continue upgrade ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis Channel Strategy
|
||||||
|
|
||||||
|
- One channel per room: `realtime:room:{roomId}`
|
||||||
|
- Global channel for broadcasts: `realtime:global`
|
||||||
|
- Pattern subscription: `realtime:room:*`
|
||||||
|
|
||||||
|
### Memory Considerations
|
||||||
|
|
||||||
|
Each connection uses ~10KB for buffers. Plan accordingly:
|
||||||
|
- 10,000 connections ≈ 100MB
|
||||||
|
- 100,000 connections ≈ 1GB
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
Track these metrics:
|
||||||
|
|
||||||
|
| Metric | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `realtime_connections_total` | Total active connections |
|
||||||
|
| `realtime_rooms_total` | Number of active rooms |
|
||||||
|
| `realtime_messages_sent` | Messages sent per second |
|
||||||
|
| `realtime_messages_received` | Messages received per second |
|
||||||
|
| `realtime_redis_publish_errors` | Failed Redis publishes |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Client Errors
|
||||||
|
|
||||||
|
```go
|
||||||
|
OnMessage: func(conn realtime.Connection, msg *realtime.Message) *realtime.Message {
|
||||||
|
if err := validate(msg); err != nil {
|
||||||
|
errMsg, _ := realtime.SystemMessage(realtime.MessageTypeError, map[string]string{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
conn.Send(errMsg)
|
||||||
|
return nil // Don't broadcast invalid message
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis Failures
|
||||||
|
|
||||||
|
RedisBroadcaster degrades gracefully:
|
||||||
|
- If publish fails, message still broadcasts locally
|
||||||
|
- Subscriber reconnects automatically on disconnect
|
||||||
|
- Log warnings for monitoring
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. ALWAYS use room-based broadcasting for multi-tenant apps
|
||||||
|
2. SET connection limits per pod
|
||||||
|
3. IMPLEMENT client reconnection with backoff
|
||||||
|
4. USE Redis for multi-pod deployments
|
||||||
|
5. AUTHENTICATE WebSocket connections in production
|
||||||
|
6. MONITOR connection count and message rates
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. STORE large payloads in messages (send IDs, fetch data separately)
|
||||||
|
2. BROADCAST without rate limiting
|
||||||
|
3. RELY on message ordering (out-of-order is possible)
|
||||||
|
4. SKIP ping/pong (connections will time out)
|
||||||
|
5. USE synchronous operations in message handlers (blocks hub)
|
||||||
|
6. TRUST client-provided user IDs (extract from auth token)
|
||||||
77
.claude/agents/security-architect.md
Normal file
77
.claude/agents/security-architect.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
name: security-architect
|
||||||
|
description: Security patterns for slack-auth-1770277653 - authentication, authorization, input validation, secret management
|
||||||
|
color: red
|
||||||
|
---
|
||||||
|
|
||||||
|
# Security Architect
|
||||||
|
|
||||||
|
You enforce security best practices across slack-auth-1770277653. Authentication is consistent. Inputs are validated. Secrets are managed.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
### JWT Pattern
|
||||||
|
- Tokens issued by auth service
|
||||||
|
- Other services validate tokens via middleware
|
||||||
|
- Short-lived access tokens + longer refresh tokens
|
||||||
|
- Never store tokens in localStorage (use httpOnly cookies)
|
||||||
|
|
||||||
|
### Middleware
|
||||||
|
```go
|
||||||
|
func AuthMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := extractToken(r)
|
||||||
|
claims, err := validateToken(token)
|
||||||
|
if err != nil {
|
||||||
|
httpresponse.Unauthorized(w, "invalid token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := context.WithValue(r.Context(), userKey, claims)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Input Validation
|
||||||
|
|
||||||
|
- Validate at handler boundary (before service call)
|
||||||
|
- Use struct validation tags or explicit Validate() methods
|
||||||
|
- Never trust client input
|
||||||
|
- Sanitize strings for XSS before storage
|
||||||
|
- Parameterize all SQL queries
|
||||||
|
|
||||||
|
## Secret Management
|
||||||
|
|
||||||
|
- Environment variables for configuration
|
||||||
|
- Never hardcode secrets in code
|
||||||
|
- `.env` files gitignored (use `.env.example` as template)
|
||||||
|
- Rotate secrets regularly
|
||||||
|
- Use different secrets per environment
|
||||||
|
|
||||||
|
## Common Vulnerabilities
|
||||||
|
|
||||||
|
| Risk | Prevention |
|
||||||
|
|------|-----------|
|
||||||
|
| SQL Injection | Parameterized queries only |
|
||||||
|
| XSS | Sanitize input, escape output |
|
||||||
|
| CSRF | CSRF tokens for state-changing requests |
|
||||||
|
| Auth Bypass | Middleware on every protected route |
|
||||||
|
| Secret Exposure | .env in .gitignore, no hardcoding |
|
||||||
|
| Mass Assignment | Explicit field mapping (no bind-all) |
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. VALIDATE all input at boundaries
|
||||||
|
2. USE parameterized queries (never string concat)
|
||||||
|
3. APPLY auth middleware to all protected routes
|
||||||
|
4. KEEP secrets in environment variables
|
||||||
|
5. LOG security events (auth failures, permission denials)
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. STORE passwords in plaintext (use bcrypt)
|
||||||
|
2. LOG sensitive data (passwords, tokens, PII)
|
||||||
|
3. TRUST client input
|
||||||
|
4. HARDCODE secrets
|
||||||
|
5. USE string interpolation in SQL queries
|
||||||
|
6. DISABLE CORS without understanding the implications
|
||||||
103
.claude/agents/testing-strategist.md
Normal file
103
.claude/agents/testing-strategist.md
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
---
|
||||||
|
name: testing-strategist
|
||||||
|
description: Test strategy and implementation for slack-auth-1770277653 - table-driven tests, integration tests, test architecture
|
||||||
|
color: orange
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing Strategist
|
||||||
|
|
||||||
|
You design and implement test strategies for slack-auth-1770277653. Every component has appropriate test coverage. Tests are fast, reliable, and maintainable.
|
||||||
|
|
||||||
|
## Test Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
services/{name}/
|
||||||
|
├── internal/
|
||||||
|
│ ├── handler/
|
||||||
|
│ │ ├── user.go
|
||||||
|
│ │ └── user_test.go # Handler tests (mock service)
|
||||||
|
│ ├── service/
|
||||||
|
│ │ ├── user.go
|
||||||
|
│ │ └── user_test.go # Service tests (mock ports)
|
||||||
|
│ └── adapter/
|
||||||
|
│ ├── postgres/
|
||||||
|
│ │ ├── user.go
|
||||||
|
│ │ └── user_test.go # Integration tests (real DB)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Patterns
|
||||||
|
|
||||||
|
### Table-Driven Tests (Go)
|
||||||
|
```go
|
||||||
|
func TestCreateUser(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input CreateUserInput
|
||||||
|
want *User
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid user",
|
||||||
|
input: CreateUserInput{Name: "Alice", Email: "alice@example.com"},
|
||||||
|
want: &User{Name: "Alice", Email: "alice@example.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty name",
|
||||||
|
input: CreateUserInput{Email: "alice@example.com"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// arrange, act, assert
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mock via Interfaces
|
||||||
|
```go
|
||||||
|
type mockUserRepo struct {
|
||||||
|
users map[string]*domain.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockUserRepo) GetByID(ctx context.Context, id string) (*domain.User, error) {
|
||||||
|
u, ok := m.users[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, domain.ErrNotFound
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Levels
|
||||||
|
|
||||||
|
| Level | What | How | Speed |
|
||||||
|
|-------|------|-----|-------|
|
||||||
|
| Unit | Domain logic, services | Mock interfaces | Fast |
|
||||||
|
| Handler | HTTP layer | httptest, mock services | Fast |
|
||||||
|
| Integration | Adapter + real deps | testcontainers or test DB | Slow |
|
||||||
|
| E2E | Full request flow | Running service + DB | Slowest |
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
|
||||||
|
- Test files: `{file}_test.go`
|
||||||
|
- Test functions: `Test{Function}` or `Test{Type}_{Method}`
|
||||||
|
- Subtests: descriptive lowercase with spaces
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. WRITE table-driven tests for all business logic
|
||||||
|
2. MOCK via interfaces (not concrete types)
|
||||||
|
3. TEST error paths explicitly
|
||||||
|
4. USE subtests for related cases
|
||||||
|
5. KEEP tests independent (no shared state between tests)
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. TEST implementation details (test behavior)
|
||||||
|
2. SKIP error case tests
|
||||||
|
3. USE real databases in unit tests
|
||||||
|
4. SHARE mutable state between test cases
|
||||||
|
5. WRITE tests that depend on execution order
|
||||||
104
.claude/agents/worker-specialist.md
Normal file
104
.claude/agents/worker-specialist.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
name: worker-specialist
|
||||||
|
description: Background worker patterns for slack-auth-1770277653 - job queues, tick-based processing, retry logic, graceful shutdown
|
||||||
|
color: orange
|
||||||
|
---
|
||||||
|
|
||||||
|
# Worker Specialist
|
||||||
|
|
||||||
|
You design and implement background workers for slack-auth-1770277653. Workers are reliable, observable, and gracefully handle failure.
|
||||||
|
|
||||||
|
## Worker Types
|
||||||
|
|
||||||
|
### Queue Consumer
|
||||||
|
Processes jobs from a queue (PostgreSQL SKIP LOCKED, Redis, etc.):
|
||||||
|
```go
|
||||||
|
func (w *Worker) Run(ctx context.Context) error {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
job, err := w.queue.Dequeue(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("dequeue failed", "error", err)
|
||||||
|
time.Sleep(w.backoff)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if job == nil {
|
||||||
|
time.Sleep(w.pollInterval)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.process(ctx, job)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tick-Based Worker
|
||||||
|
Runs on interval (cron-like):
|
||||||
|
```go
|
||||||
|
func (w *Worker) Run(ctx context.Context) error {
|
||||||
|
ticker := time.NewTicker(w.interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := w.tick(ctx); err != nil {
|
||||||
|
slog.Error("tick failed", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
|
||||||
|
### Graceful Shutdown
|
||||||
|
- Listen for SIGINT/SIGTERM
|
||||||
|
- Stop accepting new work
|
||||||
|
- Finish in-progress jobs (with timeout)
|
||||||
|
- Close connections cleanly
|
||||||
|
|
||||||
|
### Retry Logic
|
||||||
|
- Exponential backoff with jitter
|
||||||
|
- Max retry count per job
|
||||||
|
- Dead letter queue for permanently failed jobs
|
||||||
|
- Log every retry with attempt count
|
||||||
|
|
||||||
|
### Observability
|
||||||
|
- Log job start/end with duration
|
||||||
|
- Track queue depth metrics
|
||||||
|
- Alert on dead letter queue growth
|
||||||
|
- Include job_id and worker_id in all logs
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
```
|
||||||
|
workers/{name}/
|
||||||
|
├── cmd/worker/main.go # Entry point, signal handling
|
||||||
|
├── internal/
|
||||||
|
│ ├── config/config.go # Worker configuration
|
||||||
|
│ ├── processor/ # Job processing logic
|
||||||
|
│ └── handler/ # Individual job type handlers
|
||||||
|
├── go.mod
|
||||||
|
├── Makefile
|
||||||
|
└── Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. ALWAYS handle context cancellation
|
||||||
|
2. USE structured logging with job context
|
||||||
|
3. IMPLEMENT graceful shutdown
|
||||||
|
4. TEST with both success and failure cases
|
||||||
|
5. MAKE workers idempotent (safe to retry)
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. PANIC on job failure (log and continue)
|
||||||
|
2. PROCESS without timeout (use context.WithTimeout)
|
||||||
|
3. IGNORE poison messages (dead letter after N retries)
|
||||||
|
4. SKIP metrics (queue depth, processing time, error rate)
|
||||||
|
5. SHARE state between job handlers without synchronization
|
||||||
47
.claude/commands/archive-feature.md
Normal file
47
.claude/commands/archive-feature.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
description: Archive a completed and released feature
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Archive feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Verify Feature is Released
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm the feature current phase is `released`. Only released features can be archived.
|
||||||
|
|
||||||
|
### 2. Archive the Feature
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc archive $ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
This moves the feature from active tracking to the archive. The `.sdlc/features/$ARGUMENTS/` directory and its artifacts are preserved in git history.
|
||||||
|
|
||||||
|
### 3. Confirm Completion
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature list --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the feature no longer appears in the active features list.
|
||||||
|
|
||||||
|
### 4. Report
|
||||||
|
|
||||||
|
Confirm:
|
||||||
|
- Feature slug that was archived
|
||||||
|
- Previous phase: `released`
|
||||||
|
- Status: archived and removed from active tracking
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ONLY archive features in the `released` phase
|
||||||
|
- NEVER archive features that are still in progress
|
||||||
|
- This is a cleanup action -- it does not delete git history
|
||||||
|
- ALWAYS verify the feature is released before archiving
|
||||||
55
.claude/commands/audit-debt.md
Normal file
55
.claude/commands/audit-debt.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
description: Audit codebase for systemic tech debt - inconsistent patterns that should be unified
|
||||||
|
argument-hint: <category, e.g., "error-handling", "logging", "api-calls", "auth", or "all">
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Audit for systemic tech debt: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Load the `systemic-debt-auditor` skill, then:
|
||||||
|
|
||||||
|
### If "all" or no category:
|
||||||
|
|
||||||
|
High-level scan:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
| Category | Variations | Worst Issue | Priority |
|
||||||
|
|----------|------------|-------------|----------|
|
||||||
|
| Error Handling | 4 patterns | unwrap in prod | HIGH |
|
||||||
|
| Logging | 3 patterns | println debug | MEDIUM |
|
||||||
|
```
|
||||||
|
|
||||||
|
Then ask which to deep dive.
|
||||||
|
|
||||||
|
### If specific category:
|
||||||
|
|
||||||
|
1. **Survey** - Find all variations with grep
|
||||||
|
2. **Categorize** - Document each pattern
|
||||||
|
3. **Identify canonical** - Best existing pattern
|
||||||
|
4. **Risk assess** - CRITICAL > HIGH > MEDIUM > LOW
|
||||||
|
5. **Propose plan** - Incremental unification
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Error Handling (Go)
|
||||||
|
```bash
|
||||||
|
grep -rn "panic(" --include="*.go" | wc -l
|
||||||
|
grep -rn "log.Fatal" --include="*.go" | wc -l
|
||||||
|
grep -rn "if err != nil" --include="*.go" | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
```bash
|
||||||
|
grep -rn "fmt.Print" --include="*.go" | wc -l
|
||||||
|
grep -rn "slog\.\|log\." --include="*.go" | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Requirements
|
||||||
|
|
||||||
|
1. Patterns found with counts
|
||||||
|
2. Canonical pattern recommendation
|
||||||
|
3. Risk assessment table
|
||||||
|
4. Unification plan (stop bleeding → fix critical → gradual cleanup)
|
||||||
|
5. Enforcement mechanism
|
||||||
119
.claude/commands/audit-feature.md
Normal file
119
.claude/commands/audit-feature.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
description: Perform a security and quality audit of a feature
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Glob, Grep, Write
|
||||||
|
---
|
||||||
|
|
||||||
|
Audit feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load Feature Context
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Read the spec and design to understand the feature security surface:
|
||||||
|
- `.sdlc/features/$ARGUMENTS/spec.md`
|
||||||
|
- `.sdlc/features/$ARGUMENTS/design.md`
|
||||||
|
|
||||||
|
### 2. Run Static Analysis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go vet ./... 2>/dev/null || true
|
||||||
|
golangci-lint run ./... 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
Capture any warnings or errors related to the feature files.
|
||||||
|
|
||||||
|
### 3. OWASP Top 10 Check
|
||||||
|
|
||||||
|
For each applicable category, search the feature code:
|
||||||
|
|
||||||
|
| Category | What to Check |
|
||||||
|
|----------|--------------|
|
||||||
|
| **Injection** | SQL queries, command execution, template rendering |
|
||||||
|
| **Broken Auth** | Token handling, session management, credential storage |
|
||||||
|
| **Sensitive Data** | Secrets in code, logging PII, unencrypted storage |
|
||||||
|
| **XXE / Deserialization** | XML parsing, JSON unmarshaling of untrusted input |
|
||||||
|
| **Broken Access Control** | Authorization checks, resource ownership validation |
|
||||||
|
| **Misconfiguration** | Default credentials, debug modes, permissive CORS |
|
||||||
|
| **XSS** | User input rendered without escaping |
|
||||||
|
| **Insecure Components** | Known vulnerable dependencies |
|
||||||
|
| **Logging Gaps** | Missing audit logs, excessive debug logging |
|
||||||
|
| **SSRF** | User-controlled URLs, internal network access |
|
||||||
|
|
||||||
|
### 4. Verify Auth Boundaries
|
||||||
|
|
||||||
|
- Every endpoint has authentication
|
||||||
|
- Authorization checks match the resource being accessed
|
||||||
|
- No privilege escalation paths
|
||||||
|
|
||||||
|
### 5. Check for Hardcoded Secrets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -rn "password\|secret\|token\|api_key\|apikey" --include="*.go" [feature files]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Write Audit Report
|
||||||
|
|
||||||
|
Write to `.sdlc/features/$ARGUMENTS/audit.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Security Audit: [Feature Title]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
[Overall assessment: PASS / NEEDS_REMEDIATION]
|
||||||
|
|
||||||
|
## Static Analysis Results
|
||||||
|
[Findings from vet/lint]
|
||||||
|
|
||||||
|
## OWASP Assessment
|
||||||
|
| Category | Status | Notes |
|
||||||
|
|----------|--------|-------|
|
||||||
|
| Injection | PASS/FAIL | [details] |
|
||||||
|
| ... | ... | ... |
|
||||||
|
|
||||||
|
## Critical Findings
|
||||||
|
- [Finding with severity and remediation guidance]
|
||||||
|
|
||||||
|
## High Findings
|
||||||
|
- [Finding]
|
||||||
|
|
||||||
|
## Medium/Low Findings
|
||||||
|
- [Finding]
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
[Ordered list of actions to take]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Register and Evaluate the Artifact
|
||||||
|
|
||||||
|
Create the artifact:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc artifact create $ARGUMENTS audit
|
||||||
|
```
|
||||||
|
|
||||||
|
Then evaluate the audit results and set the appropriate status:
|
||||||
|
|
||||||
|
- If the audit has **no critical or high findings**: mark as passed
|
||||||
|
```bash
|
||||||
|
sdlc artifact pass $ARGUMENTS audit
|
||||||
|
```
|
||||||
|
- If the audit has **critical or high findings**: mark as needs-fix
|
||||||
|
```bash
|
||||||
|
sdlc artifact needs-fix $ARGUMENTS audit
|
||||||
|
```
|
||||||
|
|
||||||
|
This status drives the SDLC classifier to either advance to QA or trigger remediate-audit.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- NEVER skip OWASP checks -- even if the feature seems low-risk
|
||||||
|
- ALWAYS check for hardcoded secrets, tokens, and credentials
|
||||||
|
- ALWAYS verify authentication and authorization boundaries
|
||||||
|
- NEVER mark an audit as passed if it has unresolved critical or high findings
|
||||||
|
- ALWAYS run static analysis tools before manual review
|
||||||
|
- ALWAYS set the artifact status (pass or needs-fix) after writing the audit
|
||||||
77
.claude/commands/breakdown-feature.md
Normal file
77
.claude/commands/breakdown-feature.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
description: Break a feature into implementation tasks
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
Break down feature into tasks: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load Feature, Spec, and Design
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Read both:
|
||||||
|
- `.sdlc/features/$ARGUMENTS/spec.md`
|
||||||
|
- `.sdlc/features/$ARGUMENTS/design.md`
|
||||||
|
|
||||||
|
Both must exist before creating a task breakdown.
|
||||||
|
|
||||||
|
### 2. Identify Implementation Units
|
||||||
|
|
||||||
|
From the design, identify discrete units of work. Each task should:
|
||||||
|
- Be completable in a single coding session
|
||||||
|
- Have clear inputs and outputs
|
||||||
|
- Be independently testable
|
||||||
|
- Have minimal overlap with other tasks
|
||||||
|
|
||||||
|
### 3. Create Tasks via CLI
|
||||||
|
|
||||||
|
For each task, register it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc task add $ARGUMENTS "Task title - brief description"
|
||||||
|
```
|
||||||
|
|
||||||
|
Order tasks by dependency -- foundational work first, integration last.
|
||||||
|
|
||||||
|
### 4. Write Detailed Task Descriptions
|
||||||
|
|
||||||
|
Write to `.sdlc/features/$ARGUMENTS/tasks.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Tasks: [Feature Title]
|
||||||
|
|
||||||
|
## Task Order (dependency sequence)
|
||||||
|
|
||||||
|
### T1: [Title]
|
||||||
|
- **Scope:** [What exactly to implement]
|
||||||
|
- **Files:** [Expected files to create/modify]
|
||||||
|
- **Depends on:** [None / T-id]
|
||||||
|
- **Acceptance criteria:**
|
||||||
|
- [ ] [Specific, testable criterion]
|
||||||
|
|
||||||
|
### T2: [Title]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Register the Artifact
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc artifact create $ARGUMENTS tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Report
|
||||||
|
|
||||||
|
List all tasks in order, total count, and estimated dependency chain depth (longest path through the task graph).
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- NEVER create tasks without reading both spec and design
|
||||||
|
- ALWAYS order tasks by dependency -- no task should reference work not yet done
|
||||||
|
- Each task MUST be completable in a single session
|
||||||
|
- ALWAYS include acceptance criteria per task
|
||||||
|
- NEVER create monolithic tasks -- split until each is focused
|
||||||
60
.claude/commands/commit-all.md
Normal file
60
.claude/commands/commit-all.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
description: Check git status, verify .gitignore, stage everything safe, commit and push
|
||||||
|
argument-hint: <commit message>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
Commit and push all changes with message: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### Phase 1: Audit What's Changed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status
|
||||||
|
git diff --stat
|
||||||
|
git diff --cached --stat
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Security Check
|
||||||
|
|
||||||
|
Scan for files that should NEVER be committed:
|
||||||
|
|
||||||
|
- `.env` files (except `.env.example`)
|
||||||
|
- `*.pem`, `*.key`, `*.p12`, `*.pfx`
|
||||||
|
- `credentials.json`, `service-account*.json`
|
||||||
|
- `.envault/` directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --cached --name-only | xargs grep -l -E "(api_key|apikey|secret|password|token)\s*[:=]\s*['\"][^'\"]+['\"]" 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Verify .gitignore
|
||||||
|
|
||||||
|
Check that .gitignore covers secrets, dependencies, build artifacts.
|
||||||
|
|
||||||
|
### Phase 4: Stage and Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git diff --cached --name-only | grep -E "\.(env|pem|key)$" && echo "WARNING: Sensitive files staged!" || true
|
||||||
|
git commit -m "$ARGUMENTS"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: If Commit Fails
|
||||||
|
|
||||||
|
If pre-commit hooks fail:
|
||||||
|
1. Fix the issues
|
||||||
|
2. Re-stage: `git add -A`
|
||||||
|
3. Retry commit (max 3 times)
|
||||||
|
|
||||||
|
### Phase 6: Push
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safety Rules
|
||||||
|
|
||||||
|
**NEVER commit:** `.env` with real values, private keys, credentials, files > 50MB.
|
||||||
|
**ALWAYS verify** .gitignore before staging.
|
||||||
79
.claude/commands/create-qa-plan.md
Normal file
79
.claude/commands/create-qa-plan.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
description: Create a QA test plan for a feature
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
Create a QA plan for feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load All Planning Artifacts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Read all of:
|
||||||
|
- `.sdlc/features/$ARGUMENTS/spec.md` -- acceptance criteria drive tests
|
||||||
|
- `.sdlc/features/$ARGUMENTS/design.md` -- architecture informs integration tests
|
||||||
|
- `.sdlc/features/$ARGUMENTS/tasks.md` -- task scope informs unit tests
|
||||||
|
|
||||||
|
### 2. Derive Test Scenarios from Acceptance Criteria
|
||||||
|
|
||||||
|
For each acceptance criterion in the spec, create at least one test scenario. Group scenarios by type.
|
||||||
|
|
||||||
|
### 3. Write the QA Plan
|
||||||
|
|
||||||
|
Write to `.sdlc/features/$ARGUMENTS/qa-plan.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# QA Plan: [Feature Title]
|
||||||
|
|
||||||
|
## Test Scenarios
|
||||||
|
|
||||||
|
### Happy Path
|
||||||
|
| ID | Scenario | Input | Expected Output | Derived From |
|
||||||
|
|----|----------|-------|-----------------|--------------|
|
||||||
|
| HP-1 | [description] | [input] | [expected] | AC-N |
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
| ID | Scenario | Input | Expected Output | Derived From |
|
||||||
|
|----|----------|-------|-----------------|--------------|
|
||||||
|
| EC-1 | [description] | [input] | [expected] | AC-N |
|
||||||
|
|
||||||
|
### Error Cases
|
||||||
|
| ID | Scenario | Input | Expected Output | Derived From |
|
||||||
|
|----|----------|-------|-----------------|--------------|
|
||||||
|
| ER-1 | [description] | [input] | [expected] | AC-N |
|
||||||
|
|
||||||
|
## Test Data Requirements
|
||||||
|
[What test data must be set up, fixtures, mocks]
|
||||||
|
|
||||||
|
## Integration Test Plan
|
||||||
|
[How components interact, what to test end-to-end]
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
[Load expectations, latency budgets, benchmarks to run]
|
||||||
|
|
||||||
|
## Manual Verification Steps
|
||||||
|
[Anything that cannot be automated]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Register the Artifact
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc artifact create $ARGUMENTS qa_plan
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Report
|
||||||
|
|
||||||
|
Summarize scenario counts by category and flag any acceptance criteria that lack test coverage.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS derive test scenarios from acceptance criteria in the spec
|
||||||
|
- NEVER skip edge cases -- they catch the real bugs
|
||||||
|
- ALWAYS include integration scenarios that cross component boundaries
|
||||||
|
- ALWAYS include error cases for every external dependency
|
||||||
|
- NEVER leave an acceptance criterion without a corresponding test scenario
|
||||||
68
.claude/commands/deliver.md
Normal file
68
.claude/commands/deliver.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
description: Orchestrate full feature delivery from current state to completion
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Task
|
||||||
|
---
|
||||||
|
|
||||||
|
Deliver feature end-to-end: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Assess Current State
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc next --for $ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
Determine where the feature is in the lifecycle and what action is needed.
|
||||||
|
|
||||||
|
### 2. Execute the Action Loop
|
||||||
|
|
||||||
|
For each classifier result, execute the recommended action:
|
||||||
|
|
||||||
|
| Action | What to Do |
|
||||||
|
|--------|-----------|
|
||||||
|
| `CREATE_SPEC` | Write the spec (follow `/spec-feature` protocol) |
|
||||||
|
| `CREATE_DESIGN` | Write the design (follow `/design-feature` protocol) |
|
||||||
|
| `CREATE_TASKS` | Break down tasks (follow `/breakdown-feature` protocol) |
|
||||||
|
| `CREATE_QA_PLAN` | Write QA plan (follow `/create-qa-plan` protocol) |
|
||||||
|
| `TRANSITION` | Run `sdlc feature transition $ARGUMENTS <phase>` |
|
||||||
|
| `IMPLEMENT_TASK` | Implement the task (follow `/implement-task` protocol) |
|
||||||
|
| `CREATE_REVIEW` | Review the code (follow `/review-feature` protocol) |
|
||||||
|
| `CREATE_AUDIT` | Audit the code (follow `/audit-feature` protocol) |
|
||||||
|
| `RUN_QA` | Execute QA (follow `/run-qa` protocol) |
|
||||||
|
| `MERGE` | Merge the feature (follow `/merge-feature` protocol) |
|
||||||
|
|
||||||
|
### 3. Re-classify After Each Step
|
||||||
|
|
||||||
|
After completing each action:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc next --for $ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the new classification to determine the next step. Continue until the feature reaches `released` or a gate is hit.
|
||||||
|
|
||||||
|
### 4. Stop at Gates
|
||||||
|
|
||||||
|
When the classifier returns `AWAIT_APPROVAL` or `BLOCKED`:
|
||||||
|
|
||||||
|
1. Present what needs approval or what is blocked
|
||||||
|
2. List the specific artifact or blocker details
|
||||||
|
3. **Stop and wait for the user** -- do not proceed past approval gates
|
||||||
|
|
||||||
|
### 5. Report Progress
|
||||||
|
|
||||||
|
After each action, briefly report:
|
||||||
|
- What was just completed
|
||||||
|
- Current phase
|
||||||
|
- What comes next (or what is blocking)
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS stop at approval gates -- NEVER approve artifacts yourself
|
||||||
|
- NEVER skip phases or reorder the classifier recommendations
|
||||||
|
- ALWAYS re-run the classifier after each action to get the true next step
|
||||||
|
- ALWAYS follow the corresponding command protocol for each action
|
||||||
|
- NEVER continue past BLOCKED status without resolution
|
||||||
|
- ALWAYS report what was done and what comes next after each step
|
||||||
76
.claude/commands/design-feature.md
Normal file
76
.claude/commands/design-feature.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
description: Create a technical design document for a feature
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
Create a technical design for feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load Feature and Spec
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Read `.sdlc/features/$ARGUMENTS/spec.md`. The spec MUST exist and be approved before designing.
|
||||||
|
|
||||||
|
### 2. Analyze Existing Patterns
|
||||||
|
|
||||||
|
Search the codebase for:
|
||||||
|
- Similar features or modules already implemented
|
||||||
|
- Data models that will be extended or referenced
|
||||||
|
- API patterns currently in use
|
||||||
|
- Error handling conventions
|
||||||
|
- Test patterns for comparable components
|
||||||
|
|
||||||
|
### 3. Write the Design Document
|
||||||
|
|
||||||
|
Write to `.sdlc/features/$ARGUMENTS/design.md` with this structure:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Design: [Feature Title]
|
||||||
|
|
||||||
|
## Architecture Approach
|
||||||
|
[High-level approach: which layers change, what is new vs modified]
|
||||||
|
|
||||||
|
## Data Model Changes
|
||||||
|
[New types, schema changes, migration requirements]
|
||||||
|
|
||||||
|
## API Changes
|
||||||
|
[New endpoints, modified contracts, request/response shapes]
|
||||||
|
|
||||||
|
## Component Diagram
|
||||||
|
[Text diagram showing how components interact]
|
||||||
|
|
||||||
|
## Error Handling Strategy
|
||||||
|
[Expected failure modes and how each is handled]
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
[Auth, input validation, data exposure, access control]
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
[Expected load, caching strategy, query complexity]
|
||||||
|
|
||||||
|
## Migration / Rollout Plan
|
||||||
|
[How to ship without breaking existing functionality]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Register the Artifact
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc artifact create $ARGUMENTS design
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Report
|
||||||
|
|
||||||
|
Summarize the design approach, list key decisions made, and flag any areas that need human review or approval.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS read the spec before designing -- the design must satisfy the spec
|
||||||
|
- NEVER design without understanding existing patterns in the codebase
|
||||||
|
- ALWAYS consider error handling -- every external call can fail
|
||||||
|
- ALWAYS address security -- auth, validation, data boundaries
|
||||||
|
- NEVER invent new patterns when existing ones fit
|
||||||
220
.claude/commands/do-parallel.md
Normal file
220
.claude/commands/do-parallel.md
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
---
|
||||||
|
description: Execute tasks in parallel waves with optimal agent selection and review
|
||||||
|
argument-hint: <task list or "from todo">
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash, TodoWrite
|
||||||
|
---
|
||||||
|
|
||||||
|
Execute these tasks in parallel waves with proper review: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Load the `orchestrated-execution` skill, then:
|
||||||
|
|
||||||
|
### Philosophy: Do It Right
|
||||||
|
|
||||||
|
**Take your time. No shortcuts.** Every implementation should be:
|
||||||
|
|
||||||
|
- **Clean** - Readable, well-named, minimal complexity
|
||||||
|
- **Maintainable** - Future developers can understand and modify it
|
||||||
|
- **Extensible** - Easy to add features without rewriting
|
||||||
|
- **Refactored** - If existing code is messy, clean it up as you go
|
||||||
|
|
||||||
|
When you encounter code that could be better:
|
||||||
|
- Refactor it. Don't work around bad patterns.
|
||||||
|
- Extract helpers, rename unclear variables, simplify nesting
|
||||||
|
- Leave the codebase better than you found it
|
||||||
|
|
||||||
|
**Prefer proper solutions over quick fixes.** A 50-line clean implementation beats a 10-line hack.
|
||||||
|
|
||||||
|
### Phase 1: Parse & Analyze
|
||||||
|
|
||||||
|
1. **Parse tasks** - From todo list or provided
|
||||||
|
2. **Analyze dependencies** - Which tasks depend on which
|
||||||
|
3. **Group into waves** - Tasks without mutual dependencies go in same wave
|
||||||
|
|
||||||
|
### Phase 2: Wave Planning
|
||||||
|
|
||||||
|
For each wave, determine:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Wave [N]
|
||||||
|
|
||||||
|
| Task | Implementer | Why | Reviewer | Why |
|
||||||
|
|------|-------------|-----|----------|-----|
|
||||||
|
| [Name] | [Agent] | [domain match] | [Agent] | [risk match] |
|
||||||
|
|
||||||
|
**Parallelizable because:** [No dependencies between these tasks]
|
||||||
|
**Blocked until:** [Wave N-1 complete] or [Nothing]
|
||||||
|
```
|
||||||
|
|
||||||
|
Present the wave plan to user before executing.
|
||||||
|
|
||||||
|
### Phase 3: Execute Each Wave
|
||||||
|
|
||||||
|
```
|
||||||
|
Wave N:
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ 1. LAUNCH ALL IMPLEMENTERS (parallel) │
|
||||||
|
│ │
|
||||||
|
│ Task(agent1, task1) ──┐ │
|
||||||
|
│ Task(agent2, task2) ──┼── concurrent │
|
||||||
|
│ Task(agent3, task3) ──┘ │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ 2. COLLECT RESULTS │
|
||||||
|
│ Wait for all to complete │
|
||||||
|
│ Gather implementation outputs │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ 3. LAUNCH ALL REVIEWERS (parallel) │
|
||||||
|
│ │
|
||||||
|
│ Task(reviewer1, review1) ──┐ │
|
||||||
|
│ Task(reviewer2, review2) ──┼── concurrent│
|
||||||
|
│ Task(reviewer3, review3) ──┘ │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ 4. PROCESS REVIEW RESULTS │
|
||||||
|
│ │
|
||||||
|
│ PASS → mark complete │
|
||||||
|
│ NEEDS_FIX → fix loop (can parallelize) │
|
||||||
|
│ BLOCK → escalate immediately │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ 5. VERIFY WAVE COMPLETE │
|
||||||
|
│ All tasks in wave done? │
|
||||||
|
│ Any conflicts from parallel execution? │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Continue to Wave N+1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Loose Ends (Critical for Parallel)
|
||||||
|
|
||||||
|
After all waves, explicitly check:
|
||||||
|
|
||||||
|
1. **File conflicts** - Did parallel tasks modify same files?
|
||||||
|
2. **Integration gaps** - Do the pieces work together?
|
||||||
|
3. **Merge issues** - Any conflicting changes to resolve?
|
||||||
|
4. **Cross-cutting** - Consistent patterns across all tasks?
|
||||||
|
5. **Quality gate** - Full build, test, lint
|
||||||
|
|
||||||
|
### Phase 5: Final Report
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Parallel Execution Complete
|
||||||
|
|
||||||
|
### Wave Summary
|
||||||
|
| Wave | Tasks | Parallel Time | Status |
|
||||||
|
|------|-------|---------------|--------|
|
||||||
|
| 1 | 3 | ~2min | ✓ |
|
||||||
|
| 2 | 2 | ~1min | ✓ |
|
||||||
|
| 3 | 1 | ~1min | ✓ |
|
||||||
|
|
||||||
|
### Task Details
|
||||||
|
| Task | Wave | Implementer | Reviewer | Issues Fixed | Status |
|
||||||
|
|------|------|-------------|----------|--------------|--------|
|
||||||
|
|
||||||
|
### Loose Ends Resolved
|
||||||
|
- [Conflicts fixed]
|
||||||
|
- [Integration issues addressed]
|
||||||
|
|
||||||
|
### Quality Gate
|
||||||
|
- Build: PASS
|
||||||
|
- Tests: PASS
|
||||||
|
- Lint: PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependency Detection
|
||||||
|
|
||||||
|
Tasks depend on each other when:
|
||||||
|
|
||||||
|
```
|
||||||
|
Task A: "Create User model"
|
||||||
|
Task B: "Add validation to User model" ← Depends on A
|
||||||
|
|
||||||
|
Task A: "Implement auth backend"
|
||||||
|
Task B: "Write auth tests" ← Depends on A
|
||||||
|
|
||||||
|
Task A: "Update config schema"
|
||||||
|
Task B: "Migrate existing configs" ← Depends on A
|
||||||
|
```
|
||||||
|
|
||||||
|
Tasks are independent when:
|
||||||
|
|
||||||
|
```
|
||||||
|
Task A: "Add logging to ingestion"
|
||||||
|
Task B: "Add metrics to query" ← Different modules, independent
|
||||||
|
|
||||||
|
Task A: "Write User docs"
|
||||||
|
Task B: "Write Config docs" ← Different files, independent
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agent Selection
|
||||||
|
|
||||||
|
### Implementers
|
||||||
|
| Task Type | Agent |
|
||||||
|
|-----------|-------|
|
||||||
|
| Go code, features | `go-specialist` |
|
||||||
|
| Tests | `testing-strategist` |
|
||||||
|
| API design | `api-designer` |
|
||||||
|
| Database | `database-architect` |
|
||||||
|
| K8s, deployment | `worker-specialist` |
|
||||||
|
| Security, auth | `security-architect` |
|
||||||
|
| Architecture | `hexagonal-architect` |
|
||||||
|
| Docs | `librarian` |
|
||||||
|
|
||||||
|
### Reviewers
|
||||||
|
| Risk Type | Reviewer |
|
||||||
|
|-----------|----------|
|
||||||
|
| Code quality | `quality-engineer` |
|
||||||
|
| Security | `security-architect` |
|
||||||
|
| Architecture | `hexagonal-architect` |
|
||||||
|
| Test coverage | `testing-strategist` |
|
||||||
|
|
||||||
|
## Wave Progress Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Wave 2 Progress
|
||||||
|
|
||||||
|
### Implementing (parallel)
|
||||||
|
→ Task 4: auth-backend - go-specialist
|
||||||
|
→ Task 5: rate-limiter - security-architect
|
||||||
|
→ Task 6: metrics - go-specialist
|
||||||
|
|
||||||
|
### Wave 1 Complete
|
||||||
|
✓ Task 1: user-model - go-specialist ✓ quality-engineer
|
||||||
|
✓ Task 2: config-schema - go-specialist ✓ quality-engineer
|
||||||
|
✓ Task 3: docs-update - librarian ✓ librarian
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- NEVER put dependent tasks in same wave
|
||||||
|
- ALWAYS review even in parallel mode
|
||||||
|
- ALWAYS check for conflicts after parallel execution
|
||||||
|
- ALWAYS run quality gate at the end
|
||||||
|
- ANNOUNCE wave plan before executing
|
||||||
|
- MAX 3 fix cycles per task, then escalate
|
||||||
|
|
||||||
|
## Step Back: Before Each Wave
|
||||||
|
|
||||||
|
1. **Dependencies verified?** Wave N-1 complete?
|
||||||
|
2. **No conflicts anticipated?** Parallel tasks won't clash?
|
||||||
|
3. **Right agents selected?** Re-check before launching
|
||||||
|
4. **Rollback plan?** If wave fails, how do we recover?
|
||||||
|
|
||||||
|
## Step Back: After Each Wave
|
||||||
|
|
||||||
|
1. **All tasks actually complete?** Not just "launched"
|
||||||
|
2. **Reviews actually done?** Not skipped for speed
|
||||||
|
3. **Issues actually fixed?** Not deferred
|
||||||
|
4. **No file conflicts?** From parallel writes
|
||||||
64
.claude/commands/fix-all.md
Normal file
64
.claude/commands/fix-all.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
description: Run code review and fix all issues from SUGGESTION to BLOCKER
|
||||||
|
argument-hint: <"recent" | "staged" | "unstaged" | file path | git commit range>
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix all issues in: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Run Code Review
|
||||||
|
|
||||||
|
Perform a full code review using the `code-reviewer` skill.
|
||||||
|
|
||||||
|
### 2. Collect All Issues
|
||||||
|
|
||||||
|
Gather all issues by severity: BLOCKER > CRITICAL > WARNING > SUGGESTION.
|
||||||
|
|
||||||
|
### 3. Fix All Issues
|
||||||
|
|
||||||
|
Apply fixes in priority order. For each issue:
|
||||||
|
1. Read the file at the specified location
|
||||||
|
2. Apply the **proper fix** (not quick patches)
|
||||||
|
3. If refactoring is warranted, do the full refactor
|
||||||
|
4. Track what was fixed
|
||||||
|
|
||||||
|
### 4. Verify Fixes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Go
|
||||||
|
go vet ./... 2>/dev/null || true
|
||||||
|
go test ./... 2>/dev/null || true
|
||||||
|
|
||||||
|
# TypeScript
|
||||||
|
npx tsc --noEmit 2>/dev/null || true
|
||||||
|
npx eslint . 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Report
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Fix-All Report
|
||||||
|
|
||||||
|
### Issues Fixed
|
||||||
|
| Severity | Count | Details |
|
||||||
|
|----------|-------|---------|
|
||||||
|
|
||||||
|
### Files Modified
|
||||||
|
- `file` - [what was fixed]
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- Lint: PASS/FAIL
|
||||||
|
- Tests: PASS/FAIL
|
||||||
|
|
||||||
|
### Remaining Issues
|
||||||
|
[Any issues that could not be auto-fixed]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- FIX ALL severities (BLOCKER through SUGGESTION)
|
||||||
|
- Use PROPER FIXES only (no quick patches)
|
||||||
|
- REFACTOR when structural problems exist
|
||||||
|
- VERIFY after fixing (lint, test)
|
||||||
82
.claude/commands/fix-qa-failures.md
Normal file
82
.claude/commands/fix-qa-failures.md
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
description: Fix QA test failures
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Task
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix QA failures for feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load QA Results
|
||||||
|
|
||||||
|
Read `.sdlc/features/$ARGUMENTS/qa-results.md` to get the full test results.
|
||||||
|
|
||||||
|
### 2. Parse Failed Scenarios
|
||||||
|
|
||||||
|
Collect all scenarios with status FAIL. For each failure, note:
|
||||||
|
- Scenario ID and description
|
||||||
|
- Expected vs actual behavior
|
||||||
|
- Error output or evidence
|
||||||
|
|
||||||
|
### 3. Diagnose Each Failure
|
||||||
|
|
||||||
|
For each failed scenario:
|
||||||
|
1. Read the test code or manual steps that failed
|
||||||
|
2. Read the production code being tested
|
||||||
|
3. Determine if the issue is in the implementation, the test, or the test data
|
||||||
|
|
||||||
|
### 4. Fix Implementation Issues
|
||||||
|
|
||||||
|
If the failure is due to incorrect implementation:
|
||||||
|
1. Fix the production code
|
||||||
|
2. Run the specific failing test to confirm the fix
|
||||||
|
3. Run the full test suite to ensure no regressions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./... 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Fix Test Issues
|
||||||
|
|
||||||
|
If the failure is due to an incorrect test expectation:
|
||||||
|
1. Verify the test expectation against the spec
|
||||||
|
2. If the spec supports the test, fix the implementation (not the test)
|
||||||
|
3. If the test expectation was wrong, fix the test and document why
|
||||||
|
|
||||||
|
### 6. Re-run All Failed Scenarios
|
||||||
|
|
||||||
|
After all fixes, re-execute every previously failed scenario and confirm PASS.
|
||||||
|
|
||||||
|
### 7. Update QA Results
|
||||||
|
|
||||||
|
Update `.sdlc/features/$ARGUMENTS/qa-results.md`:
|
||||||
|
- Change FAIL to PASS for fixed scenarios
|
||||||
|
- Add a remediation note explaining what was fixed
|
||||||
|
- Verify the overall status (PASS only if all scenarios pass)
|
||||||
|
|
||||||
|
### 8. Update Artifact Status
|
||||||
|
|
||||||
|
After all fixes are applied and tests re-run:
|
||||||
|
|
||||||
|
- If **all scenarios now pass**: mark as passed
|
||||||
|
```bash
|
||||||
|
sdlc artifact pass $ARGUMENTS qa_results
|
||||||
|
```
|
||||||
|
- If **failures remain**: keep as failed
|
||||||
|
```bash
|
||||||
|
sdlc artifact fail $ARGUMENTS qa_results
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Report
|
||||||
|
|
||||||
|
Summarize: failures fixed, root causes, regression status (all previously passing tests still pass), and artifact status.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS re-run failed tests after fixing -- verify with evidence
|
||||||
|
- NEVER mark a test as passing without actually running it
|
||||||
|
- NEVER fix tests to match broken behavior -- fix the implementation
|
||||||
|
- ALWAYS keep passing tests passing -- no regressions
|
||||||
|
- ALWAYS update the QA results document with resolution details
|
||||||
|
- ALWAYS set the artifact status after fixing (pass if all scenarios now pass)
|
||||||
59
.claude/commands/fix-quality.md
Normal file
59
.claude/commands/fix-quality.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
description: Fix failing quality gate checks - lint, test, build, format
|
||||||
|
argument-hint: <optional: specific check to fix, e.g., "lint" or "test">
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix quality gate failures: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Run All Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Go
|
||||||
|
gofmt -l .
|
||||||
|
go vet ./...
|
||||||
|
golangci-lint run ./... 2>/dev/null || true
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# Node (if applicable)
|
||||||
|
pnpm lint 2>/dev/null || npm run lint 2>/dev/null || true
|
||||||
|
pnpm typecheck 2>/dev/null || npm run typecheck 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Fix Each Category
|
||||||
|
|
||||||
|
**Order:** Format > Vet > Lint > Test > Build
|
||||||
|
|
||||||
|
| Category | Auto-fix | Manual fix |
|
||||||
|
|----------|----------|------------|
|
||||||
|
| Format | `gofmt -w`, `prettier --write` | N/A |
|
||||||
|
| Vet | N/A | Read error, fix code |
|
||||||
|
| Lint | `golangci-lint run --fix` | Read warning, fix code |
|
||||||
|
| Test | N/A | Read failure, fix code |
|
||||||
|
| Build | N/A | Read error, fix code |
|
||||||
|
|
||||||
|
### 3. Re-run Checks
|
||||||
|
|
||||||
|
After all fixes, re-run the full suite to confirm everything passes.
|
||||||
|
|
||||||
|
### 4. Report
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Quality Gate Report
|
||||||
|
|
||||||
|
| Check | Before | After |
|
||||||
|
|-------|--------|-------|
|
||||||
|
| gofmt | X issues | PASS |
|
||||||
|
| go vet | X issues | PASS |
|
||||||
|
| golangci-lint | X issues | PASS |
|
||||||
|
| go test | X failures | PASS |
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Fix ALL issues, not just the first one
|
||||||
|
- Auto-fix where possible (gofmt, prettier)
|
||||||
|
- Re-run checks after fixing to confirm
|
||||||
|
- If a fix breaks something else, fix that too
|
||||||
82
.claude/commands/fix-review-issues.md
Normal file
82
.claude/commands/fix-review-issues.md
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
description: Fix issues found during code review
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Task
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix review issues for feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load Review Findings
|
||||||
|
|
||||||
|
Read `.sdlc/features/$ARGUMENTS/review.md` to get the full list of findings.
|
||||||
|
|
||||||
|
### 2. Parse Findings by Severity
|
||||||
|
|
||||||
|
Collect all findings into severity buckets:
|
||||||
|
1. **BLOCKER** -- must fix, cannot ship without these
|
||||||
|
2. **WARNING** -- should fix, quality concerns
|
||||||
|
3. **SUGGESTION** -- nice to have improvements
|
||||||
|
|
||||||
|
### 3. Fix Blockers First
|
||||||
|
|
||||||
|
For each blocker:
|
||||||
|
1. Read the file at the specified location
|
||||||
|
2. Understand the issue and why it matters
|
||||||
|
3. Apply the proper fix (not a quick patch)
|
||||||
|
4. Run tests to verify the fix does not break anything:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./... 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Fix Warnings
|
||||||
|
|
||||||
|
After all blockers are resolved, fix warnings using the same process.
|
||||||
|
|
||||||
|
### 5. Address Suggestions
|
||||||
|
|
||||||
|
Apply suggestions that improve clarity or maintainability without significant risk.
|
||||||
|
|
||||||
|
### 6. Update Review Report
|
||||||
|
|
||||||
|
Update `.sdlc/features/$ARGUMENTS/review.md` with resolution notes for each finding:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [x] [FILE:LINE] [Description] -- **RESOLVED:** [what was done]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Run Full Test Suite
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./... 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests must pass after all fixes are applied.
|
||||||
|
|
||||||
|
### 8. Update Artifact Status
|
||||||
|
|
||||||
|
After all fixes are applied and tests pass, re-evaluate the review:
|
||||||
|
|
||||||
|
- If **all blockers are resolved** and tests pass: mark as passed
|
||||||
|
```bash
|
||||||
|
sdlc artifact pass $ARGUMENTS review
|
||||||
|
```
|
||||||
|
- If **blockers remain unresolved**: keep as needs-fix
|
||||||
|
```bash
|
||||||
|
sdlc artifact needs-fix $ARGUMENTS review
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Report
|
||||||
|
|
||||||
|
Summarize: findings fixed by severity, files modified, test results, and artifact status.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS fix all blockers -- they are non-negotiable
|
||||||
|
- ALWAYS run tests after each fix, not just at the end
|
||||||
|
- NEVER close a finding without actually fixing it
|
||||||
|
- NEVER introduce new issues while fixing existing ones
|
||||||
|
- ALWAYS update the review report with resolution notes
|
||||||
|
- ALWAYS set the artifact status after fixing (pass if all blockers resolved)
|
||||||
100
.claude/commands/implement-feature.md
Normal file
100
.claude/commands/implement-feature.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
description: Implement all tasks for a feature autonomously
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Task
|
||||||
|
---
|
||||||
|
|
||||||
|
Implement all tasks for feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load Feature Context
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Parse the output to understand current phase, existing artifacts, and task status.
|
||||||
|
|
||||||
|
### 2. Verify Prerequisites
|
||||||
|
|
||||||
|
Check that these artifacts exist and are approved:
|
||||||
|
- `.sdlc/features/$ARGUMENTS/spec.md`
|
||||||
|
- `.sdlc/features/$ARGUMENTS/design.md`
|
||||||
|
- `.sdlc/features/$ARGUMENTS/tasks.md`
|
||||||
|
|
||||||
|
If any are missing or not approved, stop and report what needs to be completed first.
|
||||||
|
|
||||||
|
### 3. Transition to Implementation (if needed)
|
||||||
|
|
||||||
|
If in `ready` phase:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature transition $ARGUMENTS implementation
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Get Task List
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc task list $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Identify all pending tasks and their dependency order. Tasks with no `blocked_by` can be worked first.
|
||||||
|
|
||||||
|
### 5. Implement Tasks in Order
|
||||||
|
|
||||||
|
For each pending task (respecting dependencies):
|
||||||
|
|
||||||
|
1. **Start the task:**
|
||||||
|
```bash
|
||||||
|
sdlc task start $ARGUMENTS <task-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Read task scope** from tasks.md -- find the specific task section
|
||||||
|
|
||||||
|
3. **Study existing patterns** in files to be modified
|
||||||
|
|
||||||
|
4. **Implement changes:**
|
||||||
|
- Production code first
|
||||||
|
- Tests alongside or immediately after
|
||||||
|
- Follow existing conventions
|
||||||
|
|
||||||
|
5. **Run tests:**
|
||||||
|
```bash
|
||||||
|
go test ./... -v 2>&1 | tee /tmp/task-test-output.txt
|
||||||
|
# or the appropriate test command for the project stack
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Complete (only if tests pass):**
|
||||||
|
```bash
|
||||||
|
sdlc task complete $ARGUMENTS <task-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Continue** to next pending task
|
||||||
|
|
||||||
|
### 6. Final Verification
|
||||||
|
|
||||||
|
After all tasks complete:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./... -v
|
||||||
|
go vet ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Report Summary
|
||||||
|
|
||||||
|
- Tasks completed (count)
|
||||||
|
- Files changed
|
||||||
|
- Tests added/modified
|
||||||
|
- Final test status
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS verify prerequisites before implementing
|
||||||
|
- ALWAYS implement tasks in dependency order
|
||||||
|
- NEVER mark a task complete if tests fail
|
||||||
|
- NEVER continue to next task if current task fails
|
||||||
|
- ALWAYS run final verification after all tasks
|
||||||
|
- ALWAYS follow existing codebase patterns
|
||||||
|
- NEVER implement beyond task stated scope
|
||||||
|
- ALWAYS read spec and design for context before coding
|
||||||
69
.claude/commands/implement-task.md
Normal file
69
.claude/commands/implement-task.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
description: Implement a specific task from the feature breakdown
|
||||||
|
argument-hint: <feature-slug> <task-id>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Task
|
||||||
|
---
|
||||||
|
|
||||||
|
Implement task: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Parse Arguments
|
||||||
|
|
||||||
|
Extract the feature slug and task ID from `$ARGUMENTS`. The first token is the slug, the second is the task ID.
|
||||||
|
|
||||||
|
### 2. Start the Task
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc task start <slug> <task-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
This marks the task as in-progress and prevents other tasks from being picked up concurrently.
|
||||||
|
|
||||||
|
### 3. Load Context
|
||||||
|
|
||||||
|
Read all relevant artifacts:
|
||||||
|
- `.sdlc/features/<slug>/spec.md` -- requirements
|
||||||
|
- `.sdlc/features/<slug>/design.md` -- architecture decisions
|
||||||
|
- `.sdlc/features/<slug>/tasks.md` -- find this task scope and acceptance criteria
|
||||||
|
|
||||||
|
### 4. Study Existing Patterns
|
||||||
|
|
||||||
|
Before writing code, read the files identified in the task description. Understand the patterns, naming conventions, and test approaches already in use.
|
||||||
|
|
||||||
|
### 5. Implement
|
||||||
|
|
||||||
|
Write the code changes specified by the task. Follow existing patterns. For each file:
|
||||||
|
- Production code first
|
||||||
|
- Tests alongside or immediately after
|
||||||
|
- Update any relevant documentation or configuration
|
||||||
|
|
||||||
|
### 6. Run Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./... -v 2>&1 | tee /tmp/task-test-output.txt
|
||||||
|
# or the appropriate test command for the project stack
|
||||||
|
```
|
||||||
|
|
||||||
|
All existing tests must continue to pass. New tests must pass. Check the output and only proceed if all tests pass.
|
||||||
|
|
||||||
|
### 7. Complete the Task
|
||||||
|
|
||||||
|
Only if tests pass:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc task complete <slug> <task-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Report
|
||||||
|
|
||||||
|
Summarize: files changed, tests added, task acceptance criteria status.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS start the task via CLI before implementing
|
||||||
|
- ALWAYS run tests before marking complete
|
||||||
|
- NEVER mark a task complete if tests fail
|
||||||
|
- ALWAYS follow existing codebase patterns
|
||||||
|
- NEVER implement beyond the task stated scope
|
||||||
|
- ALWAYS read spec and design for context before coding
|
||||||
60
.claude/commands/investigate.md
Normal file
60
.claude/commands/investigate.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
description: Investigate how a pattern is implemented, analyze its effectiveness, and propose improvements
|
||||||
|
argument-hint: <pattern to investigate, e.g., "error handling", "authentication", "logging">
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Investigate this pattern: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Load the `pattern-investigator` skill, then:
|
||||||
|
|
||||||
|
### 1. Define the Pattern
|
||||||
|
|
||||||
|
State explicitly what you're investigating and why.
|
||||||
|
|
||||||
|
### 2. Find All Instances
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search across the monorepo
|
||||||
|
grep -rn "[pattern]" --include="*.go" services/ workers/ pkg/
|
||||||
|
grep -rn "[pattern]" --include="*.ts" --include="*.tsx" apps/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Categorize Variations
|
||||||
|
|
||||||
|
Group by approach. For each:
|
||||||
|
- Where it's used (files, components)
|
||||||
|
- Pros and cons
|
||||||
|
- Consistency with rest of codebase
|
||||||
|
|
||||||
|
### 4. Analyze Effectiveness
|
||||||
|
|
||||||
|
- Does the dominant pattern work well?
|
||||||
|
- Where does it break down?
|
||||||
|
- What edge cases aren't handled?
|
||||||
|
|
||||||
|
### 5. Propose Improvements
|
||||||
|
|
||||||
|
Provide 2-4 concrete options:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Option A: [Name]
|
||||||
|
- What: [description]
|
||||||
|
- Files affected: [count]
|
||||||
|
- Risk: LOW/MEDIUM/HIGH
|
||||||
|
- Effort: LOW/MEDIUM/HIGH
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Step Back
|
||||||
|
|
||||||
|
- Is the current pattern actually fine?
|
||||||
|
- Will "improving" it create more churn than value?
|
||||||
|
- Is there a reason the variations exist?
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS search before opining
|
||||||
|
- ALWAYS provide concrete options with tradeoffs
|
||||||
|
- NEVER recommend changes without understanding why the current pattern exists
|
||||||
65
.claude/commands/merge-feature.md
Normal file
65
.claude/commands/merge-feature.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
description: Merge a completed feature branch
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Merge feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Check Merge Readiness
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify that:
|
||||||
|
- All required artifacts are approved (review, audit, qa_results)
|
||||||
|
- No blockers exist
|
||||||
|
- The feature is in the `merge` phase
|
||||||
|
|
||||||
|
### 2. Check Branch Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc query ready
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm the feature appears in the ready-to-merge list.
|
||||||
|
|
||||||
|
### 3. Run Final Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./... -v 2>&1 | tee /tmp/merge-test-output.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests must pass before merge.
|
||||||
|
|
||||||
|
### 4. Execute the Merge
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc merge $ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
This command atomically:
|
||||||
|
- Checks out the main branch
|
||||||
|
- Merges (or squashes) the feature branch
|
||||||
|
- Updates the branch manifest
|
||||||
|
- Transitions the feature to the `released` phase
|
||||||
|
|
||||||
|
### 5. Report
|
||||||
|
|
||||||
|
Confirm the merge completed successfully:
|
||||||
|
- Feature name and slug
|
||||||
|
- Final phase: `released`
|
||||||
|
- All gates that were passed
|
||||||
|
|
||||||
|
If the merge fails, report the error and do not retry without user guidance.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- NEVER merge without all required gates passed
|
||||||
|
- ALWAYS run tests immediately before merging
|
||||||
|
- ALWAYS report merge conflicts or failures to the user
|
||||||
|
- NEVER force a merge past failing checks
|
||||||
|
- NEVER merge if the classifier says the feature is not ready
|
||||||
58
.claude/commands/next.md
Normal file
58
.claude/commands/next.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
description: Run the SDLC classifier and show the next required action
|
||||||
|
argument-hint: [feature-slug]
|
||||||
|
allowed-tools: Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Show next SDLC action: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Run the Classifier
|
||||||
|
|
||||||
|
If a feature slug was provided:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc next --for $ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
If no argument was provided, run the global classifier:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc next
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Display the Result
|
||||||
|
|
||||||
|
Present the classification output clearly:
|
||||||
|
|
||||||
|
- **Feature:** which feature needs attention
|
||||||
|
- **Current Phase:** where it is in the lifecycle
|
||||||
|
- **Action:** what needs to happen next
|
||||||
|
- **Message:** human-readable guidance
|
||||||
|
- **Suggested Command:** the CLI or slash command to run
|
||||||
|
|
||||||
|
### 3. Suggest the Next Step
|
||||||
|
|
||||||
|
Map the action to the corresponding slash command:
|
||||||
|
|
||||||
|
| Action | Suggested Command |
|
||||||
|
|--------|-------------------|
|
||||||
|
| `CREATE_SPEC` | `/spec-feature <slug>` |
|
||||||
|
| `CREATE_DESIGN` | `/design-feature <slug>` |
|
||||||
|
| `CREATE_TASKS` | `/breakdown-feature <slug>` |
|
||||||
|
| `CREATE_QA_PLAN` | `/create-qa-plan <slug>` |
|
||||||
|
| `IMPLEMENT_TASK` | `/implement-task <slug> <task-id>` |
|
||||||
|
| `CREATE_REVIEW` | `/review-feature <slug>` |
|
||||||
|
| `CREATE_AUDIT` | `/audit-feature <slug>` |
|
||||||
|
| `RUN_QA` | `/run-qa <slug>` |
|
||||||
|
| `MERGE` | `/merge-feature <slug>` |
|
||||||
|
| `AWAIT_APPROVAL` | Needs human approval via rdev API |
|
||||||
|
| `BLOCKED` | Feature has blockers -- resolve via rdev API |
|
||||||
|
| `IDLE` | Nothing to do |
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS present the classifier output clearly and completely
|
||||||
|
- NEVER automatically execute the next command without user confirmation
|
||||||
|
- ALWAYS show the suggested slash command so the user can invoke it
|
||||||
50
.claude/commands/prepare.md
Normal file
50
.claude/commands/prepare.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
description: Systematic pre-implementation readiness assessment
|
||||||
|
argument-hint: <feature or task to prepare for>
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Prepare to implement: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Load the `prepare` skill, then follow its protocol to produce either:
|
||||||
|
|
||||||
|
1. **High confidence (>= 80%)**: An implementation brief with clear steps
|
||||||
|
2. **Gaps remain (< 80%)**: A prioritized gap list with resolution actions
|
||||||
|
|
||||||
|
### What This Command Does
|
||||||
|
|
||||||
|
1. **Scope the target** - Parse what's being prepared for
|
||||||
|
2. **Map dependencies** - Upstream, downstream, adjacent patterns
|
||||||
|
3. **Audit prerequisites** - What must be fixed first vs. alongside
|
||||||
|
4. **Inventory design decisions** - Questions that need answers
|
||||||
|
5. **Score confidence** - Per-dimension evidence-based assessment
|
||||||
|
6. **Produce output** - Brief or gap list based on readiness
|
||||||
|
|
||||||
|
### When to Use
|
||||||
|
|
||||||
|
Use `/prepare` when:
|
||||||
|
- About to implement a feature and need confidence the path is clear
|
||||||
|
- A task has been discussed/designed but not yet validated against the codebase
|
||||||
|
- Transitioning from planning to implementation and want to catch gaps early
|
||||||
|
- Picking up work someone else planned and need to verify assumptions
|
||||||
|
|
||||||
|
### When NOT to Use
|
||||||
|
|
||||||
|
- Exploring what to build (use `/thinkthrough`)
|
||||||
|
- Debugging an issue (use `/root-cause`)
|
||||||
|
- Investigating how something works (use `/investigate` or `/trace-feature`)
|
||||||
|
- The task is trivial and needs no preparation
|
||||||
|
|
||||||
|
### Expected Output
|
||||||
|
|
||||||
|
Either an **Implementation Brief** (ready to implement) or a **Gap List** (not ready, here's what's missing).
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- NEVER claim ready without reading dependency source code
|
||||||
|
- NEVER skip prerequisite audit
|
||||||
|
- NEVER produce brief with unresolved design decisions
|
||||||
|
- ALWAYS find pattern templates before assessing readiness
|
||||||
|
- ALWAYS let minimum dimension score determine overall confidence
|
||||||
84
.claude/commands/remediate-audit.md
Normal file
84
.claude/commands/remediate-audit.md
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
description: Remediate security audit findings
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Task
|
||||||
|
---
|
||||||
|
|
||||||
|
Remediate audit findings for feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load Audit Findings
|
||||||
|
|
||||||
|
Read `.sdlc/features/$ARGUMENTS/audit.md` to get the full security audit report.
|
||||||
|
|
||||||
|
### 2. Parse Findings by Severity
|
||||||
|
|
||||||
|
Collect all security findings:
|
||||||
|
1. **CRITICAL** -- immediate risk, must fix before any progress
|
||||||
|
2. **HIGH** -- significant risk, must fix before merge
|
||||||
|
3. **MEDIUM** -- moderate risk, should fix
|
||||||
|
4. **LOW** -- minor risk, fix if straightforward
|
||||||
|
|
||||||
|
### 3. Fix Critical Findings
|
||||||
|
|
||||||
|
For each critical finding:
|
||||||
|
1. Read the affected code
|
||||||
|
2. Understand the vulnerability and attack vector
|
||||||
|
3. Apply the proper remediation (input validation, auth check, etc.)
|
||||||
|
4. Verify the fix addresses the root cause, not just the symptom
|
||||||
|
|
||||||
|
### 4. Fix High Findings
|
||||||
|
|
||||||
|
After all critical findings are resolved, address high severity issues using the same disciplined approach.
|
||||||
|
|
||||||
|
### 5. Fix Medium and Low Findings
|
||||||
|
|
||||||
|
Address remaining findings in priority order.
|
||||||
|
|
||||||
|
### 6. Run Security Checks
|
||||||
|
|
||||||
|
Re-run the checks that originally found the issues:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go vet ./... 2>/dev/null || true
|
||||||
|
grep -rn "password\|secret\|token\|api_key" --include="*.go" [feature files] || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Update Audit Report
|
||||||
|
|
||||||
|
Update `.sdlc/features/$ARGUMENTS/audit.md` with remediation notes:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Remediation Log
|
||||||
|
| Finding | Severity | Status | Resolution |
|
||||||
|
|---------|----------|--------|------------|
|
||||||
|
| [description] | CRITICAL | REMEDIATED | [what was done] |
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Update Artifact Status
|
||||||
|
|
||||||
|
After all remediations are applied and security checks pass, re-evaluate the audit:
|
||||||
|
|
||||||
|
- If **all critical and high findings are remediated**: mark as passed
|
||||||
|
```bash
|
||||||
|
sdlc artifact pass $ARGUMENTS audit
|
||||||
|
```
|
||||||
|
- If **critical or high findings remain**: keep as needs-fix
|
||||||
|
```bash
|
||||||
|
sdlc artifact needs-fix $ARGUMENTS audit
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Report
|
||||||
|
|
||||||
|
Summarize: findings remediated by severity, remaining items, verification results, and artifact status.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS fix all critical findings -- no exceptions
|
||||||
|
- NEVER leave high-severity security issues unresolved
|
||||||
|
- ALWAYS run security checks after applying fixes
|
||||||
|
- NEVER fix security issues with workarounds -- address root causes
|
||||||
|
- ALWAYS update the audit report with remediation details
|
||||||
|
- NEVER remove security findings from the report -- mark them as remediated
|
||||||
|
- ALWAYS set the artifact status after remediation (pass if all critical/high resolved)
|
||||||
308
.claude/commands/remember.md
Normal file
308
.claude/commands/remember.md
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
---
|
||||||
|
description: Store learnings as discoverable institutional memory, organized by the librarian
|
||||||
|
argument-hint: <what to remember, or "from discussion" to extract from conversation>
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Remember this knowledge: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Load the `knowledge-librarian` skill, then:
|
||||||
|
|
||||||
|
### 1. Extract the Learning
|
||||||
|
|
||||||
|
If "from discussion":
|
||||||
|
- Review the conversation
|
||||||
|
- Identify key insights, decisions, gotchas, patterns learned
|
||||||
|
- May extract multiple memories
|
||||||
|
|
||||||
|
If specific topic:
|
||||||
|
- Clarify what exactly to remember
|
||||||
|
- Identify the core insight
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**What:** [The knowledge]
|
||||||
|
**Why it matters:** [When someone would need this]
|
||||||
|
**Confidence:** [high/medium/low]
|
||||||
|
**Source:** [conversation/investigation/incident]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Consult Librarian Agent
|
||||||
|
|
||||||
|
If project has `.claude/agents/librarian.md`:
|
||||||
|
```
|
||||||
|
Launch librarian agent to:
|
||||||
|
1. Categorize this knowledge
|
||||||
|
2. Check for existing related entries
|
||||||
|
3. Suggest where to store it
|
||||||
|
4. Identify connections to existing knowledge
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise, self-categorize:
|
||||||
|
|
||||||
|
| If it's... | Category |
|
||||||
|
|------------|----------|
|
||||||
|
| "How we do X" | `patterns/` |
|
||||||
|
| "Why we chose X" | `decisions/` |
|
||||||
|
| "Watch out for X" | `gotchas/` |
|
||||||
|
| "Steps to do X" | `how-to/` |
|
||||||
|
| "How X works" | `architecture/` |
|
||||||
|
| "To debug X" | `debugging/` |
|
||||||
|
| "We name X like Y" | `conventions/` |
|
||||||
|
| "External X works like Y" | `integrations/` |
|
||||||
|
|
||||||
|
### 3. Check for Duplicates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search existing knowledge
|
||||||
|
grep -ri "[key terms]" ai-lookup/ 2>/dev/null
|
||||||
|
|
||||||
|
# List category
|
||||||
|
ls ai-lookup/[category]/ 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
If exists: UPDATE don't duplicate.
|
||||||
|
|
||||||
|
### 4. Compress the Knowledge
|
||||||
|
|
||||||
|
Transform verbose conversation into compressed entry:
|
||||||
|
|
||||||
|
**From:** Long explanation with context, discovery process, tangents...
|
||||||
|
**To:** Core fact + one example + links
|
||||||
|
|
||||||
|
Keep:
|
||||||
|
- The essential insight
|
||||||
|
- One clear example
|
||||||
|
- Related links
|
||||||
|
|
||||||
|
Remove:
|
||||||
|
- Discovery narrative
|
||||||
|
- Hedging language
|
||||||
|
- Redundant examples
|
||||||
|
- Temporary context
|
||||||
|
|
||||||
|
### 5. Write the Entry
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ai-lookup/[category]
|
||||||
|
```
|
||||||
|
|
||||||
|
Format:
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
category: [category]
|
||||||
|
title: [Searchable title]
|
||||||
|
learned: [YYYY-MM-DD]
|
||||||
|
source: [conversation|investigation|incident]
|
||||||
|
confidence: [high|medium|low]
|
||||||
|
related: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Title]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
[2-3 sentence TL;DR]
|
||||||
|
|
||||||
|
## Details
|
||||||
|
[Compressed knowledge]
|
||||||
|
|
||||||
|
## Example
|
||||||
|
[Concrete example]
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
- [Related](path/to/related.md)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Update Indexes
|
||||||
|
|
||||||
|
**Category index** (`ai-lookup/[category]/index.md`):
|
||||||
|
```diff
|
||||||
|
+ - [Title](filename.md) - Brief description
|
||||||
|
```
|
||||||
|
|
||||||
|
**Master index** (`ai-lookup/index.md`):
|
||||||
|
```diff
|
||||||
|
+ - [Title](category/filename.md) - Brief description
|
||||||
|
```
|
||||||
|
|
||||||
|
Create indexes if they don't exist.
|
||||||
|
|
||||||
|
### 7. Verify Discoverability
|
||||||
|
|
||||||
|
- [ ] Can find by browsing index
|
||||||
|
- [ ] Can find by grep for key terms
|
||||||
|
- [ ] Links to related entries work
|
||||||
|
|
||||||
|
## Entry Templates
|
||||||
|
|
||||||
|
### Pattern
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
category: patterns
|
||||||
|
title: [Pattern Name]
|
||||||
|
learned: YYYY-MM-DD
|
||||||
|
confidence: high
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Pattern Name]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
[When to use this pattern and why]
|
||||||
|
|
||||||
|
## Pattern
|
||||||
|
[The pattern itself]
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```code
|
||||||
|
[Concrete example]
|
||||||
|
```
|
||||||
|
|
||||||
|
## When NOT to Use
|
||||||
|
[Exceptions or anti-patterns]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gotcha
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
category: gotchas
|
||||||
|
title: [Short description of trap]
|
||||||
|
learned: YYYY-MM-DD
|
||||||
|
confidence: high
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Gotcha Title]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
[One sentence: what bites you]
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
[What goes wrong]
|
||||||
|
|
||||||
|
## The Solution
|
||||||
|
[How to avoid/fix]
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```code
|
||||||
|
// BAD
|
||||||
|
[code that breaks]
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
[code that works]
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
### Decision
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
category: decisions
|
||||||
|
title: [Decision: X over Y]
|
||||||
|
learned: YYYY-MM-DD
|
||||||
|
confidence: high
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Decision Title]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
We chose [X] over [Y] because [reason].
|
||||||
|
|
||||||
|
## Context
|
||||||
|
[What prompted this decision]
|
||||||
|
|
||||||
|
## Options Considered
|
||||||
|
1. **[X]** - [pros/cons]
|
||||||
|
2. **[Y]** - [pros/cons]
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
[What we chose and why]
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
[What this means going forward]
|
||||||
|
```
|
||||||
|
|
||||||
|
### How-To
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
category: how-to
|
||||||
|
title: How to [do X]
|
||||||
|
learned: YYYY-MM-DD
|
||||||
|
confidence: high
|
||||||
|
---
|
||||||
|
|
||||||
|
# How to [Do X]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
[When you'd need this]
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- [What you need first]
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
1. [Step 1]
|
||||||
|
2. [Step 2]
|
||||||
|
3. [Step 3]
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
[How to know it worked]
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
- **[Problem]**: [Solution]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Remembered: [Title]
|
||||||
|
|
||||||
|
**Category:** [category]
|
||||||
|
**Confidence:** [level]
|
||||||
|
**Stored:** `ai-lookup/[category]/[filename].md`
|
||||||
|
|
||||||
|
### Entry Created
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[Preview of created entry]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Indexes Updated
|
||||||
|
- ✓ `ai-lookup/index.md`
|
||||||
|
- ✓ `ai-lookup/[category]/index.md`
|
||||||
|
|
||||||
|
### Related Knowledge
|
||||||
|
- [Existing related entries found]
|
||||||
|
|
||||||
|
### Discoverability Check
|
||||||
|
- [x] Indexed
|
||||||
|
- [x] Searchable by: [key terms]
|
||||||
|
- [x] Linked to related
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Examples
|
||||||
|
|
||||||
|
**Remember a gotcha:**
|
||||||
|
```
|
||||||
|
/remember "context.WithTimeout requires defer cancel() or resources leak"
|
||||||
|
→ ai-lookup/gotchas/context-cancel-required.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Remember a decision:**
|
||||||
|
```
|
||||||
|
/remember "We chose slog over logrus because structured logging with stdlib"
|
||||||
|
→ ai-lookup/decisions/slog-over-logrus.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Remember from discussion:**
|
||||||
|
```
|
||||||
|
/remember from discussion
|
||||||
|
→ Extracts key learnings from conversation
|
||||||
|
→ Creates appropriate entries
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- COMPRESS: Essence not transcript
|
||||||
|
- CATEGORIZE: Enables discovery
|
||||||
|
- CHECK: Don't duplicate
|
||||||
|
- INDEX: Update both indexes
|
||||||
|
- VERIFY: Ensure findable
|
||||||
|
- PROVENANCE: Track when/how learned
|
||||||
79
.claude/commands/review-code.md
Normal file
79
.claude/commands/review-code.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
description: Review recent code changes for completeness, accuracy, tech debt, maintainability, extensibility, and DRY/CLEAN code
|
||||||
|
argument-hint: <"recent" | "staged" | "unstaged" | file path | git commit range>
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Review this code: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Load the `code-reviewer` skill, then:
|
||||||
|
|
||||||
|
### 1. Identify What to Review
|
||||||
|
|
||||||
|
| Argument | What to Review |
|
||||||
|
|----------|---------------|
|
||||||
|
| `recent` | `git diff HEAD~1` (last commit) |
|
||||||
|
| `staged` | `git diff --cached` (staged changes) |
|
||||||
|
| `unstaged` | `git diff` (working directory) |
|
||||||
|
| file path | Specific file(s) |
|
||||||
|
| commit range | `git diff <range>` |
|
||||||
|
|
||||||
|
### 2. Review Each Dimension
|
||||||
|
|
||||||
|
| Dimension | Key Question |
|
||||||
|
|-----------|--------------|
|
||||||
|
| **Completeness** | Does it do everything it should? |
|
||||||
|
| **Accuracy** | Is it correct? Edge cases? Errors? |
|
||||||
|
| **Tech Debt** | Are we creating future problems? |
|
||||||
|
| **Maintainability** | Can someone else understand this? |
|
||||||
|
| **Extensibility** | Can this grow without rewrites? |
|
||||||
|
| **DRY** | Is there duplicated logic? |
|
||||||
|
| **CLEAN** | Clear, Logical, Efficient, Accurate, Neat? |
|
||||||
|
|
||||||
|
### 3. Categorize by Severity
|
||||||
|
|
||||||
|
| Severity | Meaning |
|
||||||
|
|----------|---------|
|
||||||
|
| **BLOCKER** | Cannot ship |
|
||||||
|
| **CRITICAL** | Significant risk |
|
||||||
|
| **WARNING** | Quality concern |
|
||||||
|
| **SUGGESTION** | Improvement |
|
||||||
|
| **PRAISE** | Good practice |
|
||||||
|
|
||||||
|
### 4. Provide Proper Fixes
|
||||||
|
|
||||||
|
For each issue:
|
||||||
|
- Location (file:line)
|
||||||
|
- What's wrong and why it matters
|
||||||
|
- **Production-quality fix** (not a quick patch)
|
||||||
|
|
||||||
|
### 5. Summarize
|
||||||
|
|
||||||
|
- Overall recommendation: APPROVE / REQUEST_CHANGES
|
||||||
|
- Count by severity
|
||||||
|
- Key action items
|
||||||
|
- What's done well
|
||||||
|
|
||||||
|
## Quick Checks
|
||||||
|
|
||||||
|
### Go
|
||||||
|
```bash
|
||||||
|
grep -n "panic(\|log.Fatal" [files] # Should use error returns
|
||||||
|
grep -n "// TODO\|// FIXME" [files] # Tracked?
|
||||||
|
```
|
||||||
|
|
||||||
|
### TypeScript
|
||||||
|
```bash
|
||||||
|
grep -n ": any\|as any" [files] # Should be typed
|
||||||
|
grep -n "console.log" [files] # Debug left in?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS provide production-quality fixes
|
||||||
|
- ALWAYS categorize by severity
|
||||||
|
- ALWAYS acknowledge good practices
|
||||||
|
- NEVER block on formatting (formatters do that)
|
||||||
|
- NEVER critique without alternative
|
||||||
101
.claude/commands/review-feature.md
Normal file
101
.claude/commands/review-feature.md
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
description: Perform a comprehensive code review of a feature
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Glob, Grep, Write
|
||||||
|
---
|
||||||
|
|
||||||
|
Review feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load Feature Context
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Read the spec and design to understand what was intended:
|
||||||
|
- `.sdlc/features/$ARGUMENTS/spec.md`
|
||||||
|
- `.sdlc/features/$ARGUMENTS/design.md`
|
||||||
|
|
||||||
|
### 2. Identify Changed Files
|
||||||
|
|
||||||
|
Determine what files were created or modified for this feature. Check git history, task descriptions, or search for recent changes in relevant directories.
|
||||||
|
|
||||||
|
### 3. Review Each Dimension
|
||||||
|
|
||||||
|
| Dimension | Key Question |
|
||||||
|
|-----------|--------------|
|
||||||
|
| **Correctness** | Does the code do what the spec requires? |
|
||||||
|
| **Test Coverage** | Is every acceptance criterion tested? |
|
||||||
|
| **Error Handling** | Are failures handled, not swallowed? |
|
||||||
|
| **Security** | Input validation, auth checks, data exposure? |
|
||||||
|
| **Performance** | N+1 queries, unbounded loops, missing timeouts? |
|
||||||
|
| **Code Style** | Follows existing patterns and conventions? |
|
||||||
|
| **Documentation** | Public APIs documented, complex logic commented? |
|
||||||
|
|
||||||
|
### 4. Categorize Findings
|
||||||
|
|
||||||
|
| Severity | Meaning |
|
||||||
|
|----------|---------|
|
||||||
|
| **BLOCKER** | Cannot ship -- must fix before merge |
|
||||||
|
| **WARNING** | Quality concern -- should fix |
|
||||||
|
| **SUGGESTION** | Improvement -- nice to have |
|
||||||
|
|
||||||
|
### 5. Write Review Report
|
||||||
|
|
||||||
|
Write to `.sdlc/features/$ARGUMENTS/review.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Code Review: [Feature Title]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
[Overall assessment: PASS / NEEDS_FIX]
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### Blockers
|
||||||
|
- [ ] [FILE:LINE] [Description] -- [Why it matters]
|
||||||
|
|
||||||
|
### Warnings
|
||||||
|
- [ ] [FILE:LINE] [Description] -- [Suggested fix]
|
||||||
|
|
||||||
|
### Suggestions
|
||||||
|
- [ ] [FILE:LINE] [Description]
|
||||||
|
|
||||||
|
## Spec Alignment
|
||||||
|
[Does the implementation match the spec? Any gaps?]
|
||||||
|
|
||||||
|
## Test Coverage Assessment
|
||||||
|
[Which acceptance criteria have tests? Which are missing?]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Register and Evaluate the Artifact
|
||||||
|
|
||||||
|
Create the artifact:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc artifact create $ARGUMENTS review
|
||||||
|
```
|
||||||
|
|
||||||
|
Then evaluate the review results and set the appropriate status:
|
||||||
|
|
||||||
|
- If the review has **NO blockers**: mark as passed
|
||||||
|
```bash
|
||||||
|
sdlc artifact pass $ARGUMENTS review
|
||||||
|
```
|
||||||
|
- If the review has **blockers**: mark as needs-fix
|
||||||
|
```bash
|
||||||
|
sdlc artifact needs-fix $ARGUMENTS review
|
||||||
|
```
|
||||||
|
|
||||||
|
This status drives the SDLC classifier to either advance to audit or trigger fix-review-issues.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS read spec and design before reviewing code
|
||||||
|
- NEVER skip the security review dimension
|
||||||
|
- ALWAYS check test coverage against acceptance criteria
|
||||||
|
- ALWAYS provide actionable findings with file locations
|
||||||
|
- NEVER mark a review as passed if it has unresolved blockers
|
||||||
|
- ALWAYS set the artifact status (pass or needs-fix) after writing the review
|
||||||
81
.claude/commands/root-cause.md
Normal file
81
.claude/commands/root-cause.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
description: Systematic root cause analysis with parallel agent investigation
|
||||||
|
argument-hint: <issue or symptom to diagnose>
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Investigate the root cause of: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Load the `root-cause-analyst` skill, then follow its protocol:
|
||||||
|
|
||||||
|
### What This Command Does
|
||||||
|
|
||||||
|
1. **Triage** - Categorize the symptom (error, performance, data, security, infra)
|
||||||
|
2. **Parallel investigation** - Launch 1-5 investigation threads with Task tool
|
||||||
|
3. **Synthesize** - Collect findings, identify corroboration and contradictions
|
||||||
|
4. **Propose root causes** - With confidence scores and evidence
|
||||||
|
5. **Solution spectrum** - Patch (quick), Fix (direct), Proper (architectural)
|
||||||
|
|
||||||
|
### Investigation Focus Areas
|
||||||
|
|
||||||
|
| Signal | Investigation Focus |
|
||||||
|
|--------|---------------------|
|
||||||
|
| Stack trace, panic, error | Code paths, error handling |
|
||||||
|
| Slow, timeout, latency | Bottlenecks, queries, I/O |
|
||||||
|
| Data missing, corrupt | Storage layer, data flow |
|
||||||
|
| Auth, permission denied | Auth middleware, token flow |
|
||||||
|
| Infra, deploy, env | Config, networking, resources |
|
||||||
|
| Test failures | Test setup, mocks, assertions |
|
||||||
|
| Race condition, deadlock | Concurrency, shared state |
|
||||||
|
| Security, injection | Input validation, sanitization |
|
||||||
|
|
||||||
|
### Expected Output
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Issue Triage
|
||||||
|
|
||||||
|
**Symptom:** [What's happening]
|
||||||
|
**Category:** [error | performance | data | security | infra]
|
||||||
|
**Investigation Threads:** [List with rationale]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Investigation Results
|
||||||
|
|
||||||
|
### Thread 1: [Focus Area]
|
||||||
|
[Summary of what was found]
|
||||||
|
|
||||||
|
### Thread 2: [Focus Area]
|
||||||
|
[Summary of what was found]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Causes
|
||||||
|
|
||||||
|
### #1: [Name] (Confidence: X%)
|
||||||
|
**Evidence:** ...
|
||||||
|
**Mechanism:** ...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended: Root Cause #1
|
||||||
|
|
||||||
|
### Patch (Quick)
|
||||||
|
[Minimal change]
|
||||||
|
|
||||||
|
### Fix (Direct)
|
||||||
|
[Address root cause]
|
||||||
|
|
||||||
|
### Proper (Architectural)
|
||||||
|
[Prevent class of issues]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- NEVER propose root cause without citing investigation findings
|
||||||
|
- NEVER skip investigation (coordinate, don't guess)
|
||||||
|
- NEVER give 100% confidence (always leave room for unknowns)
|
||||||
|
- ALWAYS offer at least patch and proper solutions
|
||||||
|
- ALWAYS launch investigation threads in parallel when possible
|
||||||
108
.claude/commands/run-qa.md
Normal file
108
.claude/commands/run-qa.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
description: Execute the QA test plan for a feature
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Task
|
||||||
|
---
|
||||||
|
|
||||||
|
Run QA for feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load Feature and QA Plan
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Read:
|
||||||
|
- `.sdlc/features/$ARGUMENTS/qa-plan.md` -- the test plan to execute
|
||||||
|
- `.sdlc/features/$ARGUMENTS/spec.md` -- acceptance criteria to verify
|
||||||
|
|
||||||
|
### 2. Execute Unit Tests
|
||||||
|
|
||||||
|
Run the project test suite and capture results:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./... -v 2>&1 | tee /tmp/qa-test-output.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Execute Each Test Scenario
|
||||||
|
|
||||||
|
Work through every scenario in the QA plan:
|
||||||
|
- **Happy path scenarios** -- verify expected behavior
|
||||||
|
- **Edge case scenarios** -- verify boundary handling
|
||||||
|
- **Error case scenarios** -- verify failure modes
|
||||||
|
|
||||||
|
For each scenario, record: scenario ID, status (PASS/FAIL), evidence (test output or manual verification).
|
||||||
|
|
||||||
|
### 4. Verify Acceptance Criteria
|
||||||
|
|
||||||
|
Cross-reference each acceptance criterion from the spec against test results. Every criterion must have at least one passing test scenario.
|
||||||
|
|
||||||
|
### 5. Write QA Results
|
||||||
|
|
||||||
|
Write to `.sdlc/features/$ARGUMENTS/qa-results.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# QA Results: [Feature Title]
|
||||||
|
|
||||||
|
## Test Run Summary
|
||||||
|
- **Date:** [timestamp]
|
||||||
|
- **Overall:** PASS / FAIL
|
||||||
|
- **Scenarios:** N passed, M failed, K skipped
|
||||||
|
|
||||||
|
## Scenario Results
|
||||||
|
|
||||||
|
### Happy Path
|
||||||
|
| ID | Scenario | Status | Evidence |
|
||||||
|
|----|----------|--------|----------|
|
||||||
|
| HP-1 | [description] | PASS/FAIL | [test name or output] |
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
| ID | Scenario | Status | Evidence |
|
||||||
|
|----|----------|--------|----------|
|
||||||
|
| EC-1 | [description] | PASS/FAIL | [evidence] |
|
||||||
|
|
||||||
|
### Error Cases
|
||||||
|
| ID | Scenario | Status | Evidence |
|
||||||
|
|----|----------|--------|----------|
|
||||||
|
| ER-1 | [description] | PASS/FAIL | [evidence] |
|
||||||
|
|
||||||
|
## Acceptance Criteria Coverage
|
||||||
|
| Criterion | Scenarios | Status |
|
||||||
|
|-----------|-----------|--------|
|
||||||
|
| AC-1 | HP-1, EC-2 | COVERED / GAP |
|
||||||
|
|
||||||
|
## Failures (if any)
|
||||||
|
[Detailed description of each failure with reproduction steps]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Register and Evaluate the Artifact
|
||||||
|
|
||||||
|
Create the artifact:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc artifact create $ARGUMENTS qa_results
|
||||||
|
```
|
||||||
|
|
||||||
|
Then evaluate the QA results and set the appropriate status:
|
||||||
|
|
||||||
|
- If **all scenarios pass** and all acceptance criteria are covered: mark as passed
|
||||||
|
```bash
|
||||||
|
sdlc artifact pass $ARGUMENTS qa_results
|
||||||
|
```
|
||||||
|
- If **any scenario fails** or acceptance criteria have gaps: mark as failed
|
||||||
|
```bash
|
||||||
|
sdlc artifact fail $ARGUMENTS qa_results
|
||||||
|
```
|
||||||
|
|
||||||
|
This status drives the SDLC classifier to either advance to merge or trigger fix-qa-failures.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- ALWAYS execute every scenario from the QA plan -- no skipping
|
||||||
|
- NEVER skip failing tests or mark them as passing without evidence
|
||||||
|
- ALWAYS document ALL results, including passing scenarios
|
||||||
|
- ALWAYS verify acceptance criteria coverage explicitly
|
||||||
|
- NEVER fabricate test evidence -- run the actual tests
|
||||||
|
- ALWAYS set the artifact status (pass or fail) after writing QA results
|
||||||
76
.claude/commands/spec-feature.md
Normal file
76
.claude/commands/spec-feature.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
description: Create a feature specification document
|
||||||
|
argument-hint: <feature-slug>
|
||||||
|
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
Create a specification for feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Load Feature Context
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc feature show $ARGUMENTS --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Parse the output to understand the feature current phase, metadata, and any existing artifacts.
|
||||||
|
|
||||||
|
### 2. Check for Existing Spec
|
||||||
|
|
||||||
|
Read `.sdlc/features/$ARGUMENTS/spec.md` if it exists. If a draft already exists, build on it rather than starting from scratch.
|
||||||
|
|
||||||
|
### 3. Gather Codebase Context
|
||||||
|
|
||||||
|
Search the codebase for patterns, modules, and systems relevant to this feature. Understand what exists before specifying what should change.
|
||||||
|
|
||||||
|
### 4. Write the Specification
|
||||||
|
|
||||||
|
Write to `.sdlc/features/$ARGUMENTS/spec.md` with this structure:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Feature: [Title]
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
[What problem does this solve? Who has this problem?]
|
||||||
|
|
||||||
|
## User Stories
|
||||||
|
- As a [role], I want [capability] so that [benefit]
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- [ ] [Criterion 1 - testable, specific]
|
||||||
|
- [ ] [Criterion 2]
|
||||||
|
- [ ] [Criterion N]
|
||||||
|
|
||||||
|
## Technical Constraints
|
||||||
|
[Platform limits, API compatibility, performance requirements]
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
[What must exist or be true before this can be built]
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
[Explicitly excluded from this feature]
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
[Unresolved decisions that need input]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Register the Artifact
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdlc artifact create $ARGUMENTS spec
|
||||||
|
```
|
||||||
|
|
||||||
|
The classifier will detect the spec exists and determine the next action (typically awaiting approval).
|
||||||
|
|
||||||
|
### 6. Report
|
||||||
|
|
||||||
|
Summarize what was specified, list acceptance criteria count, and note any open questions that need human input.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- NEVER skip loading feature context first
|
||||||
|
- ALWAYS include acceptance criteria -- they drive QA later
|
||||||
|
- NEVER approve your own spec -- it requires human approval
|
||||||
|
- ALWAYS list open questions rather than making assumptions
|
||||||
|
- ALWAYS search the codebase before writing constraints
|
||||||
61
.claude/commands/thinkthrough.md
Normal file
61
.claude/commands/thinkthrough.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
description: Deep collaborative thinking about a problem - read code, consult experts, explore options, think together
|
||||||
|
argument-hint: <problem to think through>
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion
|
||||||
|
---
|
||||||
|
|
||||||
|
Think through this problem deeply: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Load the `ideate` skill, then:
|
||||||
|
|
||||||
|
### 1. Understand What's Being Asked
|
||||||
|
|
||||||
|
- **User's words:** [exact quote]
|
||||||
|
- **Your interpretation:** [what you think they mean]
|
||||||
|
- **Scope:** [what's in/out]
|
||||||
|
|
||||||
|
If interpretation differs, **ask**.
|
||||||
|
|
||||||
|
### 2. Gather Context
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -rn "[keywords]" --include="*.go" --include="*.ts"
|
||||||
|
```
|
||||||
|
|
||||||
|
Read the specs, docs, adjacent systems.
|
||||||
|
|
||||||
|
### 3. Explore the Solution Space
|
||||||
|
|
||||||
|
Write out 3-4 options (always include "do nothing"):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Option A: [Name]
|
||||||
|
- Approach: [how]
|
||||||
|
- Pros: [why good]
|
||||||
|
- Cons: [why risky]
|
||||||
|
- Assumption: [what must be true]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Step Back
|
||||||
|
|
||||||
|
- **Assumptions:** What am I assuming that's unverified?
|
||||||
|
- **Fresh eyes:** Would someone new agree?
|
||||||
|
- **Skeptic:** What would a disagreer say?
|
||||||
|
- **Missing:** Whose perspective am I ignoring?
|
||||||
|
|
||||||
|
### 5. Think Out Loud
|
||||||
|
|
||||||
|
Share: what you learned, what surprised you, the core tension, where you're leaning, questions for them.
|
||||||
|
|
||||||
|
### 6. Collaborate
|
||||||
|
|
||||||
|
Listen, adjust, drill deeper, iterate.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- READ code before forming opinions
|
||||||
|
- INCLUDE "do nothing" as an option
|
||||||
|
- SURFACE assumptions explicitly
|
||||||
|
- INVITE dialogue, don't just deliver answers
|
||||||
60
.claude/commands/trace-feature.md
Normal file
60
.claude/commands/trace-feature.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
description: Trace a feature end-to-end across the codebase - find all files, flows, DB tables, quality issues, and dead code
|
||||||
|
argument-hint: <feature name or description>
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Trace this feature: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Load the `feature-tracer` skill, then:
|
||||||
|
|
||||||
|
### 1. Discover Entry Points
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API handlers (Go)
|
||||||
|
grep -rn "[keyword]" --include="*.go" services/*/internal/
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
grep -rn "[keyword]" --include="*.tsx" --include="*.ts" apps/
|
||||||
|
|
||||||
|
# Workers
|
||||||
|
grep -rn "[keyword]" --include="*.go" workers/*/internal/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Trace Each Path
|
||||||
|
|
||||||
|
For each entry point:
|
||||||
|
1. Read the file
|
||||||
|
2. Follow each call (service → repository → external)
|
||||||
|
3. Map DB tables touched
|
||||||
|
4. Note external dependencies
|
||||||
|
|
||||||
|
### 3. Assess Quality
|
||||||
|
|
||||||
|
For each traced file:
|
||||||
|
- Has tests? (`ls [file]_test.go` or `ls [file].test.ts`)
|
||||||
|
- TODOs? (`grep -n "TODO\|FIXME" [file]`)
|
||||||
|
- Dead code? (grep for function callers)
|
||||||
|
- Error handling adequate?
|
||||||
|
|
||||||
|
### 4. Step Back
|
||||||
|
|
||||||
|
- [ ] Traced both read AND write paths?
|
||||||
|
- [ ] Checked error/failure paths?
|
||||||
|
- [ ] Verified dead code claims with grep counts?
|
||||||
|
|
||||||
|
### 5. Output
|
||||||
|
|
||||||
|
1. **Entry Points Table** - UI, API, Worker with file:line
|
||||||
|
2. **Execution Flow** - Visual path diagram
|
||||||
|
3. **Database Schema** - Tables touched
|
||||||
|
4. **Quality Assessment** - Good / Bad / Ugly / Dead Code
|
||||||
|
5. **Uncertainties** - What couldn't be traced
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- NEVER mark code as "dead" without grep evidence showing 0 callers
|
||||||
|
- ALWAYS include file:line references
|
||||||
|
- ALWAYS note what you couldn't trace
|
||||||
74
.claude/commands/verify.md
Normal file
74
.claude/commands/verify.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
description: Verify completed work meets acceptance criteria and quality standards
|
||||||
|
argument-hint: <feature or task description to verify>
|
||||||
|
allowed-tools: Task, Read, Write, Edit, Glob, Grep, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
Verify this work: $ARGUMENTS
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Understand What Was Done
|
||||||
|
|
||||||
|
Read recent commits and changed files:
|
||||||
|
```bash
|
||||||
|
git log --oneline -5
|
||||||
|
git diff HEAD~1 --stat
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Check Acceptance Criteria
|
||||||
|
|
||||||
|
For each requirement:
|
||||||
|
- [ ] Implemented correctly?
|
||||||
|
- [ ] Tests pass?
|
||||||
|
- [ ] Edge cases handled?
|
||||||
|
- [ ] Error handling appropriate?
|
||||||
|
|
||||||
|
### 3. Run Quality Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go vet ./...
|
||||||
|
go test ./...
|
||||||
|
golangci-lint run ./... 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Functional Verification
|
||||||
|
|
||||||
|
- Read the code path for the feature
|
||||||
|
- Verify inputs are validated
|
||||||
|
- Verify errors propagate correctly
|
||||||
|
- Check that the happy path works logically
|
||||||
|
|
||||||
|
### 5. Integration Check
|
||||||
|
|
||||||
|
- Does it work with existing code?
|
||||||
|
- Are there breaking changes?
|
||||||
|
- Are dependencies updated?
|
||||||
|
|
||||||
|
### 6. Report
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Verification Report: [Feature]
|
||||||
|
|
||||||
|
### Criteria
|
||||||
|
| Requirement | Status | Evidence |
|
||||||
|
|-------------|--------|----------|
|
||||||
|
| [requirement] | PASS/FAIL | [file:line or test name] |
|
||||||
|
|
||||||
|
### Quality
|
||||||
|
- Tests: PASS/FAIL (N tests)
|
||||||
|
- Lint: PASS/FAIL
|
||||||
|
- Vet: PASS/FAIL
|
||||||
|
|
||||||
|
### Issues Found
|
||||||
|
[Any problems discovered]
|
||||||
|
|
||||||
|
### Verdict: VERIFIED / NEEDS_WORK
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- VERIFY with evidence, not assumptions
|
||||||
|
- RUN tests, don't just read them
|
||||||
|
- CHECK edge cases explicitly
|
||||||
|
- REPORT honestly - don't pass work that isn't ready
|
||||||
151
.claude/guides/backend/api-patterns.md
Normal file
151
.claude/guides/backend/api-patterns.md
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# Backend API Patterns
|
||||||
|
|
||||||
|
## Handler Pattern (Wrap)
|
||||||
|
|
||||||
|
All handlers return `error` and are wrapped with `app.Wrap()`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
id := chi.URLParam(r, "id")
|
||||||
|
item, err := h.svc.Get(r.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
return httperror.NotFoundf("item %s not found", id)
|
||||||
|
}
|
||||||
|
return err // becomes 500
|
||||||
|
}
|
||||||
|
httpresponse.OK(w, r, item)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// In routes.go:
|
||||||
|
r.Get("/items/{id}", app.Wrap(handler.Get))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Request Binding
|
||||||
|
|
||||||
|
Use `app.Bind` or `app.BindAndValidate`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
var req CreateRequest
|
||||||
|
if err := app.BindAndValidate(r, &req); err != nil {
|
||||||
|
return err // returns 400 or 422 HTTPError
|
||||||
|
}
|
||||||
|
// req is decoded and validated
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Validation uses go-playground/validator struct tags:
|
||||||
|
- `validate:"required"` - field is required
|
||||||
|
- `validate:"min=1,max=100"` - length constraints
|
||||||
|
- `validate:"email"` - email format
|
||||||
|
- `validate:"uuid"` - UUID format
|
||||||
|
|
||||||
|
## HTTPError Sentinels
|
||||||
|
|
||||||
|
Use `httperror` factories to return typed errors:
|
||||||
|
|
||||||
|
| Function | Status | When to use |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `httperror.BadRequest(msg)` | 400 | Invalid input format |
|
||||||
|
| `httperror.Unauthorized(msg)` | 401 | Missing/invalid credentials |
|
||||||
|
| `httperror.Forbidden(msg)` | 403 | No permission |
|
||||||
|
| `httperror.NotFoundf(fmt, args)` | 404 | Resource doesn't exist |
|
||||||
|
| `httperror.Conflict(msg)` | 409 | Duplicate resource |
|
||||||
|
| `httperror.Validation(msg)` | 422 | Struct validation failure |
|
||||||
|
| `httperror.Internal(msg)` | 500 | Server error (prefer returning raw err) |
|
||||||
|
|
||||||
|
Add details with `httperror.WithDetails(err, details)`.
|
||||||
|
|
||||||
|
## Response Envelope
|
||||||
|
|
||||||
|
All responses use the standard envelope from `httpresponse`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": { ... },
|
||||||
|
"meta": {
|
||||||
|
"request_id": "abc-123",
|
||||||
|
"timestamp": "2024-01-15T10:30:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `httpresponse.OK(w, r, data)`, `httpresponse.Created(w, r, data)`, `httpresponse.NoContent(w)`.
|
||||||
|
|
||||||
|
## OpenAPI Documentation
|
||||||
|
|
||||||
|
Annotate endpoints in a `spec.go` file:
|
||||||
|
|
||||||
|
```go
|
||||||
|
spec := openapi.NewOpenAPISpec("Service Name", "1.0.0").
|
||||||
|
WithBearerSecurity("bearer", "JWT token")
|
||||||
|
|
||||||
|
spec.WithSchema("Item", openapi.Object(map[string]openapi.Schema{
|
||||||
|
"id": openapi.UUID(),
|
||||||
|
"name": openapi.String().WithExample("My Item"),
|
||||||
|
}, "id", "name"))
|
||||||
|
|
||||||
|
spec.AddPath("/api/v1/items", "get", map[string]any{
|
||||||
|
"summary": "List items",
|
||||||
|
"tags": []string{"Items"},
|
||||||
|
"responses": map[string]any{
|
||||||
|
"200": openapi.OpResponse("Success", openapi.RefArray("Item")),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Mount with `application.EnableDocs(spec)` to get `/docs` (Scalar UI) and `/openapi.json`.
|
||||||
|
|
||||||
|
## Auth Integration
|
||||||
|
|
||||||
|
Auth is opt-in via `AUTH_ENABLED=true`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// In routes.go - protected route group
|
||||||
|
r.Group(func(r app.Router) {
|
||||||
|
if cfg.AuthEnabled {
|
||||||
|
r.Use(auth.Middleware(auth.MiddlewareConfig{
|
||||||
|
Validator: auth.NewJWTValidator(auth.JWTConfig{
|
||||||
|
Secret: []byte(cfg.JWTSecret),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
r.Post("/items", app.Wrap(handler.Create))
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Access user in handlers:
|
||||||
|
```go
|
||||||
|
user := auth.GetUser(r.Context())
|
||||||
|
if user != nil {
|
||||||
|
logger.Info("created by", "user", user.ID)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Health Checks
|
||||||
|
|
||||||
|
Basic health is auto-registered at `/health` and `/ready`.
|
||||||
|
|
||||||
|
For dependency checks:
|
||||||
|
```go
|
||||||
|
healthHandler := app.NewHealthHandler(app.HealthConfig{
|
||||||
|
Service: "my-service",
|
||||||
|
Checks: map[string]app.HealthChecker{
|
||||||
|
"database": app.PingChecker(db.PingContext),
|
||||||
|
"redis": app.PingChecker(redis.Ping),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r.Get("/health", healthHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chassis Package
|
||||||
|
|
||||||
|
The `pkg/chassis` package re-exports `pkg/app` types for convenience:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "git.threesix.ai/jordan/slack-auth-1770277653/pkg/chassis"
|
||||||
|
|
||||||
|
svc := chassis.New("my-service", chassis.WithDefaultPort(8080))
|
||||||
|
```
|
||||||
314
.claude/guides/feature-development.md
Normal file
314
.claude/guides/feature-development.md
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
# Feature Development Guide
|
||||||
|
|
||||||
|
This guide documents the end-to-end workflow for building features in your composable monorepo.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create feature branch
|
||||||
|
git checkout -b feature/my-feature
|
||||||
|
|
||||||
|
# 2. Implement API endpoint (use Wrap + Bind patterns)
|
||||||
|
# 3. Implement frontend (use design system + auth)
|
||||||
|
# 4. Write tests
|
||||||
|
# 5. Run quality checks
|
||||||
|
./scripts/quality.sh
|
||||||
|
|
||||||
|
# 6. Commit and push
|
||||||
|
git add -A && git commit -m "feat: my feature"
|
||||||
|
git push -u origin feature/my-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
slack-auth-1770277653/
|
||||||
|
├── pkg/ # Shared Go packages (chassis)
|
||||||
|
│ ├── app/ # Wrap, Bind, Health patterns
|
||||||
|
│ ├── httperror/ # Typed HTTP errors
|
||||||
|
│ ├── httpresponse/ # JSON response envelope
|
||||||
|
│ └── auth/ # JWT + API key validation
|
||||||
|
├── packages/ # Shared TypeScript packages
|
||||||
|
│ ├── ui/ # Design system components
|
||||||
|
│ ├── layout/ # DashboardShell, Sidebar
|
||||||
|
│ ├── auth/ # AuthProvider, useAuth
|
||||||
|
│ └── api-client/ # Generated TypeScript client
|
||||||
|
├── services/ # Go backend services
|
||||||
|
└── apps/ # Frontend applications
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backend Patterns
|
||||||
|
|
||||||
|
### Wrap Pattern
|
||||||
|
|
||||||
|
Convert error-returning handlers to `http.HandlerFunc`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "slack-auth-1770277653/pkg/app"
|
||||||
|
|
||||||
|
func (h *Handler) GetItem() http.HandlerFunc {
|
||||||
|
return app.Wrap(func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
// Return errors - they become proper HTTP responses
|
||||||
|
item, err := h.svc.Get(r.Context(), chi.URLParam(r, "id"))
|
||||||
|
if err != nil {
|
||||||
|
return httperror.NotFound("item not found")
|
||||||
|
}
|
||||||
|
httpresponse.JSON(w, http.StatusOK, item)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bind Pattern
|
||||||
|
|
||||||
|
Parse and validate request bodies in one call:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "slack-auth-1770277653/pkg/app"
|
||||||
|
|
||||||
|
func (h *Handler) CreateItem() http.HandlerFunc {
|
||||||
|
type request struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||||
|
Type string `json:"type" validate:"required,oneof=a b c"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Wrap(func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
var req request
|
||||||
|
if err := app.BindAndValidate(r, &req); err != nil {
|
||||||
|
return err // Validation errors become 400 with details
|
||||||
|
}
|
||||||
|
// Use req.Name, req.Type...
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTTPError Sentinels
|
||||||
|
|
||||||
|
Use typed errors that map to HTTP status codes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "slack-auth-1770277653/pkg/httperror"
|
||||||
|
|
||||||
|
httperror.BadRequest("invalid input") // 400
|
||||||
|
httperror.Unauthorized("not authenticated") // 401
|
||||||
|
httperror.Forbidden("access denied") // 403
|
||||||
|
httperror.NotFound("resource not found") // 404
|
||||||
|
httperror.Conflict("already exists") // 409
|
||||||
|
httperror.Internal("something went wrong") // 500
|
||||||
|
|
||||||
|
// With formatted messages
|
||||||
|
httperror.NotFoundf("user %s not found", userID)
|
||||||
|
|
||||||
|
// With details
|
||||||
|
httperror.WithDetails(
|
||||||
|
httperror.BadRequest("validation failed"),
|
||||||
|
map[string]string{"name": "required"},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auth Context
|
||||||
|
|
||||||
|
Access the authenticated user from request context:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "slack-auth-1770277653/pkg/auth"
|
||||||
|
|
||||||
|
user, ok := auth.GetUser(r.Context())
|
||||||
|
if !ok {
|
||||||
|
return httperror.Unauthorized("not authenticated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// user.ID, user.Email, user.Roles available
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontend Patterns
|
||||||
|
|
||||||
|
### Design System Components
|
||||||
|
|
||||||
|
Import from the shared UI package:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Button, Card, Input, Label, Badge } from '@slack-auth-1770277653/ui';
|
||||||
|
import { DashboardShell, Sidebar, Header } from '@slack-auth-1770277653/layout';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auth Hook
|
||||||
|
|
||||||
|
Access auth state and actions:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useAuth } from '@slack-auth-1770277653/auth';
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const { user, isLoading, login, logout, refreshUser } = useAuth();
|
||||||
|
|
||||||
|
if (isLoading) return <div>Loading...</div>;
|
||||||
|
if (!user) return <LoginPrompt />;
|
||||||
|
|
||||||
|
return <div>Hello, {user.name}!</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Protected Routes
|
||||||
|
|
||||||
|
Wrap routes that require authentication:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { ProtectedRoute } from '@slack-auth-1770277653/auth';
|
||||||
|
|
||||||
|
// In your router or layout
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardPage />
|
||||||
|
</ProtectedRoute>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server Actions (Next.js)
|
||||||
|
|
||||||
|
Create server actions for form submissions:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/actions/my-action.ts
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
import { revalidatePath } from 'next/cache';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
|
export async function createItem(formData: FormData) {
|
||||||
|
const token = cookies().get('auth_token')?.value;
|
||||||
|
|
||||||
|
const response = await fetch(`${process.env.API_URL}/api/items`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: formData.get('name'),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
revalidatePath('/items');
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Client Generation
|
||||||
|
|
||||||
|
After adding OpenAPI annotations, regenerate the TypeScript client:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/generate-client.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This updates `packages/api-client/src/schema.d.ts` with typed endpoints.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Handler Tests
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestHandler_GetItem(t *testing.T) {
|
||||||
|
handler := NewHandler(mockService, slog.Default())
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/api/items/123", nil)
|
||||||
|
req = req.WithContext(auth.SetUser(req.Context(), &auth.User{ID: "user-1"}))
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.GetItem().ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# All tests
|
||||||
|
./scripts/quality.sh
|
||||||
|
|
||||||
|
# Specific service
|
||||||
|
go test ./services/api/...
|
||||||
|
|
||||||
|
# With coverage
|
||||||
|
go test -cover ./services/api/...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Review
|
||||||
|
|
||||||
|
Use the built-in review command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/review-code
|
||||||
|
```
|
||||||
|
|
||||||
|
This checks for:
|
||||||
|
- Completeness and accuracy
|
||||||
|
- Tech debt indicators
|
||||||
|
- Maintainability issues
|
||||||
|
- DRY/CLEAN violations
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Commit Message Format
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: add user profile feature
|
||||||
|
|
||||||
|
- Add GET/PUT /api/users/me endpoints
|
||||||
|
- Add profile page with edit form
|
||||||
|
- Add handler tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI Pipeline
|
||||||
|
|
||||||
|
On push to any branch:
|
||||||
|
1. Build all components
|
||||||
|
2. Run tests
|
||||||
|
3. Build Docker images
|
||||||
|
|
||||||
|
On merge to main:
|
||||||
|
1. Build + test
|
||||||
|
2. Push to registry
|
||||||
|
3. Deploy to K8s
|
||||||
|
|
||||||
|
### Verifying Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check pod status
|
||||||
|
kubectl get pods -n projects -l app=slack-auth-1770277653
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
kubectl logs -n projects -l app=slack-auth-1770277653 -f
|
||||||
|
|
||||||
|
# Test endpoint
|
||||||
|
curl https://we65jz83.threesix.ai/api/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### Handler returns 500 instead of proper error
|
||||||
|
Use `httperror.*` functions, not raw `errors.New()`.
|
||||||
|
|
||||||
|
### Validation errors missing details
|
||||||
|
Use `app.BindAndValidate()` instead of separate bind + validate.
|
||||||
|
|
||||||
|
### Auth middleware not working
|
||||||
|
Check route is inside the `r.Group()` with `auth.Middleware()`.
|
||||||
|
|
||||||
|
### Generated client out of sync
|
||||||
|
Run `./scripts/generate-client.sh` after OpenAPI changes.
|
||||||
|
|
||||||
|
### Frontend auth not working
|
||||||
|
Ensure `AuthProvider` wraps your app in `providers.tsx`.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Local Setup](./local/setup.md)
|
||||||
|
- [Deploying](./ops/deploying.md)
|
||||||
125
.claude/guides/frontend/design-system.md
Normal file
125
.claude/guides/frontend/design-system.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# Frontend Design System
|
||||||
|
|
||||||
|
## UI Components (`@slack-auth-1770277653/ui`)
|
||||||
|
|
||||||
|
Available components from `packages/ui`:
|
||||||
|
|
||||||
|
| Component | Import | Variants |
|
||||||
|
|-----------|--------|----------|
|
||||||
|
| Button | `Button` | default, destructive, outline, secondary, ghost, link |
|
||||||
|
| Card | `Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter` | - |
|
||||||
|
| Input | `Input` | - |
|
||||||
|
| Label | `Label` | - |
|
||||||
|
| Badge | `Badge` | default, secondary, outline, success, warning, error, info |
|
||||||
|
| Dialog | `Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose` | - |
|
||||||
|
| Table | `Table, TableHeader, TableBody, TableRow, TableHead, TableCell` | - |
|
||||||
|
| Select | `Select, SelectTrigger, SelectContent, SelectItem, SelectValue` | - |
|
||||||
|
| Checkbox | `Checkbox` | - |
|
||||||
|
| Alert | `Alert, AlertTitle, AlertDescription` | default, destructive, success, warning |
|
||||||
|
| Textarea | `Textarea` | - |
|
||||||
|
| DropdownMenu | `DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, ...` | - |
|
||||||
|
| Sheet | `Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter` | side: top, right, bottom, left |
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
```tsx
|
||||||
|
import { Button, Badge, Card, CardContent } from '@slack-auth-1770277653/ui';
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Badge variant="success">Active</Badge>
|
||||||
|
<Button variant="outline">Edit</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Tokens
|
||||||
|
|
||||||
|
All components use CSS custom properties for theming. Define these in your app's globals.css:
|
||||||
|
|
||||||
|
### Colors
|
||||||
|
- `--background` - Page background
|
||||||
|
- `--surface-100` - Card/input backgrounds
|
||||||
|
- `--surface-200` - Hover states, secondary surfaces
|
||||||
|
- `--text-primary` - Main text
|
||||||
|
- `--text-secondary` - Secondary text
|
||||||
|
- `--text-muted` - Placeholder, hint text
|
||||||
|
- `--accent` - Primary accent color
|
||||||
|
- `--accent-foreground` - Text on accent
|
||||||
|
- `--border` - Border color
|
||||||
|
|
||||||
|
### Semantic Colors
|
||||||
|
- `--success`, `--success-bg`, `--success-border` - Success states
|
||||||
|
- `--warning`, `--warning-bg`, `--warning-border` - Warning states
|
||||||
|
- `--error`, `--error-bg`, `--error-border` - Error states
|
||||||
|
- `--info`, `--info-bg`, `--info-border` - Info states
|
||||||
|
|
||||||
|
### Z-Index
|
||||||
|
- `--z-popover` - Dropdowns, tooltips
|
||||||
|
- `--z-modal` - Dialogs, sheets
|
||||||
|
|
||||||
|
Import base tokens: `import '@slack-auth-1770277653/ui/styles';`
|
||||||
|
|
||||||
|
## Layout (`@slack-auth-1770277653/layout`)
|
||||||
|
|
||||||
|
DashboardShell provides the standard app layout:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { DashboardShell, Sidebar, Header } from '@slack-auth-1770277653/layout';
|
||||||
|
|
||||||
|
<DashboardShell
|
||||||
|
sidebar={<Sidebar items={navItems} />}
|
||||||
|
header={<Header title="Dashboard" />}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DashboardShell>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Auth (`@slack-auth-1770277653/auth`)
|
||||||
|
|
||||||
|
AuthProvider and ProtectedRoute for client-side auth:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { AuthProvider, ProtectedRoute, useAuth } from '@slack-auth-1770277653/auth';
|
||||||
|
|
||||||
|
// Wrap app in AuthProvider
|
||||||
|
<AuthProvider>
|
||||||
|
<App />
|
||||||
|
</AuthProvider>
|
||||||
|
|
||||||
|
// Protect routes
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DashboardPage />
|
||||||
|
</ProtectedRoute>
|
||||||
|
|
||||||
|
// Access auth state
|
||||||
|
const { user, isAuthenticated, login, logout } = useAuth();
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Client (`@slack-auth-1770277653/api-client`)
|
||||||
|
|
||||||
|
Typed API client with auth:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { createAPIClient } from '@slack-auth-1770277653/api-client';
|
||||||
|
|
||||||
|
const api = createAPIClient({
|
||||||
|
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use in server actions or client components
|
||||||
|
const items = await api.examples.list();
|
||||||
|
const item = await api.examples.get(id);
|
||||||
|
await api.examples.create({ name: 'New Item' });
|
||||||
|
```
|
||||||
|
|
||||||
|
## Icons
|
||||||
|
|
||||||
|
Re-exported from lucide-react via `@slack-auth-1770277653/ui`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Plus, Search, Settings, Trash2, User } from '@slack-auth-1770277653/ui';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dark Theme
|
||||||
|
|
||||||
|
All components default to dark theme using CSS variables. The design system is dark-first with surface layering (surface-100 lighter than background, surface-200 lighter than surface-100).
|
||||||
37
.claude/guides/local/setup.md
Normal file
37
.claude/guides/local/setup.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Local Development Setup
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Go 1.23+
|
||||||
|
- Node.js 20+ (for frontend apps)
|
||||||
|
- Docker and Docker Compose
|
||||||
|
- Make
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. **Start infrastructure services:**
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install dependencies:**
|
||||||
|
```bash
|
||||||
|
./scripts/install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start development:**
|
||||||
|
```bash
|
||||||
|
./scripts/dev.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Copy `.env.example` files in each component to `.env` and configure as needed.
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
| Task | Command |
|
||||||
|
|------|---------|
|
||||||
|
| Run all tests | `./scripts/quality.sh` |
|
||||||
|
| List components | `./scripts/discover.sh` |
|
||||||
|
| Lint code | `golangci-lint run ./...` |
|
||||||
24
.claude/guides/ops/deploying.md
Normal file
24
.claude/guides/ops/deploying.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Deploying slack-auth-1770277653
|
||||||
|
|
||||||
|
## CI/CD Pipeline
|
||||||
|
|
||||||
|
Deployments are triggered automatically via Woodpecker CI when changes are pushed to `main`.
|
||||||
|
|
||||||
|
## Manual Deployment
|
||||||
|
|
||||||
|
For manual deployments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy all components
|
||||||
|
curl -X POST $RDEV_API_URL/projects/slack-auth-1770277653/deploy \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY"
|
||||||
|
|
||||||
|
# Deploy a single component
|
||||||
|
curl -X POST $RDEV_API_URL/projects/slack-auth-1770277653/deploy \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-d '{"component": "services/auth-api"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Production environment variables are managed via Kubernetes secrets.
|
||||||
14
.claude/settings.local.json
Normal file
14
.claude/settings.local.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(go:*)",
|
||||||
|
"Bash(npm:*)",
|
||||||
|
"Bash(make:*)",
|
||||||
|
"Bash(docker:*)",
|
||||||
|
"Bash(kubectl:*)",
|
||||||
|
"Read",
|
||||||
|
"Write",
|
||||||
|
"Edit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
155
.claude/skills/code-review/SKILL.md
Normal file
155
.claude/skills/code-review/SKILL.md
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
---
|
||||||
|
name: code-reviewer
|
||||||
|
description: Review code for completeness, accuracy, tech debt, maintainability, extensibility, and DRY/CLEAN principles. Use after writing code to catch issues before commit.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Code Reviewer
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a senior engineer who reviews code like you'll be maintaining it at 3am during an incident. You care about correctness, clarity, and sustainability - not cleverness.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **Correctness First**: Does it work? Edge cases handled?
|
||||||
|
- **Clarity Over Cleverness**: Can someone else understand this in 6 months?
|
||||||
|
- **Sustainability**: Are we creating debt or paying it down?
|
||||||
|
- **Proper Fixes**: Suggest production-quality solutions, not quick patches
|
||||||
|
- **Actionable Feedback**: Show how to fix it, don't just point at problems
|
||||||
|
|
||||||
|
## Review Dimensions
|
||||||
|
|
||||||
|
### 1. Completeness
|
||||||
|
- Requirements met?
|
||||||
|
- Edge cases handled?
|
||||||
|
- Error paths covered?
|
||||||
|
- Tests for new code?
|
||||||
|
|
||||||
|
### 2. Accuracy
|
||||||
|
- Logic correct?
|
||||||
|
- Types used correctly?
|
||||||
|
- Race conditions?
|
||||||
|
- Resources properly acquired/released?
|
||||||
|
- Security (injection, auth bypass)?
|
||||||
|
|
||||||
|
### 3. Tech Debt
|
||||||
|
- Copy-pasted code?
|
||||||
|
- Hardcoded values?
|
||||||
|
- Tight coupling?
|
||||||
|
- "Temporary" solutions?
|
||||||
|
|
||||||
|
### 4. Maintainability
|
||||||
|
- Clear naming?
|
||||||
|
- Functions focused and < 50 lines?
|
||||||
|
- Consistent with surrounding code?
|
||||||
|
- Comments explain WHY not WHAT?
|
||||||
|
|
||||||
|
### 5. Extensibility
|
||||||
|
- Appropriate abstraction for likely changes?
|
||||||
|
- Well-defined boundaries?
|
||||||
|
- YAGNI - not over-engineering?
|
||||||
|
|
||||||
|
### 6. DRY
|
||||||
|
- Repeated code blocks?
|
||||||
|
- Same logic in multiple handlers?
|
||||||
|
- Constants in multiple files?
|
||||||
|
|
||||||
|
### 7. CLEAN
|
||||||
|
- **C**lear: Intent obvious from reading
|
||||||
|
- **L**ogical: Organized sensibly
|
||||||
|
- **E**fficient: No unnecessary work
|
||||||
|
- **A**ccurate: Does what it says
|
||||||
|
- **N**eat: Formatted, no cruft
|
||||||
|
|
||||||
|
## Severity Levels
|
||||||
|
|
||||||
|
| Severity | Meaning | Action |
|
||||||
|
|----------|---------|--------|
|
||||||
|
| **BLOCKER** | Cannot ship | Must fix |
|
||||||
|
| **CRITICAL** | Significant risk | Should fix |
|
||||||
|
| **WARNING** | Quality concern | Fix soon |
|
||||||
|
| **SUGGESTION** | Improvement | Consider |
|
||||||
|
| **PRAISE** | Good practice | Acknowledge |
|
||||||
|
|
||||||
|
## Review Verdict
|
||||||
|
|
||||||
|
| Verdict | When to Use |
|
||||||
|
|---------|-------------|
|
||||||
|
| **PASS** | All checks pass, no blockers or critical issues |
|
||||||
|
| **NEEDS_FIX** | Fixable issues found, re-review after changes |
|
||||||
|
| **BLOCK** | Fundamental problem, needs redesign |
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Code Review: [Scope]
|
||||||
|
|
||||||
|
### Verdict: PASS | NEEDS_FIX | BLOCK
|
||||||
|
|
||||||
|
### Files
|
||||||
|
- `file` - [what changed]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issues Found
|
||||||
|
| Severity | Issue | Location | Fix |
|
||||||
|
|----------|-------|----------|-----|
|
||||||
|
| BLOCKER | [Issue description] | `file:line` | [How to fix] |
|
||||||
|
| CRITICAL | [Issue description] | `file:line` | [How to fix] |
|
||||||
|
| WARNING | [Issue description] | `file:line` | [How to fix] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### What's Good
|
||||||
|
- [positives]
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
| Severity | Count |
|
||||||
|
|----------|-------|
|
||||||
|
| Blocker | N |
|
||||||
|
| Critical | N |
|
||||||
|
| Warning | N |
|
||||||
|
| Suggestion | N |
|
||||||
|
|
||||||
|
**Recommendation:** PASS / NEEDS_FIX / BLOCK
|
||||||
|
```
|
||||||
|
|
||||||
|
## Language Checks
|
||||||
|
|
||||||
|
### Go Red Flags
|
||||||
|
```go
|
||||||
|
panic() // Should use error returns
|
||||||
|
log.Fatal() // Only in main()
|
||||||
|
_ = err // Never ignore errors
|
||||||
|
interface{} // Use concrete types or any
|
||||||
|
```
|
||||||
|
|
||||||
|
### TypeScript Red Flags
|
||||||
|
```typescript
|
||||||
|
any // Should be typed
|
||||||
|
// eslint-disable // Why?
|
||||||
|
console.log // Debug left in?
|
||||||
|
useEffect(()=>{}, []) // Missing deps?
|
||||||
|
```
|
||||||
|
|
||||||
|
## What NOT to Review
|
||||||
|
|
||||||
|
- Formatting (formatters handle that)
|
||||||
|
- Personal style preferences
|
||||||
|
- Already-approved patterns
|
||||||
|
- Generated or vendored code
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. Read the full change before commenting
|
||||||
|
2. Understand intent before critiquing
|
||||||
|
3. Provide concrete fixes
|
||||||
|
4. Acknowledge what's done well
|
||||||
|
5. Prioritize feedback
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. Critique without suggesting solutions
|
||||||
|
2. Block on style preferences
|
||||||
|
3. Nitpick trivial issues
|
||||||
|
4. Default to minimal fixes when proper solutions exist
|
||||||
99
.claude/skills/feature-tracer/SKILL.md
Normal file
99
.claude/skills/feature-tracer/SKILL.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
name: feature-tracer
|
||||||
|
description: Trace a feature end-to-end across the codebase - find all services, workers, DB tables, and assess code quality.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature Tracer
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a systems detective who traces features across service boundaries. You follow data from entry point to storage and back, mapping every touchpoint.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **Evidence-Based**: Every claim backed by file:line references
|
||||||
|
- **Complete Path**: Trace read AND write paths, success AND failure
|
||||||
|
- **Quality Lens**: Assess test coverage, error handling, dead code at each stop
|
||||||
|
- **Honest Uncertainty**: State clearly what you couldn't trace
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
### 1. Clarify the Feature
|
||||||
|
- Feature name and what it does (1-2 sentences)
|
||||||
|
- One feature or multiple? Split if needed.
|
||||||
|
|
||||||
|
### 2. Discover Entry Points
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API handlers
|
||||||
|
grep -rn "[keyword]" --include="*.go" services/*/internal/
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
grep -rn "[keyword]" --include="*.tsx" --include="*.ts" apps/
|
||||||
|
|
||||||
|
# Workers
|
||||||
|
grep -rn "[keyword]" --include="*.go" workers/*/internal/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Trace Each Path
|
||||||
|
|
||||||
|
For each entry point:
|
||||||
|
1. Read the file
|
||||||
|
2. Find what it calls (service → repository → external)
|
||||||
|
3. Follow each call chain
|
||||||
|
4. Map DB tables touched
|
||||||
|
5. Note external dependencies
|
||||||
|
|
||||||
|
### 4. Assess Quality
|
||||||
|
|
||||||
|
For each traced file:
|
||||||
|
|
||||||
|
| Check | How |
|
||||||
|
|-------|-----|
|
||||||
|
| Has tests? | `ls [file]_test.go` |
|
||||||
|
| TODOs? | `grep -n "TODO\|FIXME" [file]` |
|
||||||
|
| Dead code? | `grep -rn "[function]" . \| wc -l` |
|
||||||
|
| Error handling? | `grep -n "if err" [file]` |
|
||||||
|
|
||||||
|
### 5. Step Back
|
||||||
|
|
||||||
|
Before finalizing:
|
||||||
|
- [ ] Traced both read AND write paths?
|
||||||
|
- [ ] Checked error/failure paths?
|
||||||
|
- [ ] Verified dead code claims with grep counts?
|
||||||
|
- [ ] Noted uncertainties?
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Feature Trace: [Name]
|
||||||
|
|
||||||
|
### Entry Points
|
||||||
|
| Layer | File | Function | Line |
|
||||||
|
|-------|------|----------|------|
|
||||||
|
|
||||||
|
### Execution Flow
|
||||||
|
[entry] → [service] → [repository] → [DB]
|
||||||
|
|
||||||
|
### Database
|
||||||
|
| Table | Operation | File |
|
||||||
|
|-------|-----------|------|
|
||||||
|
|
||||||
|
### Quality
|
||||||
|
| Category | Details |
|
||||||
|
|----------|---------|
|
||||||
|
| Good | [tested, well-structured] |
|
||||||
|
| Bad | [missing tests, TODOs] |
|
||||||
|
| Ugly | [debt, concerns] |
|
||||||
|
| Dead | [unused code with evidence] |
|
||||||
|
|
||||||
|
### Uncertainties
|
||||||
|
[What couldn't be traced and why]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- NEVER mark code as "dead" without grep evidence
|
||||||
|
- NEVER skip the step-back verification
|
||||||
|
- ALWAYS include file:line references
|
||||||
|
- ALWAYS note what you couldn't trace
|
||||||
247
.claude/skills/feature-verifier/SKILL.md
Normal file
247
.claude/skills/feature-verifier/SKILL.md
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
---
|
||||||
|
name: feature-verifier
|
||||||
|
description: Verify features work as intended through systematic investigation. Use when validating implementations, checking behavior matches expectations, or collecting evidence about how something actually works.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature Verifier
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a QA lead who doesn't trust "it works on my machine." You verify claims with evidence, distinguish between "code exists" and "feature works," and surface gaps between intention and reality.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **Evidence Over Assertion**: "It should work" ≠ "it works." Show proof.
|
||||||
|
- **Behavior Over Implementation**: Test what users see, not just what code does.
|
||||||
|
- **Adversarial Testing**: Try to break it. Find edge cases.
|
||||||
|
- **Complete Picture**: Investigate code, tests, docs, and actual behavior.
|
||||||
|
|
||||||
|
## Investigation Protocol
|
||||||
|
|
||||||
|
### Phase 1: Understand the Claim
|
||||||
|
|
||||||
|
Parse what's being verified:
|
||||||
|
|
||||||
|
1. **Feature description**: What should it do?
|
||||||
|
2. **Success criteria**: How do we know it works?
|
||||||
|
3. **Scope boundaries**: What's explicitly NOT included?
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Verification Target
|
||||||
|
|
||||||
|
**Feature:** [Name/description]
|
||||||
|
**Expected behavior:** [What should happen]
|
||||||
|
**Success criteria:**
|
||||||
|
- [ ] [Criterion 1]
|
||||||
|
- [ ] [Criterion 2]
|
||||||
|
**Out of scope:** [What we're NOT verifying]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Locate Evidence Sources
|
||||||
|
|
||||||
|
Find all relevant artifacts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find implementation
|
||||||
|
grep -r "[feature keywords]" --include="*.go" --include="*.ts" -l
|
||||||
|
|
||||||
|
# Find tests
|
||||||
|
find . -name "*test*" | xargs grep -l "[feature keywords]"
|
||||||
|
|
||||||
|
# Find docs
|
||||||
|
grep -r "[feature keywords]" --include="*.md" -l
|
||||||
|
|
||||||
|
# Find config
|
||||||
|
grep -r "[feature keywords]" --include="*.json" --include="*.yaml" --include="*.toml" -l
|
||||||
|
```
|
||||||
|
|
||||||
|
Catalog what exists:
|
||||||
|
|
||||||
|
| Source | Location | Status |
|
||||||
|
|--------|----------|--------|
|
||||||
|
| Implementation | `path/to/file.go` | Found/Missing |
|
||||||
|
| Unit tests | `path/to/test.go` | Found/Missing |
|
||||||
|
| Integration tests | `path/to/e2e.go` | Found/Missing |
|
||||||
|
| Documentation | `docs/feature.md` | Found/Missing |
|
||||||
|
| Config | `config/feature.yaml` | Found/Missing |
|
||||||
|
|
||||||
|
### Phase 3: Analyze Implementation
|
||||||
|
|
||||||
|
Read the implementation code. Document:
|
||||||
|
|
||||||
|
1. **Entry points**: Where does the feature start?
|
||||||
|
2. **Core logic**: What does it actually do?
|
||||||
|
3. **Exit points**: What are the possible outcomes?
|
||||||
|
4. **Error handling**: What happens when things fail?
|
||||||
|
5. **Dependencies**: What does it rely on?
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Implementation Analysis
|
||||||
|
|
||||||
|
**Entry point:** `file:line` - [description]
|
||||||
|
**Core logic:** [summary of what it does]
|
||||||
|
**Outcomes:**
|
||||||
|
- Success: [what happens]
|
||||||
|
- Failure: [what happens]
|
||||||
|
**Dependencies:** [list]
|
||||||
|
**Concerns:** [anything suspicious]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Analyze Test Coverage
|
||||||
|
|
||||||
|
Read existing tests. Document:
|
||||||
|
|
||||||
|
1. **What's tested**: Happy paths? Edge cases? Errors?
|
||||||
|
2. **What's NOT tested**: Gaps in coverage
|
||||||
|
3. **Test quality**: Are tests meaningful or superficial?
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Test Analysis
|
||||||
|
|
||||||
|
**Covered scenarios:**
|
||||||
|
- [x] [Scenario 1] - `test_file:line`
|
||||||
|
- [x] [Scenario 2] - `test_file:line`
|
||||||
|
|
||||||
|
**Missing scenarios:**
|
||||||
|
- [ ] [Gap 1] - Why it matters
|
||||||
|
- [ ] [Gap 2] - Why it matters
|
||||||
|
|
||||||
|
**Test quality concerns:**
|
||||||
|
- [Concern 1]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: Verify Actual Behavior
|
||||||
|
|
||||||
|
If possible, run the feature:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run relevant tests
|
||||||
|
go test -v ./path/to/package -run TestFeature
|
||||||
|
|
||||||
|
# Run the actual feature if applicable
|
||||||
|
# Check logs, outputs, side effects
|
||||||
|
```
|
||||||
|
|
||||||
|
Document observed vs expected:
|
||||||
|
|
||||||
|
| Criterion | Expected | Observed | Status |
|
||||||
|
|-----------|----------|----------|--------|
|
||||||
|
| [Criterion 1] | [Expected] | [Actual] | ✓/✗/? |
|
||||||
|
| [Criterion 2] | [Expected] | [Actual] | ✓/✗/? |
|
||||||
|
|
||||||
|
### Phase 6: Compile Findings
|
||||||
|
|
||||||
|
Synthesize everything into a verification report:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Feature Verification Report
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
**Feature:** [Name]
|
||||||
|
**Verdict:** [VERIFIED / PARTIALLY VERIFIED / NOT VERIFIED / NEEDS INVESTIGATION]
|
||||||
|
**Confidence:** [High/Medium/Low]
|
||||||
|
|
||||||
|
### Evidence Matrix
|
||||||
|
|
||||||
|
| Aspect | Status | Evidence |
|
||||||
|
|--------|--------|----------|
|
||||||
|
| Code exists | ✓/✗ | `file:line` |
|
||||||
|
| Tests exist | ✓/✗ | `test:line` |
|
||||||
|
| Tests pass | ✓/✗ | Output |
|
||||||
|
| Behavior correct | ✓/✗ | [Proof] |
|
||||||
|
| Docs accurate | ✓/✗ | [Comparison] |
|
||||||
|
|
||||||
|
### Findings
|
||||||
|
|
||||||
|
#### What Works
|
||||||
|
- [Finding 1] - Evidence: [proof]
|
||||||
|
- [Finding 2] - Evidence: [proof]
|
||||||
|
|
||||||
|
#### Issues Found
|
||||||
|
- **[Issue 1]**: [Description]
|
||||||
|
- Location: `file:line`
|
||||||
|
- Impact: [Severity]
|
||||||
|
- Evidence: [What we saw]
|
||||||
|
|
||||||
|
#### Gaps Discovered
|
||||||
|
- [Gap 1]: [What's missing]
|
||||||
|
- [Gap 2]: [What's missing]
|
||||||
|
|
||||||
|
### Recommendations
|
||||||
|
|
||||||
|
**To fully verify:**
|
||||||
|
1. [Action needed]
|
||||||
|
2. [Action needed]
|
||||||
|
|
||||||
|
**To improve:**
|
||||||
|
1. [Suggestion]
|
||||||
|
2. [Suggestion]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step Back: Challenge Your Verification
|
||||||
|
|
||||||
|
Before finalizing the report, ask:
|
||||||
|
|
||||||
|
### 1. False Confidence
|
||||||
|
> "Am I declaring 'verified' because code exists, or because it actually works?"
|
||||||
|
|
||||||
|
- Did I see the feature execute?
|
||||||
|
- Did I see correct output?
|
||||||
|
- Or did I just read code that looks right?
|
||||||
|
|
||||||
|
### 2. Missing Scenarios
|
||||||
|
> "What would break this that I haven't tested?"
|
||||||
|
|
||||||
|
- Edge cases?
|
||||||
|
- Error conditions?
|
||||||
|
- Concurrent access?
|
||||||
|
- Resource exhaustion?
|
||||||
|
|
||||||
|
### 3. Documentation Drift
|
||||||
|
> "Do the docs match reality?"
|
||||||
|
|
||||||
|
- Is the documented behavior what actually happens?
|
||||||
|
- Are there undocumented behaviors?
|
||||||
|
- Are there documented features that don't exist?
|
||||||
|
|
||||||
|
### 4. The Skeptical User
|
||||||
|
> "Would someone using this feature agree it works?"
|
||||||
|
|
||||||
|
- From their perspective, not the code's perspective
|
||||||
|
- Would they hit the issues I found?
|
||||||
|
- Are there UX problems I'm ignoring because "the code works"?
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. Read implementation before declaring "it works"
|
||||||
|
2. Run tests and observe output
|
||||||
|
3. Document evidence for every claim
|
||||||
|
4. Note confidence level for each finding
|
||||||
|
5. Identify gaps, not just confirm existence
|
||||||
|
6. Propose concrete next steps
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. Declare "verified" because tests exist (they might not run)
|
||||||
|
2. Skip reading error handling paths
|
||||||
|
3. Ignore missing test coverage
|
||||||
|
4. Assume docs are accurate
|
||||||
|
5. Overlook edge cases because happy path works
|
||||||
|
6. Provide verdict without evidence
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- NEVER say "verified" without executing tests or seeing behavior
|
||||||
|
- NEVER ignore test failures as "probably fine"
|
||||||
|
- ALWAYS distinguish "code exists" from "feature works"
|
||||||
|
- ALWAYS provide evidence matrix
|
||||||
|
- ALWAYS include recommendations
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
Final output should be the Verification Report from Phase 6, with:
|
||||||
|
- Clear verdict and confidence
|
||||||
|
- Evidence matrix showing what was checked
|
||||||
|
- Specific findings with file:line references
|
||||||
|
- Actionable recommendations
|
||||||
78
.claude/skills/ideate/SKILL.md
Normal file
78
.claude/skills/ideate/SKILL.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
name: ideate
|
||||||
|
description: Deep collaborative thinking about a problem before committing to a solution. Explore the space, challenge assumptions, think together.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ideate
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a thinking partner who helps explore problems deeply before jumping to solutions. You bring rigor, fresh perspectives, and honest challenge.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **Understand Before Solving**: Read code, gather context, then think
|
||||||
|
- **Multiple Perspectives**: Consult different viewpoints
|
||||||
|
- **Honest Challenge**: Surface assumptions, argue the opposite
|
||||||
|
- **Collaborate, Don't Deliver**: This is dialogue, not a report
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
### 1. Understand the Problem
|
||||||
|
|
||||||
|
- **User's words:** [exact quote]
|
||||||
|
- **Your interpretation:** [what you think they mean]
|
||||||
|
- **Scope:** [what's in/out of bounds]
|
||||||
|
|
||||||
|
If your interpretation differs, **ask**.
|
||||||
|
|
||||||
|
### 2. Gather Context
|
||||||
|
|
||||||
|
Read actual code. Find:
|
||||||
|
- What provides input?
|
||||||
|
- What consumes output?
|
||||||
|
- What's similar in the codebase?
|
||||||
|
|
||||||
|
### 3. Explore the Solution Space
|
||||||
|
|
||||||
|
Always include 3-4 options:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Option A: [Name]
|
||||||
|
- Approach: [how]
|
||||||
|
- Pros: [why good]
|
||||||
|
- Cons: [why risky]
|
||||||
|
- Assumption: [what must be true]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always include "do nothing" as an option.**
|
||||||
|
|
||||||
|
### 4. Step Back
|
||||||
|
|
||||||
|
Before presenting, challenge:
|
||||||
|
- **Assumptions:** What am I assuming that's unverified?
|
||||||
|
- **Fresh eyes:** Would someone new agree with my framing?
|
||||||
|
- **Skeptic:** What would a disagreer say?
|
||||||
|
- **Missing:** Whose perspective am I ignoring?
|
||||||
|
- **Reversal:** Can I argue for the opposite?
|
||||||
|
|
||||||
|
### 5. Think Out Loud
|
||||||
|
|
||||||
|
Share:
|
||||||
|
- What you learned
|
||||||
|
- What surprised you
|
||||||
|
- The core tension/tradeoff
|
||||||
|
- Where you're leaning (starting point, not conclusion)
|
||||||
|
- Questions for them
|
||||||
|
|
||||||
|
### 6. Iterate
|
||||||
|
|
||||||
|
Listen, adjust, drill deeper, repeat.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- NEVER form opinions without reading code first
|
||||||
|
- NEVER skip "do nothing" option
|
||||||
|
- NEVER present conclusions without showing reasoning
|
||||||
|
- ALWAYS surface assumptions explicitly
|
||||||
|
- ALWAYS leave room for user input
|
||||||
286
.claude/skills/knowledge-librarian/SKILL.md
Normal file
286
.claude/skills/knowledge-librarian/SKILL.md
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
---
|
||||||
|
name: knowledge-librarian
|
||||||
|
description: Organize and store learnings as discoverable institutional memory. Use after learning something worth remembering for future reference.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Knowledge Librarian
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a librarian who transforms ephemeral conversation knowledge into permanent, discoverable institutional memory. You categorize, clean, compress, and catalog - ensuring nothing valuable is lost and everything can be found.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **Compression Over Verbosity**: Store the essence, not the conversation.
|
||||||
|
- **Categorization is Discovery**: Proper taxonomy makes knowledge findable.
|
||||||
|
- **Provenance Matters**: Know where knowledge came from and when.
|
||||||
|
- **Freshness Decays**: Knowledge can become stale. Track when learned.
|
||||||
|
- **One Fact, One Place**: Don't duplicate; reference.
|
||||||
|
- **Searchable by Question**: Organize by "what would someone ask?"
|
||||||
|
|
||||||
|
## Knowledge Categories
|
||||||
|
|
||||||
|
| Category | What Goes Here | Example |
|
||||||
|
|----------|---------------|---------|
|
||||||
|
| `patterns/` | How we do things | "How we handle API errors" |
|
||||||
|
| `decisions/` | Why we chose X over Y | "Why we use slog not logrus" |
|
||||||
|
| `gotchas/` | Non-obvious pitfalls | "context.WithTimeout requires defer cancel()" |
|
||||||
|
| `how-to/` | Step-by-step procedures | "How to add a new API endpoint" |
|
||||||
|
| `architecture/` | System design facts | "How the work queue flows" |
|
||||||
|
| `debugging/` | How to diagnose issues | "How to debug pod execution" |
|
||||||
|
| `conventions/` | Naming, style, standards | "Error type naming convention" |
|
||||||
|
| `integrations/` | External system knowledge | "How we talk to PostgreSQL" |
|
||||||
|
|
||||||
|
## Storage Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
ai-lookup/
|
||||||
|
├── index.md # Master catalog
|
||||||
|
├── patterns/
|
||||||
|
│ ├── index.md # Pattern catalog
|
||||||
|
│ ├── error-handling.md
|
||||||
|
│ └── api-response-format.md
|
||||||
|
├── decisions/
|
||||||
|
│ ├── index.md
|
||||||
|
│ └── slog-over-logrus.md
|
||||||
|
├── gotchas/
|
||||||
|
│ ├── index.md
|
||||||
|
│ └── context-cancel.md
|
||||||
|
├── how-to/
|
||||||
|
│ ├── index.md
|
||||||
|
│ └── add-api-endpoint.md
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Memory Entry Format
|
||||||
|
|
||||||
|
Each memory entry should be:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
category: [patterns|decisions|gotchas|how-to|architecture|debugging|conventions|integrations]
|
||||||
|
title: [Short, searchable title]
|
||||||
|
learned: [YYYY-MM-DD]
|
||||||
|
source: [conversation|investigation|incident|documentation]
|
||||||
|
confidence: [high|medium|low]
|
||||||
|
related: [list of related entries]
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Title]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
[2-3 sentence TL;DR]
|
||||||
|
|
||||||
|
## Details
|
||||||
|
[The actual knowledge, compressed but complete]
|
||||||
|
|
||||||
|
## Example
|
||||||
|
[Concrete example if applicable]
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
- [Related entry](../category/entry.md)
|
||||||
|
- [External doc](../../docs/foo.md)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
### Phase 1: Extract the Learning
|
||||||
|
|
||||||
|
From the conversation/work, identify:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Knowledge to Remember
|
||||||
|
|
||||||
|
**What was learned:** [The core insight]
|
||||||
|
**Why it matters:** [When would someone need this]
|
||||||
|
**How it was learned:** [Context - investigation, bug, discussion]
|
||||||
|
**Confidence:** [How sure are we this is right]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Categorize with Librarian
|
||||||
|
|
||||||
|
Consult the project's librarian agent (if exists) or categorize:
|
||||||
|
|
||||||
|
Questions to determine category:
|
||||||
|
- Is this "how we do X"? → `patterns/`
|
||||||
|
- Is this "why we chose X"? → `decisions/`
|
||||||
|
- Is this "watch out for X"? → `gotchas/`
|
||||||
|
- Is this "to do X, follow these steps"? → `how-to/`
|
||||||
|
- Is this "how X works in our system"? → `architecture/`
|
||||||
|
- Is this "to debug X, check Y"? → `debugging/`
|
||||||
|
- Is this "we name things X way"? → `conventions/`
|
||||||
|
- Is this "X external system works like Y"? → `integrations/`
|
||||||
|
|
||||||
|
### Phase 3: Check for Duplicates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search existing knowledge base
|
||||||
|
grep -ri "[key terms]" ai-lookup/
|
||||||
|
|
||||||
|
# Check if this updates existing entry
|
||||||
|
ls ai-lookup/[category]/
|
||||||
|
```
|
||||||
|
|
||||||
|
If duplicate:
|
||||||
|
- Update existing entry (add new information)
|
||||||
|
- Note the update date
|
||||||
|
- Don't create new file
|
||||||
|
|
||||||
|
### Phase 4: Compress and Clean
|
||||||
|
|
||||||
|
Transform conversation knowledge into library entry:
|
||||||
|
|
||||||
|
**From conversation:**
|
||||||
|
> "So I was debugging this for 3 hours and finally figured out that when you use context.WithTimeout, you MUST call the cancel function or you leak resources. This bit me because I forgot the defer cancel()..."
|
||||||
|
|
||||||
|
**Compressed to:**
|
||||||
|
```markdown
|
||||||
|
# context.WithTimeout Requires defer cancel()
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
`context.WithTimeout` returns a cancel function that MUST be called (typically with `defer`) or resources will leak. The cancel function releases resources associated with the context.
|
||||||
|
|
||||||
|
## Details
|
||||||
|
The WithTimeout function signature returns both a context and a cancel function:
|
||||||
|
- Always capture and call the cancel function
|
||||||
|
- Use defer immediately after creating the context
|
||||||
|
- Failure to cancel leaks goroutines and memory
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```go
|
||||||
|
// BAD - leaks resources
|
||||||
|
ctx, _ := context.WithTimeout(parentCtx, 5*time.Second)
|
||||||
|
|
||||||
|
// GOOD - always defer cancel
|
||||||
|
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: Write Entry
|
||||||
|
|
||||||
|
Create the file in appropriate location:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ai-lookup/[category]
|
||||||
|
```
|
||||||
|
|
||||||
|
Write with full format (frontmatter + content).
|
||||||
|
|
||||||
|
### Phase 6: Update Indexes
|
||||||
|
|
||||||
|
Update `ai-lookup/[category]/index.md`:
|
||||||
|
```diff
|
||||||
|
+ - [Entry Title](entry-name.md) - Brief description
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `ai-lookup/index.md` if new category:
|
||||||
|
```diff
|
||||||
|
+ ## [Category]
|
||||||
|
+ - [Entry](category/entry.md) - Description
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 7: Verify Discoverability
|
||||||
|
|
||||||
|
Can this be found by:
|
||||||
|
1. Browsing the index?
|
||||||
|
2. Searching for key terms?
|
||||||
|
3. Following related links?
|
||||||
|
|
||||||
|
## Compression Guidelines
|
||||||
|
|
||||||
|
| Verbose | Compressed |
|
||||||
|
|---------|-----------|
|
||||||
|
| Long explanation of discovery process | Just the fact |
|
||||||
|
| "I think maybe..." hedging | Direct statement with confidence rating |
|
||||||
|
| Multiple examples | One clear example |
|
||||||
|
| Conversation back-and-forth | Clean Q&A or just answer |
|
||||||
|
| Implementation details that change | Concept that's stable |
|
||||||
|
|
||||||
|
## What to Remember vs Not
|
||||||
|
|
||||||
|
**Remember:**
|
||||||
|
- Non-obvious behaviors (gotchas)
|
||||||
|
- Decisions and their rationale
|
||||||
|
- Patterns that recur
|
||||||
|
- Debugging strategies that worked
|
||||||
|
- Integration quirks
|
||||||
|
- Performance insights
|
||||||
|
|
||||||
|
**Don't Remember:**
|
||||||
|
- Obvious/documented facts (link to docs instead)
|
||||||
|
- Temporary workarounds (track as tech debt)
|
||||||
|
- One-off fixes (just fix it)
|
||||||
|
- Personal preferences (unless team convention)
|
||||||
|
- Speculation (wait until confirmed)
|
||||||
|
|
||||||
|
## Freshness Management
|
||||||
|
|
||||||
|
Add `last_verified` to entries that might become stale:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
learned: 2024-01-15
|
||||||
|
last_verified: 2024-06-20
|
||||||
|
stale_after: 2025-01-15 # optional
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
When reading old entries, check if still accurate.
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. Compress to essence (not conversation transcript)
|
||||||
|
2. Categorize thoughtfully (enables discovery)
|
||||||
|
3. Include concrete examples
|
||||||
|
4. Link to related entries
|
||||||
|
5. Track provenance (when, how learned)
|
||||||
|
6. Update existing entries (don't duplicate)
|
||||||
|
7. Verify discoverability
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. Store conversation verbatim (compress!)
|
||||||
|
2. Duplicate existing knowledge (update instead)
|
||||||
|
3. Store temporary information (use todo/tickets)
|
||||||
|
4. Forget provenance (when/how learned)
|
||||||
|
5. Skip indexes (discovery depends on it)
|
||||||
|
6. Store speculation as fact (note confidence)
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- ALWAYS include frontmatter with category, learned date, confidence
|
||||||
|
- ALWAYS update indexes when adding entries
|
||||||
|
- ALWAYS compress conversation to essence
|
||||||
|
- NEVER duplicate existing entries (update them)
|
||||||
|
- NEVER store without checking for existing knowledge
|
||||||
|
- ALWAYS include at least one example for patterns/gotchas
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Remembered: [Title]
|
||||||
|
|
||||||
|
### Classification
|
||||||
|
- **Category:** [category]
|
||||||
|
- **Confidence:** [high/medium/low]
|
||||||
|
- **Source:** [how learned]
|
||||||
|
|
||||||
|
### Stored At
|
||||||
|
`ai-lookup/[category]/[filename].md`
|
||||||
|
|
||||||
|
### Entry Preview
|
||||||
|
[First few lines of the created entry]
|
||||||
|
|
||||||
|
### Indexes Updated
|
||||||
|
- `ai-lookup/index.md` - [added/updated]
|
||||||
|
- `ai-lookup/[category]/index.md` - [added/updated]
|
||||||
|
|
||||||
|
### Related Entries
|
||||||
|
- [Existing related knowledge]
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- [ ] Searchable by key terms
|
||||||
|
- [ ] Indexed in category
|
||||||
|
- [ ] Linked to related entries
|
||||||
|
```
|
||||||
263
.claude/skills/logging-standards/SKILL.md
Normal file
263
.claude/skills/logging-standards/SKILL.md
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
---
|
||||||
|
name: logging-standards
|
||||||
|
description: Logging infrastructure standards for slack-auth-1770277653 - structured logging, trace propagation, error handling, frontend/backend consistency.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Logging Standards
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You enforce consistent, actionable logging across all services and apps in slack-auth-1770277653. Every log entry is structured, traceable, and tells the story of what happened.
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
1. **Log once at the boundary** - handlers/workers log the result; internal functions return errors
|
||||||
|
2. **Every log has context** - trace_id, request_id, service, and component on every line
|
||||||
|
3. **Errors are actionable** - include what failed, why, and what to do about it
|
||||||
|
4. **Structured always** - JSON in production, text in development; never fmt.Println
|
||||||
|
5. **No sensitive data** - never log passwords, tokens, PII, or full request bodies
|
||||||
|
|
||||||
|
## Backend (Go + slog)
|
||||||
|
|
||||||
|
### Logger Creation
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Services get a logger from pkg/app - it's pre-configured
|
||||||
|
app := app.New("auth-api", app.WithDefaultPort(8001))
|
||||||
|
logger := app.Logger()
|
||||||
|
|
||||||
|
// Workers create their own context
|
||||||
|
ctx = logging.WorkerContext(ctx, "email-sender")
|
||||||
|
logger := logging.FromContext(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context Propagation
|
||||||
|
|
||||||
|
The middleware stack automatically sets up context:
|
||||||
|
|
||||||
|
```
|
||||||
|
RequestID() -> Tracing() -> RequestLogger() -> Recoverer()
|
||||||
|
```
|
||||||
|
|
||||||
|
Every request gets:
|
||||||
|
- `request_id` - unique per request (from X-Request-ID header or generated)
|
||||||
|
- `trace_id` - unique per trace (from X-Trace-ID / X-Cloud-Trace-Context or generated)
|
||||||
|
|
||||||
|
Retrieve in handlers:
|
||||||
|
```go
|
||||||
|
logger := logging.FromContext(r.Context())
|
||||||
|
logger.Info("user created", "user_id", user.ID)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Logging Pattern
|
||||||
|
|
||||||
|
```go
|
||||||
|
// GOOD - log at handler boundary with context
|
||||||
|
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user, err := h.service.Create(r.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
logging.FromContext(r.Context()).Error("create user failed",
|
||||||
|
"error", err,
|
||||||
|
"email", req.Email,
|
||||||
|
)
|
||||||
|
httpresponse.InternalError(w, r, "failed to create user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpresponse.Created(w, r, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD - service returns error, does not log
|
||||||
|
func (s *Service) Create(ctx context.Context, input CreateInput) (*User, error) {
|
||||||
|
user, err := s.repo.Insert(ctx, input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("insert user: %w", err)
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BAD - logging inside service AND returning error (double-logged)
|
||||||
|
func (s *Service) Create(ctx context.Context, input CreateInput) (*User, error) {
|
||||||
|
user, err := s.repo.Insert(ctx, input)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to insert", "error", err) // DON'T DO THIS
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log Levels
|
||||||
|
|
||||||
|
| Level | When |
|
||||||
|
|-------|------|
|
||||||
|
| **Error** | Something failed and needs attention (5xx, unrecoverable) |
|
||||||
|
| **Warn** | Something unexpected but handled (4xx, retries, fallbacks) |
|
||||||
|
| **Info** | Normal operations (request completed, job processed, startup) |
|
||||||
|
| **Debug** | Diagnostic details (SQL queries, cache hits, retry attempts) |
|
||||||
|
|
||||||
|
### Service-to-Service
|
||||||
|
|
||||||
|
The `httpclient` package automatically propagates both `X-Request-ID` and `X-Trace-ID` headers on outgoing requests:
|
||||||
|
|
||||||
|
```go
|
||||||
|
client := httpclient.New(httpclient.Config{Timeout: 5 * time.Second})
|
||||||
|
resp, err := client.Do(req) // trace_id and request_id propagated automatically
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Envelope
|
||||||
|
|
||||||
|
Every API response includes trace context in the meta field:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {},
|
||||||
|
"meta": {
|
||||||
|
"request_id": "abc-123",
|
||||||
|
"trace_id": "def-456",
|
||||||
|
"timestamp": "2024-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontend (TypeScript + @slack-auth-1770277653/logger)
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createLogger, installGlobalHandlers } from '@slack-auth-1770277653/logger';
|
||||||
|
|
||||||
|
export const logger = createLogger({
|
||||||
|
level: import.meta.env.DEV ? 'debug' : 'info',
|
||||||
|
service: 'dashboard',
|
||||||
|
endpoint: '/api/logs', // optional: send logs to backend
|
||||||
|
});
|
||||||
|
|
||||||
|
installGlobalHandlers(logger);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Simple logging
|
||||||
|
logger.info('page loaded', { route: '/dashboard' });
|
||||||
|
|
||||||
|
// Error logging with Error objects
|
||||||
|
try {
|
||||||
|
await fetchData();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('fetch failed', err, { endpoint: '/api/data' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child logger with component context
|
||||||
|
const authLogger = logger.withContext({ component: 'auth' });
|
||||||
|
authLogger.info('login attempt', { method: 'oauth' });
|
||||||
|
```
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **Batching**: Logs are buffered and sent in batches (default: 20 entries or 5s)
|
||||||
|
- **Offline resilience**: Uses `navigator.sendBeacon` for reliable delivery during page unload
|
||||||
|
- **Global handlers**: Captures uncaught exceptions and unhandled promise rejections
|
||||||
|
- **Zero-crash**: Logging failures never break the app
|
||||||
|
|
||||||
|
## Workers & Cron Jobs
|
||||||
|
|
||||||
|
Workers don't have HTTP context. Use `WorkerContext` to generate trace IDs:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (w *OrderProcessor) Handle(ctx context.Context, job queue.Job) error {
|
||||||
|
ctx = logging.WorkerContext(ctx, "order-processor")
|
||||||
|
logger := logging.FromContext(ctx)
|
||||||
|
|
||||||
|
logger.Info("processing order", "order_id", job.Payload.OrderID)
|
||||||
|
|
||||||
|
if err := w.process(ctx, job); err != nil {
|
||||||
|
logger.Error("order processing failed",
|
||||||
|
"error", err,
|
||||||
|
"order_id", job.Payload.OrderID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("order processed", "order_id", job.Payload.OrderID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Wrapping Patterns
|
||||||
|
|
||||||
|
### Standard wrap (add context)
|
||||||
|
```go
|
||||||
|
return fmt.Errorf("insert user: %w", err)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sentinel + detail wrap (Go 1.20+)
|
||||||
|
When a handler needs to classify errors for HTTP status mapping, wrap both a sentinel and detail:
|
||||||
|
```go
|
||||||
|
// Service returns a matchable sentinel WITH the detail error
|
||||||
|
return fmt.Errorf("%w: %w", domain.ErrInvalidCommand, err)
|
||||||
|
|
||||||
|
// Handler matches sentinel → 400, otherwise → 500
|
||||||
|
if errors.Is(err, domain.ErrInvalidCommand) {
|
||||||
|
httpresponse.BadRequest(w, r, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Both errors are matchable via `errors.Is()`. This is NOT an anti-pattern.
|
||||||
|
|
||||||
|
### When to use which
|
||||||
|
| Pattern | Use when |
|
||||||
|
|---------|----------|
|
||||||
|
| `fmt.Errorf("context: %w", err)` | Adding operation context |
|
||||||
|
| `fmt.Errorf("%w: %w", sentinel, err)` | Handler needs to classify error type |
|
||||||
|
| `fmt.Errorf("%w: detail string", sentinel)` | Sentinel + static detail (no inner error) |
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
| Don't | Do Instead |
|
||||||
|
|-------|-----------|
|
||||||
|
| `fmt.Println("error:", err)` | `logger.Error("description", "error", err)` |
|
||||||
|
| `log.Fatal(err)` | `logger.Error(...)` + graceful shutdown |
|
||||||
|
| Log in service AND handler | Log once at boundary, return errors |
|
||||||
|
| `logger.Info("password=" + pw)` | Never log credentials or PII |
|
||||||
|
| `logger.Error(err.Error())` | `logger.Error("what failed", "error", err)` |
|
||||||
|
| Ignore returned errors | Wrap and return: `fmt.Errorf("context: %w", err)` |
|
||||||
|
| `&http.Client{}` (no timeout) | `&http.Client{Timeout: 30 * time.Second}` |
|
||||||
|
| `http.Get(url)` (default client) | Use `httpclient.Get(ctx, url)` from pkg/httpclient |
|
||||||
|
|
||||||
|
## HTTP Client Rules
|
||||||
|
|
||||||
|
Every `http.Client` must have an explicit `Timeout`. A bare `&http.Client{}` can hang indefinitely.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// GOOD - explicit timeout
|
||||||
|
client := &http.Client{Timeout: 30 * time.Second}
|
||||||
|
|
||||||
|
// GOOD - use the shared httpclient package (has retries + trace propagation)
|
||||||
|
client := httpclient.New(httpclient.Config{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
MaxRetries: 3,
|
||||||
|
})
|
||||||
|
resp, err := client.Do(req) // propagates trace_id and request_id
|
||||||
|
|
||||||
|
// BAD - no timeout, can hang forever
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
// BAD - uses http.DefaultClient (no timeout)
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
When reviewing logging in a PR:
|
||||||
|
|
||||||
|
- [ ] Every handler logs errors before returning error responses
|
||||||
|
- [ ] Services return errors, don't log them
|
||||||
|
- [ ] No sensitive data in log output
|
||||||
|
- [ ] trace_id and request_id propagated on service-to-service calls
|
||||||
|
- [ ] Workers use `logging.WorkerContext` for correlation
|
||||||
|
- [ ] Frontend apps initialize logger and global handlers
|
||||||
|
- [ ] Error logs include enough context to debug (IDs, operation name)
|
||||||
|
- [ ] Log levels appropriate (not everything is Error)
|
||||||
|
- [ ] All `http.Client` instances have explicit `Timeout` set
|
||||||
|
- [ ] Service-to-service calls use `pkg/httpclient` (retries + trace propagation)
|
||||||
305
.claude/skills/microservices/SKILL.md
Normal file
305
.claude/skills/microservices/SKILL.md
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
---
|
||||||
|
name: microservices
|
||||||
|
description: Inter-service communication patterns using pkg/svc for service discovery and circuit breaker protection. Use when implementing service-to-service calls.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Microservices Communication
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a distributed systems engineer who understands the pitfalls of microservice communication. You prioritize resilience, observability, and graceful degradation over feature velocity.
|
||||||
|
|
||||||
|
## Service Discovery
|
||||||
|
|
||||||
|
Services discover siblings via environment variables injected automatically by the platform.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
When a component is deployed, it receives env vars for all sibling services:
|
||||||
|
- `AUTH_SVC_URL=http://myproject-auth-svc:8001`
|
||||||
|
- `CHAT_SVC_URL=http://myproject-chat-svc:8002`
|
||||||
|
|
||||||
|
The naming convention: `{COMPONENT_NAME}_URL` where `COMPONENT_NAME` is UPPER_SNAKE_CASE.
|
||||||
|
|
||||||
|
### Using pkg/svc
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "git.threesix.ai/jordan/slack-auth-1770277653/pkg/svc"
|
||||||
|
|
||||||
|
// Simple lookup
|
||||||
|
url := svc.ServiceURL("auth-svc")
|
||||||
|
if url == "" {
|
||||||
|
// Service not configured
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check availability
|
||||||
|
if svc.ServiceConfigured("auth-svc") {
|
||||||
|
// Safe to call
|
||||||
|
}
|
||||||
|
|
||||||
|
// For required dependencies (panics if missing)
|
||||||
|
url := svc.MustServiceURL("auth-svc")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Service Client
|
||||||
|
|
||||||
|
Use `svc.NewClient()` for a pre-configured HTTP client with circuit breaker protection.
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "git.threesix.ai/jordan/slack-auth-1770277653/pkg/svc"
|
||||||
|
|
||||||
|
// Create client (returns error if service not configured)
|
||||||
|
authClient, err := svc.NewClient("auth-svc")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("auth service unavailable: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make requests
|
||||||
|
resp, err := authClient.Get(ctx, "/users/123")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, httpclient.ErrCircuitOpen) {
|
||||||
|
// Circuit breaker is open - service is unhealthy
|
||||||
|
return ErrAuthServiceDown
|
||||||
|
}
|
||||||
|
return fmt.Errorf("auth request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// JSON POST
|
||||||
|
resp, err := authClient.Post(ctx, "/validate", ValidateRequest{Token: token})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Configuration
|
||||||
|
|
||||||
|
```go
|
||||||
|
client, err := svc.NewClientWithConfig("auth-svc", svc.ClientConfig{
|
||||||
|
Timeout: 5 * time.Second, // Shorter timeout for fast-fail
|
||||||
|
MaxRetries: 2, // Fewer retries
|
||||||
|
CircuitBreaker: &httpclient.CircuitBreakerConfig{
|
||||||
|
FailureThreshold: 3, // Open after 3 failures
|
||||||
|
ResetTimeout: 15 * time.Second,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Circuit Breaker
|
||||||
|
|
||||||
|
The circuit breaker prevents cascading failures by failing fast when a service is unhealthy.
|
||||||
|
|
||||||
|
### States
|
||||||
|
|
||||||
|
| State | Behavior |
|
||||||
|
|-------|----------|
|
||||||
|
| **Closed** | Normal operation, requests pass through |
|
||||||
|
| **Open** | Blocks all requests, returns `ErrCircuitOpen` immediately |
|
||||||
|
| **Half-Open** | Allows one test request to check if service recovered |
|
||||||
|
|
||||||
|
### Default Thresholds
|
||||||
|
|
||||||
|
- Opens after 5 consecutive failures
|
||||||
|
- Waits 30s before attempting recovery (half-open)
|
||||||
|
- Closes after one successful request in half-open state
|
||||||
|
|
||||||
|
### What Affects Circuit State
|
||||||
|
|
||||||
|
The circuit breaker tracks **transient failures** only:
|
||||||
|
|
||||||
|
| Response | Affects Circuit? | Reason |
|
||||||
|
|----------|-----------------|--------|
|
||||||
|
| HTTP 2xx/3xx | ✅ RecordSuccess | Service is healthy |
|
||||||
|
| HTTP 5xx | ✅ RecordFailure | Server error - transient |
|
||||||
|
| HTTP 429 | ✅ RecordFailure | Rate limited - transient |
|
||||||
|
| HTTP 4xx (except 429) | ❌ No effect | Client error - not service's fault |
|
||||||
|
| Network error | ✅ RecordFailure | Connection failed |
|
||||||
|
| Context cancelled | ❌ No effect | User/caller initiated |
|
||||||
|
| Timeout | ✅ RecordFailure | Service too slow |
|
||||||
|
|
||||||
|
**Key insight:** 4xx responses (bad requests, not found, unauthorized) don't trip the circuit because they indicate a problem with the request, not the service. A service returning 400s is still "healthy" from a circuit breaker perspective.
|
||||||
|
|
||||||
|
### Handling Circuit Open
|
||||||
|
|
||||||
|
```go
|
||||||
|
resp, err := authClient.Get(ctx, "/users/123")
|
||||||
|
if errors.Is(err, httpclient.ErrCircuitOpen) {
|
||||||
|
// Option 1: Return degraded response
|
||||||
|
return CachedUserData(userID)
|
||||||
|
|
||||||
|
// Option 2: Propagate as service unavailable
|
||||||
|
return nil, ErrServiceTemporarilyUnavailable
|
||||||
|
|
||||||
|
// Option 3: Use fallback service
|
||||||
|
return fallbackClient.Get(ctx, "/users/123")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
|
||||||
|
### Initialization Pattern
|
||||||
|
|
||||||
|
Initialize service clients at startup, not on-demand:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Server struct {
|
||||||
|
authClient *svc.Client
|
||||||
|
chatClient *svc.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer() (*Server, error) {
|
||||||
|
authClient, err := svc.NewClient("auth-svc")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("auth service required: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional dependency - check but don't fail
|
||||||
|
var chatClient *svc.Client
|
||||||
|
if svc.ServiceConfigured("chat-svc") {
|
||||||
|
chatClient, _ = svc.NewClient("chat-svc")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Server{
|
||||||
|
authClient: authClient,
|
||||||
|
chatClient: chatClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Decoding
|
||||||
|
|
||||||
|
```go
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := authClient.Get(ctx, "/users/123")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := svc.DecodeResponse[User](resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decode user: %w", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Graceful Degradation
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *Server) GetUserProfile(ctx context.Context, userID string) (*Profile, error) {
|
||||||
|
// Required call
|
||||||
|
user, err := s.fetchUser(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
profile := &Profile{User: user}
|
||||||
|
|
||||||
|
// Optional enrichment - don't fail if chat service is down
|
||||||
|
if s.chatClient != nil {
|
||||||
|
messages, err := s.fetchRecentMessages(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("failed to fetch messages", "error", err)
|
||||||
|
// Continue without messages
|
||||||
|
} else {
|
||||||
|
profile.RecentMessages = messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
### Hardcoded URLs
|
||||||
|
|
||||||
|
```go
|
||||||
|
// BAD: Hardcoded URLs break when services move
|
||||||
|
client := httpclient.New(httpclient.Config{})
|
||||||
|
resp, err := client.Get(ctx, "http://auth-svc:8001/users")
|
||||||
|
|
||||||
|
// GOOD: Use service discovery
|
||||||
|
authClient, _ := svc.NewClient("auth-svc")
|
||||||
|
resp, err := authClient.Get(ctx, "/users")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ignoring Circuit Breaker Errors
|
||||||
|
|
||||||
|
```go
|
||||||
|
// BAD: Retrying forever when circuit is open
|
||||||
|
for {
|
||||||
|
resp, err := authClient.Get(ctx, "/users")
|
||||||
|
if err != nil {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Detect circuit open and handle gracefully
|
||||||
|
resp, err := authClient.Get(ctx, "/users")
|
||||||
|
if errors.Is(err, httpclient.ErrCircuitOpen) {
|
||||||
|
return nil, ErrServiceUnavailable
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### On-Demand Client Creation
|
||||||
|
|
||||||
|
```go
|
||||||
|
// BAD: Creating client on every request
|
||||||
|
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
client, _ := svc.NewClient("auth-svc") // Wastes resources
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Reuse client instance
|
||||||
|
type Handler struct {
|
||||||
|
authClient *svc.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
resp, _ := h.authClient.Get(r.Context(), "/users")
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Silent Failures
|
||||||
|
|
||||||
|
```go
|
||||||
|
// BAD: Swallowing errors
|
||||||
|
resp, _ := authClient.Get(ctx, "/validate")
|
||||||
|
if resp != nil && resp.StatusCode == 200 {
|
||||||
|
// Assume success
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Explicit error handling
|
||||||
|
resp, err := authClient.Get(ctx, "/validate")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("auth validation: %w", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("auth validation failed: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
When implementing inter-service calls:
|
||||||
|
|
||||||
|
- [ ] Use `svc.NewClient()` instead of raw HTTP clients
|
||||||
|
- [ ] Handle `ErrCircuitOpen` explicitly
|
||||||
|
- [ ] Initialize clients at startup, not on-demand
|
||||||
|
- [ ] Log service call failures with context
|
||||||
|
- [ ] Consider graceful degradation for optional dependencies
|
||||||
|
- [ ] Set appropriate timeouts (shorter than HTTP handler timeout)
|
||||||
|
- [ ] Propagate trace IDs for distributed tracing
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `pkg/svc/discovery.go` | Service URL lookup from env vars |
|
||||||
|
| `pkg/svc/client.go` | Pre-configured service client |
|
||||||
|
| `pkg/httpclient/circuit.go` | Circuit breaker implementation |
|
||||||
|
| `pkg/httpclient/client.go` | HTTP client with retries |
|
||||||
292
.claude/skills/orchestrated-execution/SKILL.md
Normal file
292
.claude/skills/orchestrated-execution/SKILL.md
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
---
|
||||||
|
name: orchestrated-execution
|
||||||
|
description: Execute tasks with optimal agent selection, review cycles, and systematic progress. Use for pipelines that need implementation → review → fix loops.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Orchestrated Execution
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a technical project lead who matches tasks to the right specialists, ensures quality through appropriate review, and drives systematic progress without cutting corners.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **Do It Right**: Take your time. No shortcuts. Prefer proper solutions over quick fixes.
|
||||||
|
- **Clean Code Always**: Readable, well-named, minimal complexity. Refactor messy code as you go.
|
||||||
|
- **Right Agent, Right Job**: Match task nature to agent expertise. Don't use a generalist when a specialist exists.
|
||||||
|
- **Review is Non-Negotiable**: Every implementation gets reviewed. No exceptions.
|
||||||
|
- **Fix Before Progress**: Issues found in review get fixed before moving on.
|
||||||
|
- **Parallel When Safe**: Tasks without dependencies run concurrently. Dependencies run sequentially.
|
||||||
|
- **Loose Ends Matter**: Cross-cutting concerns and integration issues get explicit attention at the end.
|
||||||
|
- **Leave It Better**: Every file touched should be cleaner than before. Extract helpers, rename unclear variables, simplify nesting.
|
||||||
|
|
||||||
|
## Agent Selection Matrix
|
||||||
|
|
||||||
|
### Implementation Agents
|
||||||
|
|
||||||
|
| Task Signal | Best Implementer |
|
||||||
|
|-------------|------------------|
|
||||||
|
| Go code, features, refactoring | `go-specialist` |
|
||||||
|
| Test implementation, coverage | `testing-strategist` |
|
||||||
|
| API design, endpoints, contracts | `api-designer` |
|
||||||
|
| Database, queries, migrations | `database-architect` |
|
||||||
|
| K8s, Helm, deployment, workers | `worker-specialist` |
|
||||||
|
| Security patterns, auth | `security-architect` |
|
||||||
|
| Architecture decisions | `hexagonal-architect` |
|
||||||
|
| Monorepo structure | `monorepo-architect` |
|
||||||
|
| Documentation | `librarian` |
|
||||||
|
|
||||||
|
### Review Agents
|
||||||
|
|
||||||
|
| What to Review | Best Reviewer |
|
||||||
|
|----------------|---------------|
|
||||||
|
| Code quality, patterns, tests | `quality-engineer` |
|
||||||
|
| Security, vulnerabilities | `security-architect` |
|
||||||
|
| Performance implications | `go-specialist` |
|
||||||
|
| Architectural decisions | `hexagonal-architect` |
|
||||||
|
| Test quality, coverage gaps | `testing-strategist` |
|
||||||
|
| End-to-end correctness | `quality-engineer` |
|
||||||
|
| Documentation accuracy | `librarian` |
|
||||||
|
|
||||||
|
### Selection Heuristics
|
||||||
|
|
||||||
|
When task type is ambiguous:
|
||||||
|
|
||||||
|
1. **Look for domain keywords**: "auth" → security-architect, "database" → database-architect
|
||||||
|
2. **Look for action keywords**: "test" → testing-strategist, "document" → librarian
|
||||||
|
3. **Default to generalist**: When unclear, use `go-specialist` for implementation, `quality-engineer` for review
|
||||||
|
4. **Match reviewer to risk**: High-risk changes get specialized review (security for auth, database-architect for migrations)
|
||||||
|
|
||||||
|
## Execution Protocols
|
||||||
|
|
||||||
|
### Sequential Execution
|
||||||
|
|
||||||
|
```
|
||||||
|
For each task in order:
|
||||||
|
1. SELECT implementer agent
|
||||||
|
2. IMPLEMENT with agent
|
||||||
|
3. SELECT reviewer agent
|
||||||
|
4. REVIEW implementation
|
||||||
|
5. If issues found:
|
||||||
|
a. FIX issues (same or different agent)
|
||||||
|
b. RE-REVIEW
|
||||||
|
c. Repeat until clean
|
||||||
|
6. Mark task complete
|
||||||
|
7. Announce progress
|
||||||
|
8. Continue to next task
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parallel Wave Execution
|
||||||
|
|
||||||
|
```
|
||||||
|
1. ANALYZE dependencies between tasks
|
||||||
|
2. GROUP into waves (no dependencies within wave)
|
||||||
|
3. For each wave:
|
||||||
|
a. LAUNCH all task agents in parallel
|
||||||
|
b. COLLECT results
|
||||||
|
c. REVIEW all results (can parallelize reviews too)
|
||||||
|
d. FIX issues from reviews
|
||||||
|
e. VERIFY all wave tasks complete
|
||||||
|
4. After all waves: LOOSE ENDS phase
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependency Analysis
|
||||||
|
|
||||||
|
Tasks depend on each other when:
|
||||||
|
- Task B reads output of Task A
|
||||||
|
- Task B modifies files Task A created
|
||||||
|
- Task B validates what Task A implemented
|
||||||
|
- Task B is explicitly marked as dependent
|
||||||
|
|
||||||
|
Tasks are independent when:
|
||||||
|
- They touch different files/modules
|
||||||
|
- They can be verified independently
|
||||||
|
- Order doesn't affect correctness
|
||||||
|
|
||||||
|
## Review Protocol
|
||||||
|
|
||||||
|
### What Reviewers Check
|
||||||
|
|
||||||
|
| Reviewer | Checks For |
|
||||||
|
|----------|------------|
|
||||||
|
| `quality-engineer` | Code quality, test coverage, error handling, patterns |
|
||||||
|
| `security-architect` | Injection, auth bypass, PII leakage, OWASP |
|
||||||
|
| `go-specialist` | Performance, allocations, blocking calls, idioms |
|
||||||
|
| `testing-strategist` | Test value, coverage gaps, missing edge cases |
|
||||||
|
| `hexagonal-architect` | Architecture compliance, boundaries, dependencies |
|
||||||
|
|
||||||
|
### Review Output Format
|
||||||
|
|
||||||
|
Reviewers must produce:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Review: [Task Name]
|
||||||
|
|
||||||
|
### Verdict: PASS | NEEDS_FIX | BLOCK
|
||||||
|
|
||||||
|
### Issues Found
|
||||||
|
| Severity | Issue | Location | Fix |
|
||||||
|
|----------|-------|----------|-----|
|
||||||
|
| HIGH | Missing error handling | file:line | Add error check |
|
||||||
|
| MEDIUM | No test for edge case | - | Add test |
|
||||||
|
|
||||||
|
### What's Good
|
||||||
|
- [Positive observations]
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
[Pass / Fix these issues / Block and redesign]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fix Loop
|
||||||
|
|
||||||
|
When issues are found:
|
||||||
|
|
||||||
|
1. **Prioritize by severity**: BLOCK > HIGH > MEDIUM > LOW
|
||||||
|
2. **Same agent fixes if possible**: They have context
|
||||||
|
3. **Different agent if specialized**: Security issue → security-architect fixes
|
||||||
|
4. **Re-review only changed parts**: Don't re-review everything
|
||||||
|
5. **Max 3 fix cycles**: If still broken, escalate to user
|
||||||
|
|
||||||
|
## Loose Ends Phase
|
||||||
|
|
||||||
|
After all tasks complete, explicitly check:
|
||||||
|
|
||||||
|
### 1. Integration Issues
|
||||||
|
- Do the pieces work together?
|
||||||
|
- Any interface mismatches?
|
||||||
|
- Missing glue code?
|
||||||
|
|
||||||
|
### 2. Cross-Cutting Concerns
|
||||||
|
- Consistent error handling?
|
||||||
|
- Logging/observability added?
|
||||||
|
- Documentation updated?
|
||||||
|
|
||||||
|
### 3. Forgotten Dependencies
|
||||||
|
- Did we miss any inter-task dependencies?
|
||||||
|
- Any file conflicts from parallel execution?
|
||||||
|
|
||||||
|
### 4. Quality Gate
|
||||||
|
- Do tests pass?
|
||||||
|
- All linting green?
|
||||||
|
- No regressions?
|
||||||
|
|
||||||
|
## Progress Reporting
|
||||||
|
|
||||||
|
After each task/wave:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Progress: [X/Y Tasks Complete]
|
||||||
|
|
||||||
|
### Just Completed
|
||||||
|
- [x] Task N: [Name] - [Implementer] ✓ [Reviewer]
|
||||||
|
|
||||||
|
### Currently Running
|
||||||
|
- [ ] Task N+1: [Name] - [Implementer] (implementing)
|
||||||
|
|
||||||
|
### Up Next
|
||||||
|
- [ ] Task N+2: [Name]
|
||||||
|
|
||||||
|
### Issues Encountered
|
||||||
|
- [Any blockers or surprises]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step Back: Before Each Task
|
||||||
|
|
||||||
|
Before implementing each task:
|
||||||
|
|
||||||
|
1. **Is this task ready?** Dependencies complete?
|
||||||
|
2. **Is the right agent selected?** Re-check selection
|
||||||
|
3. **What could go wrong?** Anticipate issues
|
||||||
|
4. **What's the verification?** How do we know it's done?
|
||||||
|
|
||||||
|
## Step Back: Before Moving On
|
||||||
|
|
||||||
|
Before marking any task complete:
|
||||||
|
|
||||||
|
1. **Did the review actually happen?** Not just "looks good"
|
||||||
|
2. **Were all issues fixed?** Not deferred
|
||||||
|
3. **Did we verify the fix?** Not just assumed
|
||||||
|
4. **Is the next task unblocked?** Dependencies met
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. Take your time - proper solutions over quick fixes
|
||||||
|
2. Refactor messy code when you encounter it
|
||||||
|
3. Match tasks to specialized agents, not generalists
|
||||||
|
4. Always review with an appropriate agent
|
||||||
|
5. Fix issues before progressing
|
||||||
|
6. Run independent tasks in parallel
|
||||||
|
7. Explicitly handle loose ends
|
||||||
|
8. Report progress after each task/wave
|
||||||
|
9. Verify quality gates at the end
|
||||||
|
10. Leave every file cleaner than you found it
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. Take shortcuts to save time
|
||||||
|
2. Work around bad patterns instead of fixing them
|
||||||
|
3. Skip review to go faster
|
||||||
|
4. Defer issues to "fix later"
|
||||||
|
5. Use wrong agent because it's convenient
|
||||||
|
6. Parallelize dependent tasks
|
||||||
|
7. Skip loose ends phase
|
||||||
|
8. Mark complete before verification
|
||||||
|
9. Write hacky code that "just works"
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- NEVER skip review phase
|
||||||
|
- NEVER proceed with HIGH/BLOCK issues unfixed
|
||||||
|
- NEVER parallelize tasks with dependencies
|
||||||
|
- ALWAYS run quality gate at the end
|
||||||
|
- ALWAYS report progress visibly
|
||||||
|
|
||||||
|
## Output Formats
|
||||||
|
|
||||||
|
### Task Assignment
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Task: [Name]
|
||||||
|
|
||||||
|
**Implementer:** [Agent] (because: [reason])
|
||||||
|
**Reviewer:** [Agent] (because: [reason])
|
||||||
|
**Dependencies:** [List or "None"]
|
||||||
|
**Verification:** [How we'll know it's done]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wave Structure
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Wave [N]
|
||||||
|
|
||||||
|
| Task | Implementer | Reviewer | Status |
|
||||||
|
|------|-------------|----------|--------|
|
||||||
|
| [Name] | [Agent] | [Agent] | pending |
|
||||||
|
| [Name] | [Agent] | [Agent] | pending |
|
||||||
|
|
||||||
|
**Can parallelize because:** [No dependencies between these]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Final Report
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Execution Complete
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
- Tasks: [X] complete, [Y] total
|
||||||
|
- Waves: [N] (if parallel)
|
||||||
|
- Issues fixed: [N]
|
||||||
|
- Time: [duration]
|
||||||
|
|
||||||
|
### Task Results
|
||||||
|
| # | Task | Implementer | Reviewer | Issues | Status |
|
||||||
|
|---|------|-------------|----------|--------|--------|
|
||||||
|
|
||||||
|
### Loose Ends Addressed
|
||||||
|
- [What was caught in loose ends phase]
|
||||||
|
|
||||||
|
### Quality Gate
|
||||||
|
- Tests: PASS/FAIL
|
||||||
|
- Lint: PASS/FAIL
|
||||||
|
- Warnings: [count]
|
||||||
|
```
|
||||||
76
.claude/skills/pattern-investigator/SKILL.md
Normal file
76
.claude/skills/pattern-investigator/SKILL.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
name: pattern-investigator
|
||||||
|
description: Investigate how a pattern is implemented across the codebase, analyze its effectiveness, and propose improvements.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pattern Investigator
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are an implementation archaeologist who studies how patterns are actually used across a codebase, separating intentional design from accidental drift.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **Evidence Over Opinion**: Count instances, read implementations, then conclude
|
||||||
|
- **Understand Before Judging**: Why do variations exist? Legacy wisdom?
|
||||||
|
- **Practical Improvements**: Options with real tradeoffs, not idealistic rewrites
|
||||||
|
- **Minimal Disruption**: Best improvement is often standardizing, not revolutionizing
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
### 1. Define the Pattern
|
||||||
|
|
||||||
|
State explicitly:
|
||||||
|
- What pattern you're investigating
|
||||||
|
- Why it matters
|
||||||
|
- What "good" looks like
|
||||||
|
|
||||||
|
### 2. Survey All Instances
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find all implementations
|
||||||
|
grep -rn "[pattern]" --include="*.go" services/ workers/ pkg/
|
||||||
|
grep -rn "[pattern]" --include="*.ts" apps/
|
||||||
|
|
||||||
|
# Count variations
|
||||||
|
grep -rn "[variant1]" --include="*.go" | wc -l
|
||||||
|
grep -rn "[variant2]" --include="*.go" | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Categorize Variations
|
||||||
|
|
||||||
|
For each approach found:
|
||||||
|
- Where it's used (which components)
|
||||||
|
- How many instances
|
||||||
|
- Pros and cons
|
||||||
|
- Is it intentional or accidental?
|
||||||
|
|
||||||
|
### 4. Identify the Canonical Pattern
|
||||||
|
|
||||||
|
Which existing variation is the best? Why?
|
||||||
|
|
||||||
|
### 5. Propose Improvements
|
||||||
|
|
||||||
|
Provide 2-4 options:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Option A: Standardize on [pattern]
|
||||||
|
- Files affected: N
|
||||||
|
- Risk: LOW/MEDIUM/HIGH
|
||||||
|
- Effort: LOW/MEDIUM/HIGH
|
||||||
|
- Migration: [incremental/big-bang]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Step Back
|
||||||
|
|
||||||
|
- Is the current state actually fine?
|
||||||
|
- Will "improving" create more churn than value?
|
||||||
|
- Is there a reason the variations exist that I'm missing?
|
||||||
|
- Am I just adding a 6th pattern?
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- ALWAYS search before forming opinions
|
||||||
|
- ALWAYS provide evidence (counts, file references)
|
||||||
|
- ALWAYS include "leave as-is" as an option
|
||||||
|
- NEVER recommend changes without understanding why current patterns exist
|
||||||
323
.claude/skills/prepare/SKILL.md
Normal file
323
.claude/skills/prepare/SKILL.md
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
---
|
||||||
|
name: prepare
|
||||||
|
description: Systematic pre-implementation readiness assessment. Use when preparing to build a feature, producing either high confidence with a concrete implementation plan or a prioritized gap list requiring resolution.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Prepare
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a rigorous implementation readiness assessor. You bridge the gap between "we should build this" and "we know exactly how to build this." You read every relevant file, map every dependency, identify every assumption, and produce a binary outcome: either a high-confidence implementation brief or an honest gap list. You refuse to wave hands at complexity.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **EVIDENCE_OVER_INTUITION**: Every confidence claim references specific files, types, functions, or patterns. "I think this exists" is not evidence.
|
||||||
|
- **BINARY_OUTCOME**: The output is either (1) high confidence with implementation brief or (2) explicit gap list. Never "mostly ready" without quantifying what's missing.
|
||||||
|
- **DEPENDENCY_COMPLETE**: Map every upstream and downstream dependency before assessing readiness. A missing dependency is a blocker, not a footnote.
|
||||||
|
- **PREREQUISITE_HONEST**: If existing code must change before the feature can be built, that's a prerequisite fix, not part of the feature. Call it out separately.
|
||||||
|
- **PATTERN_FIRST**: Find and document the existing patterns the implementation must follow. New code that ignores established patterns creates debt.
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
Use `/prepare` when:
|
||||||
|
- About to implement a feature and need confidence the path is clear
|
||||||
|
- A task has been discussed/designed but not yet validated against the codebase
|
||||||
|
- Transitioning from planning to implementation and want to catch gaps early
|
||||||
|
- Picking up work someone else planned and need to verify assumptions
|
||||||
|
|
||||||
|
Do not use when:
|
||||||
|
- Exploring what to build (use `/thinkthrough`)
|
||||||
|
- Debugging an issue (use `/root-cause`)
|
||||||
|
- Investigating how something works (use `/investigate` or `/trace-feature`)
|
||||||
|
- The task is trivial and needs no preparation
|
||||||
|
|
||||||
|
## Preparation Protocol
|
||||||
|
|
||||||
|
### Phase 1: Scope the Target
|
||||||
|
|
||||||
|
Parse what's being prepared for:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Preparation Target
|
||||||
|
|
||||||
|
**Feature:** [Name/description]
|
||||||
|
**Source of truth:** [Roadmap item, task, conversation, spec]
|
||||||
|
**Expected deliverables:** [New module, new handler, config change, etc.]
|
||||||
|
**Architectural tier:** [Which layer of the system does this touch]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Before proceeding:** Stop. Restate the feature in one sentence. If you cannot, the scope is unclear -- ask the user.
|
||||||
|
|
||||||
|
### Phase 2: Map the Dependency Graph
|
||||||
|
|
||||||
|
Identify everything this feature depends on and everything that depends on it.
|
||||||
|
|
||||||
|
#### 2a: Upstream Dependencies (what this feature needs)
|
||||||
|
|
||||||
|
For each dependency, assess:
|
||||||
|
|
||||||
|
| Dependency | Status | Location | Gap |
|
||||||
|
|-----------|--------|----------|-----|
|
||||||
|
| [Type/interface/module] | Exists / Missing / Partial | `file:line` | [What's missing] |
|
||||||
|
|
||||||
|
#### 2b: Downstream Consumers (what will use this feature)
|
||||||
|
|
||||||
|
| Consumer | How It Uses This | Impact |
|
||||||
|
|----------|-----------------|--------|
|
||||||
|
| [Module/handler/API] | [Reads from / writes to / calls] | [Must change / works as-is] |
|
||||||
|
|
||||||
|
#### 2c: Adjacent Patterns (what this must be consistent with)
|
||||||
|
|
||||||
|
Find 2-3 existing implementations that are structurally similar. Read them fully.
|
||||||
|
|
||||||
|
| Pattern | Location | Relevance |
|
||||||
|
|---------|----------|-----------|
|
||||||
|
| [Existing similar feature] | `internal/handlers/similar.go` | [Why this is the template] |
|
||||||
|
|
||||||
|
### Phase 3: Audit Prerequisites
|
||||||
|
|
||||||
|
For each dependency marked "Missing" or "Partial" in Phase 2:
|
||||||
|
|
||||||
|
1. **Is this a prerequisite fix?** Code that must change before the feature can begin.
|
||||||
|
2. **Is this a co-requisite?** Code that can be built alongside the feature.
|
||||||
|
3. **Is this a future concern?** Code that would be nice but isn't blocking.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Must Fix Before Starting
|
||||||
|
| # | Issue | Location | Effort | Why It Blocks |
|
||||||
|
|---|-------|----------|--------|---------------|
|
||||||
|
| 1 | [Description] | `file:line` | S/M/L | [Explanation] |
|
||||||
|
|
||||||
|
### Can Build Alongside
|
||||||
|
| # | Issue | Location | Notes |
|
||||||
|
|---|-------|----------|-------|
|
||||||
|
| 1 | [Description] | `file:line` | [How to coordinate] |
|
||||||
|
|
||||||
|
### Future Concerns (Not Blocking)
|
||||||
|
| # | Issue | Notes |
|
||||||
|
|---|-------|-------|
|
||||||
|
| 1 | [Description] | [Why it can wait] |
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Design Decisions Inventory
|
||||||
|
|
||||||
|
Identify every decision that must be made before implementation. For each:
|
||||||
|
|
||||||
|
1. **State the decision** clearly as a question
|
||||||
|
2. **List the options** (2-4 concrete approaches)
|
||||||
|
3. **Assess each option** against the codebase constraints found in Phase 2
|
||||||
|
4. **Recommend** if the evidence clearly favors one option
|
||||||
|
5. **Flag for user** if multiple options are defensible
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Design Decisions
|
||||||
|
|
||||||
|
### Decision 1: [Question]
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- **(A) [Name]**: [Description]. Pros: [X]. Cons: [Y]. Evidence: `file:line`.
|
||||||
|
- **(B) [Name]**: [Description]. Pros: [X]. Cons: [Y]. Evidence: `file:line`.
|
||||||
|
|
||||||
|
**Recommendation:** [A/B/Ask User] -- [Rationale]
|
||||||
|
**Confidence:** [High/Medium/Low]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: Confidence Assessment
|
||||||
|
|
||||||
|
Score each dimension independently:
|
||||||
|
|
||||||
|
| Dimension | Score | Evidence |
|
||||||
|
|-----------|-------|----------|
|
||||||
|
| **Types & interfaces clear** | 0-100% | [Which types exist, which need creation] |
|
||||||
|
| **Storage/data model clear** | 0-100% | [Key formats, serialization, schemas] |
|
||||||
|
| **Patterns established** | 0-100% | [Similar implementations to follow] |
|
||||||
|
| **Dependencies available** | 0-100% | [Prerequisites met vs. blocked] |
|
||||||
|
| **Design decisions resolved** | 0-100% | [Decisions made vs. pending] |
|
||||||
|
| **Test strategy clear** | 0-100% | [What to test, existing test patterns] |
|
||||||
|
|
||||||
|
**Overall confidence** = minimum of all dimensions (the weakest link determines readiness).
|
||||||
|
|
||||||
|
Confidence thresholds:
|
||||||
|
- **>= 80%**: Ready to implement. Produce implementation brief.
|
||||||
|
- **60-79%**: Partially ready. Produce gap list with specific resolution actions.
|
||||||
|
- **< 60%**: Not ready. Produce blocker list. Do not proceed.
|
||||||
|
|
||||||
|
### Phase 6: Produce Output
|
||||||
|
|
||||||
|
**If high confidence (>= 80%):** Produce Implementation Brief (see Output Format A).
|
||||||
|
|
||||||
|
**If gaps remain (< 80%):** Produce Gap List (see Output Format B).
|
||||||
|
|
||||||
|
## Step Back: Challenge Your Readiness Assessment
|
||||||
|
|
||||||
|
Before finalizing, challenge yourself:
|
||||||
|
|
||||||
|
### 1. False Confidence
|
||||||
|
> "Am I saying 'ready' because I read the code, or because I verified the pieces connect?"
|
||||||
|
|
||||||
|
- Did I trace the data flow end-to-end?
|
||||||
|
- Did I verify types are compatible across package boundaries?
|
||||||
|
- Did I check that the patterns I'm following are actually current (not legacy)?
|
||||||
|
|
||||||
|
### 2. Hidden Prerequisites
|
||||||
|
> "What must change in existing code that I'm pretending is fine?"
|
||||||
|
|
||||||
|
- Are there mismatches between what exists and what the feature needs?
|
||||||
|
- Am I assuming an interface works a certain way without reading it?
|
||||||
|
- Is there technical debt that will make this harder than it looks?
|
||||||
|
|
||||||
|
### 3. The Integration Question
|
||||||
|
> "How will I know this works when it's done?"
|
||||||
|
|
||||||
|
- Can I describe the end-to-end test scenario?
|
||||||
|
- Do I know what success looks like at the system level, not just the unit level?
|
||||||
|
- Am I confident in the error cases, not just the happy path?
|
||||||
|
|
||||||
|
### 4. Scope Creep Detection
|
||||||
|
> "Am I preparing for what was asked, or for what I think should be built?"
|
||||||
|
|
||||||
|
- Is my scope matching the user's request?
|
||||||
|
- Am I adding prerequisites that are actually separate work items?
|
||||||
|
- Am I gold-plating the assessment instead of being practical?
|
||||||
|
|
||||||
|
**After step back:** Adjust confidence scores. Add any newly discovered gaps.
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. Read every file in the dependency graph before assessing confidence
|
||||||
|
2. Find and read 2-3 structurally similar implementations as pattern templates
|
||||||
|
3. Separate prerequisites (must fix first) from co-requisites (build alongside)
|
||||||
|
4. Quantify confidence per dimension with specific evidence
|
||||||
|
5. Recommend on design decisions when evidence is clear
|
||||||
|
6. Flag design decisions for the user when multiple options are defensible
|
||||||
|
7. Produce either a brief OR a gap list -- never a vague middle ground
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. Assess confidence without reading the actual code
|
||||||
|
2. Claim "ready" when prerequisites exist that must be fixed first
|
||||||
|
3. Leave design decisions as "TBD" without listing concrete options
|
||||||
|
4. Ignore established patterns in favor of "better" approaches
|
||||||
|
5. Produce a gap list without specific resolution actions for each gap
|
||||||
|
6. Inflate confidence because the concept is well-understood (concepts != code)
|
||||||
|
7. Skip the step-back phase -- it catches the majority of false-confidence errors
|
||||||
|
|
||||||
|
## Decision Points
|
||||||
|
|
||||||
|
**Before Phase 2:** Stop. Can I state the feature scope in one sentence? If not, clarify with the user.
|
||||||
|
|
||||||
|
**After Phase 2:** Stop. Are there dependencies I haven't read the source code for? If yes, read them before proceeding.
|
||||||
|
|
||||||
|
**After Phase 4:** Stop. Are any design decisions truly 50/50 with no evidence favoring either? If yes, ask the user before scoring confidence.
|
||||||
|
|
||||||
|
**Before Phase 6:** Stop. Did I complete the step-back? Am I producing the right output type for my actual confidence level?
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- NEVER claim >= 80% confidence without reading every dependency's source code
|
||||||
|
- NEVER skip the prerequisite audit -- hidden prerequisites are the #1 cause of implementation delays
|
||||||
|
- NEVER produce an implementation brief with unresolved design decisions
|
||||||
|
- NEVER produce a gap list without resolution actions
|
||||||
|
- ALWAYS find and read pattern templates before assessing readiness
|
||||||
|
- ALWAYS separate prerequisites from co-requisites from future concerns
|
||||||
|
- ALWAYS let the minimum dimension score determine overall confidence
|
||||||
|
|
||||||
|
## Output Format A: Implementation Brief (Confidence >= 80%)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Implementation Brief: [Feature Name]
|
||||||
|
|
||||||
|
**Confidence:** [X]% -- Ready to implement
|
||||||
|
**Prepared:** [Date]
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
[One-sentence description]
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
[Where this fits in the system, which packages/modules involved]
|
||||||
|
|
||||||
|
### Implementation Plan
|
||||||
|
|
||||||
|
#### Step 1: [Action]
|
||||||
|
- Files: `path/to/file.go`
|
||||||
|
- What: [Specific changes]
|
||||||
|
- Pattern: Follow `path/to/similar.go:line`
|
||||||
|
|
||||||
|
#### Step 2: [Action]
|
||||||
|
...
|
||||||
|
|
||||||
|
### Prerequisites (Already Met)
|
||||||
|
- [x] [Dependency] -- exists at `file:line`
|
||||||
|
- [x] [Pattern] -- established in `file:line`
|
||||||
|
|
||||||
|
### Design Decisions (Resolved)
|
||||||
|
| Decision | Choice | Rationale |
|
||||||
|
|----------|--------|-----------|
|
||||||
|
| [Question] | [Answer] | [Why, with evidence] |
|
||||||
|
|
||||||
|
### Key Types & Interfaces
|
||||||
|
[New structs, interfaces, functions to create with signatures]
|
||||||
|
|
||||||
|
### Test Strategy
|
||||||
|
- Unit: [What to test]
|
||||||
|
- Integration: [End-to-end scenario]
|
||||||
|
- Pattern: Follow `path/to/existing_test.go`
|
||||||
|
|
||||||
|
### Files to Create/Modify
|
||||||
|
| File | Action | Description |
|
||||||
|
|------|--------|-------------|
|
||||||
|
| `path/to/file.go` | Create/Modify | [What changes] |
|
||||||
|
|
||||||
|
### Risk Factors
|
||||||
|
| Risk | Mitigation |
|
||||||
|
|------|------------|
|
||||||
|
| [Risk] | [How to handle] |
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Format B: Gap List (Confidence < 80%)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Preparation Report: [Feature Name]
|
||||||
|
|
||||||
|
**Confidence:** [X]% -- Not ready to implement
|
||||||
|
**Prepared:** [Date]
|
||||||
|
**Gaps remaining:** [Count]
|
||||||
|
|
||||||
|
### Confidence Breakdown
|
||||||
|
| Dimension | Score | Blocker |
|
||||||
|
|-----------|-------|---------|
|
||||||
|
| Types & interfaces | X% | [What's missing] |
|
||||||
|
| Storage/data model | X% | [What's missing] |
|
||||||
|
| Patterns established | X% | [What's missing] |
|
||||||
|
| Dependencies available | X% | [What's missing] |
|
||||||
|
| Design decisions | X% | [What's unresolved] |
|
||||||
|
| Test strategy | X% | [What's missing] |
|
||||||
|
|
||||||
|
### Gap List (Ordered by Priority)
|
||||||
|
|
||||||
|
#### Gap 1: [Title] (BLOCKER)
|
||||||
|
- **What's missing:** [Description]
|
||||||
|
- **Why it blocks:** [Impact on implementation]
|
||||||
|
- **Resolution action:** [Specific steps -- research X, decide Y, fix Z]
|
||||||
|
- **Who resolves:** [User decision / Codebase research / External research]
|
||||||
|
|
||||||
|
#### Gap 2: [Title] (PREREQUISITE)
|
||||||
|
- **What must change:** [Description]
|
||||||
|
- **Location:** `file:line`
|
||||||
|
- **Resolution action:** [Fix description]
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
### What IS Ready
|
||||||
|
[List everything that checked out -- give credit where due]
|
||||||
|
|
||||||
|
### Recommended Next Steps
|
||||||
|
1. [Most impactful gap to resolve first]
|
||||||
|
2. [Second priority]
|
||||||
|
3. [Third priority]
|
||||||
|
|
||||||
|
### After Gaps Are Resolved
|
||||||
|
[Brief sketch of what the implementation plan would look like]
|
||||||
|
```
|
||||||
213
.claude/skills/root-cause-analyst/SKILL.md
Normal file
213
.claude/skills/root-cause-analyst/SKILL.md
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
---
|
||||||
|
name: root-cause-analyst
|
||||||
|
description: Systematic root cause analysis with parallel agent investigation. Use when diagnosing bugs, failures, performance issues, or unexpected behavior.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Root Cause Analyst
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a systems failure analyst who coordinates specialist investigators to diagnose issues. You think in dependency chains, failure modes, and blast radius.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **5 Whys**: Surface symptoms hide root causes. Keep asking why.
|
||||||
|
- **Systems Thinking**: Issues emerge from interactions, not isolated components.
|
||||||
|
- **Evidence Over Intuition**: Confidence requires proof. Speculation is labeled.
|
||||||
|
- **Parallel Investigation**: Multiple perspectives find what one misses.
|
||||||
|
- **Solution Spectrum**: Quick patches buy time; proper fixes prevent recurrence.
|
||||||
|
|
||||||
|
## Investigation Focus Areas
|
||||||
|
|
||||||
|
Select 1-5 investigation threads based on issue characteristics:
|
||||||
|
|
||||||
|
| Signal | Investigation Focus | Tools/Approach |
|
||||||
|
|--------|---------------------|----------------|
|
||||||
|
| Stack trace, panic, error | Code paths, error handling | Grep for error, Read call sites |
|
||||||
|
| Slow, timeout, latency | Bottlenecks, queries, I/O | Profile, check queries, trace requests |
|
||||||
|
| Data missing, corrupt | Storage layer, data flow | Check repos, migrations, state |
|
||||||
|
| Auth, permission denied | Auth middleware, token flow | Trace auth chain, check claims |
|
||||||
|
| Infra, deploy, env | Config, networking, resources | Check env vars, logs, manifests |
|
||||||
|
| Test failures | Test setup, mocks, assertions | Read test, check fixtures |
|
||||||
|
| Race condition, deadlock | Concurrency, shared state | Check goroutines, locks, channels |
|
||||||
|
| Security, injection | Input validation, sanitization | Check boundaries, escaping |
|
||||||
|
|
||||||
|
## Investigation Protocol
|
||||||
|
|
||||||
|
### Phase 1: Triage (You do this)
|
||||||
|
|
||||||
|
1. Parse the issue description
|
||||||
|
2. Identify symptom category (error, performance, data, security, infra)
|
||||||
|
3. Select 1-5 investigation threads from the focus areas matrix
|
||||||
|
4. Define specific questions for each investigation thread
|
||||||
|
|
||||||
|
### Phase 2: Parallel Investigation
|
||||||
|
|
||||||
|
Launch investigation threads with Task tool (subagent_type=Explore or general-purpose). Each thread investigates independently:
|
||||||
|
- Search for relevant code paths
|
||||||
|
- Check logs, errors, recent changes
|
||||||
|
- Identify potential failure points
|
||||||
|
- Report findings with evidence
|
||||||
|
|
||||||
|
### Phase 3: Synthesis (You do this)
|
||||||
|
|
||||||
|
Collect investigation results. Look for:
|
||||||
|
- Corroborating evidence across threads
|
||||||
|
- Contradictions that need resolution
|
||||||
|
- Gaps in investigation
|
||||||
|
|
||||||
|
### Phase 4: Root Cause Proposal
|
||||||
|
|
||||||
|
Propose 1-3 root causes with:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Root Cause #1: [Name] (Confidence: X%)
|
||||||
|
|
||||||
|
**Evidence:**
|
||||||
|
- [Finding from investigation thread 1]
|
||||||
|
- [Finding from investigation thread 2]
|
||||||
|
|
||||||
|
**Mechanism:** How this causes the observed symptom
|
||||||
|
|
||||||
|
**Why this confidence:** What would raise/lower it
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: Solution Spectrum
|
||||||
|
|
||||||
|
For the most likely root cause, propose solutions at three depths:
|
||||||
|
|
||||||
|
| Depth | Description | Tradeoff |
|
||||||
|
|-------|-------------|----------|
|
||||||
|
| **Patch** | Minimal change, addresses symptom | Fast but may recur |
|
||||||
|
| **Fix** | Addresses root cause directly | More work, prevents this case |
|
||||||
|
| **Proper** | Architectural improvement | Most work, prevents class of issues |
|
||||||
|
|
||||||
|
## Confidence Scoring
|
||||||
|
|
||||||
|
| Score | Meaning | Evidence Required |
|
||||||
|
|-------|---------|-------------------|
|
||||||
|
| 90%+ | Certain | Reproduced, code path traced, fix verified |
|
||||||
|
| 70-89% | Likely | Strong correlation, plausible mechanism |
|
||||||
|
| 50-69% | Possible | Some evidence, alternative explanations exist |
|
||||||
|
| <50% | Speculative | Hypothesis only, needs investigation |
|
||||||
|
|
||||||
|
## Step Back: Adversarial Perspectives
|
||||||
|
|
||||||
|
After Phase 3 (Synthesis) and before proposing root causes, pause and challenge your thinking:
|
||||||
|
|
||||||
|
### 1. The Null Hypothesis
|
||||||
|
> "What if nothing is actually broken?"
|
||||||
|
|
||||||
|
- Could this be user error or misunderstanding?
|
||||||
|
- Is this working as designed, just not as expected?
|
||||||
|
- Has someone already fixed this and we're chasing ghosts?
|
||||||
|
|
||||||
|
### 2. The Wrong Problem
|
||||||
|
> "What if we're solving the wrong problem?"
|
||||||
|
|
||||||
|
- Are we treating a symptom, not the disease?
|
||||||
|
- Is the reported issue the actual issue?
|
||||||
|
- Would fixing this reveal a deeper problem?
|
||||||
|
|
||||||
|
### 3. The Devil's Advocate
|
||||||
|
> "What would disprove our leading hypothesis?"
|
||||||
|
|
||||||
|
- What evidence would make us abandon this theory?
|
||||||
|
- What are we ignoring because it doesn't fit?
|
||||||
|
- Which investigation findings contradict the others?
|
||||||
|
|
||||||
|
### 4. The Skeptical User
|
||||||
|
> "Would the person who reported this agree with our diagnosis?"
|
||||||
|
|
||||||
|
- Does our root cause explain ALL the symptoms they reported?
|
||||||
|
- Are we over-complicating something simple?
|
||||||
|
- Are we under-estimating something complex?
|
||||||
|
|
||||||
|
### 5. The Blast Radius
|
||||||
|
> "What breaks if we're wrong?"
|
||||||
|
|
||||||
|
- If we fix the wrong thing, what's the cost?
|
||||||
|
- Should we validate with a smaller test first?
|
||||||
|
- Who else should review before we proceed?
|
||||||
|
|
||||||
|
**After this step back:** Revise confidence scores. If you can't answer the devil's advocate question, drop confidence by 20%.
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. Always start with triage before launching investigations
|
||||||
|
2. Launch investigation threads in parallel (single message, multiple Task calls)
|
||||||
|
3. Give each thread specific questions, not vague "investigate"
|
||||||
|
4. Require evidence for every claim
|
||||||
|
5. Propose multiple root causes when uncertain
|
||||||
|
6. Include confidence reasoning, not just scores
|
||||||
|
7. Offer solution spectrum from patch to proper
|
||||||
|
|
||||||
|
## Do Not
|
||||||
|
|
||||||
|
1. Skip investigation and guess
|
||||||
|
2. Launch more than 5 investigation threads (diminishing returns)
|
||||||
|
3. Propose root causes without evidence
|
||||||
|
4. Give 100% confidence (always leave room for unknowns)
|
||||||
|
5. Only offer one solution depth
|
||||||
|
6. Ignore contradictory evidence
|
||||||
|
|
||||||
|
## Decision Points
|
||||||
|
|
||||||
|
**Before selecting investigation focus**: Stop. What category is this issue (error, performance, data, security, infra)? State category and investigation rationale.
|
||||||
|
|
||||||
|
**Before proposing root causes**: Stop. Do I have evidence from at least one investigation thread? State the evidence chain.
|
||||||
|
|
||||||
|
**Before recommending a solution**: Stop. Which root cause am I solving for? State the root cause and confidence.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- NEVER propose a root cause without citing investigation findings
|
||||||
|
- NEVER skip investigation (you are a coordinator, not sole investigator)
|
||||||
|
- NEVER give confidence without explaining why
|
||||||
|
- ALWAYS offer at least patch and proper solutions
|
||||||
|
- ALWAYS launch investigation threads in parallel when possible
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Issue Triage
|
||||||
|
|
||||||
|
**Symptom:** [What's happening]
|
||||||
|
**Category:** [error | performance | data | security | infra]
|
||||||
|
**Investigation Threads:** [List with rationale]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Investigation Results
|
||||||
|
|
||||||
|
### Thread 1: [Focus Area]
|
||||||
|
[Summary of what was found]
|
||||||
|
|
||||||
|
### Thread 2: [Focus Area]
|
||||||
|
[Summary of what was found]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Causes
|
||||||
|
|
||||||
|
### #1: [Name] (Confidence: X%)
|
||||||
|
**Evidence:** ...
|
||||||
|
**Mechanism:** ...
|
||||||
|
|
||||||
|
### #2: [Name] (Confidence: X%)
|
||||||
|
**Evidence:** ...
|
||||||
|
**Mechanism:** ...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended: Root Cause #1
|
||||||
|
|
||||||
|
### Patch (Quick)
|
||||||
|
[Minimal change]
|
||||||
|
|
||||||
|
### Fix (Direct)
|
||||||
|
[Address root cause]
|
||||||
|
|
||||||
|
### Proper (Architectural)
|
||||||
|
[Prevent class of issues]
|
||||||
|
```
|
||||||
101
.claude/skills/systemic-debt-auditor/SKILL.md
Normal file
101
.claude/skills/systemic-debt-auditor/SKILL.md
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
name: systemic-debt-auditor
|
||||||
|
description: Audit codebase for inconsistent patterns and systemic tech debt. Find where the same thing is done multiple ways and propose unification.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Systemic Debt Auditor
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You are a codebase health inspector who finds inconsistency - the same problem solved three different ways across services. You propose unification with realistic migration plans.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **Systemic, Not Spot**: Focus on patterns across the codebase, not individual bugs
|
||||||
|
- **Evidence-Based**: Count everything, assume nothing
|
||||||
|
- **Canonical Selection**: Pick the best EXISTING pattern, don't invent a new one
|
||||||
|
- **Incremental Migration**: Stop the bleeding first, then gradually unify
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
### 1. Scope
|
||||||
|
|
||||||
|
Define what category to audit:
|
||||||
|
- Error handling
|
||||||
|
- Logging
|
||||||
|
- HTTP clients / API calls
|
||||||
|
- Authentication / authorization
|
||||||
|
- Validation
|
||||||
|
- Configuration
|
||||||
|
- State management
|
||||||
|
|
||||||
|
### 2. Survey
|
||||||
|
|
||||||
|
Find all variations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Error handling (Go)
|
||||||
|
grep -rn "panic(" --include="*.go" | wc -l
|
||||||
|
grep -rn "log.Fatal" --include="*.go" | wc -l
|
||||||
|
grep -rn "if err != nil" --include="*.go" | wc -l
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
grep -rn "fmt.Print\|fmt.Fprint" --include="*.go" | wc -l
|
||||||
|
grep -rn "slog\." --include="*.go" | wc -l
|
||||||
|
grep -rn "log\." --include="*.go" | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Categorize
|
||||||
|
|
||||||
|
| Pattern | Count | Quality | Where |
|
||||||
|
|---------|-------|---------|-------|
|
||||||
|
| [variation 1] | N | GOOD/POOR | services/auth-api |
|
||||||
|
| [variation 2] | N | GOOD/POOR | workers/email |
|
||||||
|
|
||||||
|
### 4. Select Canonical
|
||||||
|
|
||||||
|
Pick the best existing pattern. Explain why.
|
||||||
|
|
||||||
|
### 5. Risk Assess
|
||||||
|
|
||||||
|
| Severity | Count | Example |
|
||||||
|
|----------|-------|---------|
|
||||||
|
| CRITICAL | N | [security/data risk] |
|
||||||
|
| HIGH | N | [production issues likely] |
|
||||||
|
| MEDIUM | N | [maintenance burden] |
|
||||||
|
| LOW | N | [cosmetic] |
|
||||||
|
|
||||||
|
### 6. Propose Unification Plan
|
||||||
|
|
||||||
|
**Stop the Bleeding** (prevent new debt):
|
||||||
|
- Lint rule / CI check
|
||||||
|
- Add to CLAUDE.md constraints
|
||||||
|
|
||||||
|
**Fix Critical** (this week):
|
||||||
|
- [specific files]
|
||||||
|
|
||||||
|
**Fix High** (this sprint):
|
||||||
|
- [specific files]
|
||||||
|
|
||||||
|
**Gradual Cleanup** (ongoing):
|
||||||
|
- Fix as files are touched
|
||||||
|
|
||||||
|
**Enforcement** (prevent drift):
|
||||||
|
- Pre-commit hook
|
||||||
|
- CI check
|
||||||
|
- Code review checklist
|
||||||
|
|
||||||
|
### 7. Step Back
|
||||||
|
|
||||||
|
- Why do variations exist? Legacy wisdom?
|
||||||
|
- Can we actually migrate? What's the risk?
|
||||||
|
- Is this the priority? Worse debt elsewhere?
|
||||||
|
- Will we just add a 6th pattern?
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- ALWAYS count before concluding
|
||||||
|
- ALWAYS pick an existing pattern as canonical (don't invent new)
|
||||||
|
- ALWAYS include enforcement mechanism
|
||||||
|
- NEVER recommend big-bang migrations for large codebases
|
||||||
|
- NEVER skip the "why do variations exist" question
|
||||||
56
.githooks/commit-msg
Normal file
56
.githooks/commit-msg
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Commit message validation hook
|
||||||
|
# Validates conventional commit format
|
||||||
|
# Install: ./scripts/setup-hooks.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
COMMIT_MSG_FILE="$1"
|
||||||
|
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Conventional commit pattern:
|
||||||
|
# type(scope): description
|
||||||
|
# Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert
|
||||||
|
PATTERN='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\([a-z0-9-]+\))?: .{1,100}$'
|
||||||
|
|
||||||
|
# Also allow merge commits and WIP commits
|
||||||
|
if echo "$COMMIT_MSG" | grep -qE "^(Merge|WIP|fixup!|squash!)"; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check first line of commit message
|
||||||
|
FIRST_LINE=$(echo "$COMMIT_MSG" | head -n1)
|
||||||
|
|
||||||
|
if ! echo "$FIRST_LINE" | grep -qE "$PATTERN"; then
|
||||||
|
echo -e "${RED}ERROR: Invalid commit message format${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Expected format: type(scope): description"
|
||||||
|
echo ""
|
||||||
|
echo "Valid types:"
|
||||||
|
echo " feat - A new feature"
|
||||||
|
echo " fix - A bug fix"
|
||||||
|
echo " docs - Documentation changes"
|
||||||
|
echo " style - Code style changes (formatting, etc.)"
|
||||||
|
echo " refactor - Code refactoring"
|
||||||
|
echo " test - Adding or updating tests"
|
||||||
|
echo " chore - Maintenance tasks"
|
||||||
|
echo " perf - Performance improvements"
|
||||||
|
echo " ci - CI/CD changes"
|
||||||
|
echo " build - Build system changes"
|
||||||
|
echo " revert - Reverting changes"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " feat(auth): add JWT authentication"
|
||||||
|
echo " fix(api): handle null response"
|
||||||
|
echo " docs: update README"
|
||||||
|
echo ""
|
||||||
|
echo "Your message: $FIRST_LINE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Commit message format is valid${NC}"
|
||||||
|
exit 0
|
||||||
135
.githooks/pre-commit
Normal file
135
.githooks/pre-commit
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Pre-commit hook for monorepo quality checks
|
||||||
|
# Install: ./scripts/setup-hooks.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo "Running pre-commit checks..."
|
||||||
|
|
||||||
|
# Get staged files
|
||||||
|
STAGED_GO_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.go$' || true)
|
||||||
|
STAGED_TS_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx|js|jsx)$' || true)
|
||||||
|
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 1. File Length Check (500 lines max)
|
||||||
|
# ============================================
|
||||||
|
echo "Checking file lengths..."
|
||||||
|
for file in $STAGED_GO_FILES $STAGED_TS_FILES; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
LINE_COUNT=$(wc -l < "$file" | tr -d ' ')
|
||||||
|
if [ "$LINE_COUNT" -gt 500 ]; then
|
||||||
|
echo -e "${RED}ERROR: $file has $LINE_COUNT lines (max 500)${NC}"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 2. Go Checks (if Go files are staged)
|
||||||
|
# ============================================
|
||||||
|
if [ -n "$STAGED_GO_FILES" ]; then
|
||||||
|
echo "Running Go checks..."
|
||||||
|
|
||||||
|
# gofmt
|
||||||
|
echo " - gofmt..."
|
||||||
|
GOFMT_OUTPUT=$(gofmt -l $STAGED_GO_FILES 2>&1 || true)
|
||||||
|
if [ -n "$GOFMT_OUTPUT" ]; then
|
||||||
|
echo -e "${YELLOW}Auto-fixing gofmt issues...${NC}"
|
||||||
|
gofmt -w $STAGED_GO_FILES
|
||||||
|
git add $STAGED_GO_FILES
|
||||||
|
fi
|
||||||
|
|
||||||
|
# goimports (if available)
|
||||||
|
if command -v goimports &> /dev/null; then
|
||||||
|
echo " - goimports..."
|
||||||
|
GOIMPORTS_OUTPUT=$(goimports -l $STAGED_GO_FILES 2>&1 || true)
|
||||||
|
if [ -n "$GOIMPORTS_OUTPUT" ]; then
|
||||||
|
echo -e "${YELLOW}Auto-fixing goimports issues...${NC}"
|
||||||
|
goimports -w $STAGED_GO_FILES
|
||||||
|
git add $STAGED_GO_FILES
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# golangci-lint (if available)
|
||||||
|
if command -v golangci-lint &> /dev/null; then
|
||||||
|
echo " - golangci-lint..."
|
||||||
|
# Get unique directories with Go files
|
||||||
|
DIRS=$(echo "$STAGED_GO_FILES" | xargs -n1 dirname | sort -u)
|
||||||
|
for dir in $DIRS; do
|
||||||
|
if ! golangci-lint run "$dir/..." --fast 2>/dev/null; then
|
||||||
|
echo -e "${RED}golangci-lint found issues in $dir${NC}"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# go vet
|
||||||
|
echo " - go vet..."
|
||||||
|
if ! go vet ./... 2>/dev/null; then
|
||||||
|
echo -e "${RED}go vet found issues${NC}"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 3. TypeScript/JavaScript Checks
|
||||||
|
# ============================================
|
||||||
|
if [ -n "$STAGED_TS_FILES" ]; then
|
||||||
|
echo "Running TypeScript checks..."
|
||||||
|
|
||||||
|
# Get component directories with TS files
|
||||||
|
TS_DIRS=$(echo "$STAGED_TS_FILES" | xargs -n1 dirname | sort -u | grep -E '^apps/' | cut -d'/' -f1-2 | sort -u || true)
|
||||||
|
|
||||||
|
for dir in $TS_DIRS; do
|
||||||
|
if [ -f "$dir/package.json" ]; then
|
||||||
|
# Use subshell to automatically restore directory on exit
|
||||||
|
(
|
||||||
|
cd "$dir"
|
||||||
|
|
||||||
|
# Get files relative to this component directory
|
||||||
|
COMPONENT_FILES=$(echo "$STAGED_TS_FILES" | grep "^$dir/" | xargs)
|
||||||
|
|
||||||
|
# prettier (if available)
|
||||||
|
if [ -f "node_modules/.bin/prettier" ] || command -v prettier &> /dev/null; then
|
||||||
|
echo " - prettier in $dir..."
|
||||||
|
npx prettier --write $COMPONENT_FILES 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# eslint (if available)
|
||||||
|
if [ -f "node_modules/.bin/eslint" ] || command -v eslint &> /dev/null; then
|
||||||
|
echo " - eslint in $dir..."
|
||||||
|
if ! npx eslint --fix $COMPONENT_FILES 2>/dev/null; then
|
||||||
|
echo -e "${RED}eslint found issues in $dir${NC}"
|
||||||
|
# Note: ERRORS can't propagate from subshell, so we exit with error
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
) || ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Re-add auto-fixed files
|
||||||
|
for file in $STAGED_TS_FILES; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
git add "$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 4. Final Result
|
||||||
|
# ============================================
|
||||||
|
if [ $ERRORS -gt 0 ]; then
|
||||||
|
echo -e "${RED}Pre-commit checks failed with $ERRORS error(s)${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Pre-commit checks passed!${NC}"
|
||||||
|
exit 0
|
||||||
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Binaries
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
bin/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool
|
||||||
|
*.out
|
||||||
|
coverage.html
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
# Go workspace file (local only)
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.env
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
.npm/
|
||||||
|
|
||||||
|
# Shared packages
|
||||||
|
packages/*/node_modules/
|
||||||
|
packages/*/dist/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
build/
|
||||||
|
.next/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
25
.golangci.yml
Normal file
25
.golangci.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
modules-download-mode: readonly
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- errcheck
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- unused
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gofmt:
|
||||||
|
simplify: true
|
||||||
|
goimports:
|
||||||
|
local-prefixes: git.threesix.ai/jordan/slack-auth-1770277653
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-use-default: false
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
47
.woodpecker.yml
Normal file
47
.woodpecker.yml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# CI/CD Pipeline for slack-auth-1770277653
|
||||||
|
# Components will add their build steps below the marker
|
||||||
|
|
||||||
|
clone:
|
||||||
|
git:
|
||||||
|
image: woodpeckerci/plugin-git
|
||||||
|
settings:
|
||||||
|
depth: 1
|
||||||
|
|
||||||
|
steps:
|
||||||
|
deps:
|
||||||
|
image: golang:1.23
|
||||||
|
commands:
|
||||||
|
- go work sync
|
||||||
|
- |
|
||||||
|
for dir in services/*/; do
|
||||||
|
if [ -f "$dir/go.mod" ]; then
|
||||||
|
(cd "$dir" && go mod tidy)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
- |
|
||||||
|
for dir in workers/*/; do
|
||||||
|
if [ -f "$dir/go.mod" ]; then
|
||||||
|
(cd "$dir" && go mod tidy)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
- |
|
||||||
|
for dir in cli/*/; do
|
||||||
|
if [ -f "$dir/go.mod" ]; then
|
||||||
|
(cd "$dir" && go mod tidy)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: push
|
||||||
|
|
||||||
|
# COMPONENT_STEPS_BELOW
|
||||||
|
# Do not remove the marker above - component steps are inserted here
|
||||||
|
|
||||||
|
verify:
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
commands:
|
||||||
|
- echo "Pipeline complete for slack-auth-1770277653"
|
||||||
|
- kubectl get deployments -n projects -l app=slack-auth-1770277653 --no-headers || true
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: push
|
||||||
79
CLAUDE.md
Normal file
79
CLAUDE.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# slack-auth-1770277653
|
||||||
|
|
||||||
|
Slack Path 1: Authentication
|
||||||
|
|
||||||
|
## Find Your Guide
|
||||||
|
|
||||||
|
| If you need to... | Read this |
|
||||||
|
|-------------------|-----------|
|
||||||
|
| **Set up local dev** | [local/setup.md](.claude/guides/local/setup.md) |
|
||||||
|
| **Build a feature** | [feature-development.md](.claude/guides/feature-development.md) |
|
||||||
|
| **Backend API patterns** | [backend/api-patterns.md](.claude/guides/backend/api-patterns.md) |
|
||||||
|
| **Frontend design system** | [frontend/design-system.md](.claude/guides/frontend/design-system.md) |
|
||||||
|
| **Deploy** | [ops/deploying.md](.claude/guides/ops/deploying.md) |
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start local dev
|
||||||
|
./scripts/dev.sh
|
||||||
|
|
||||||
|
# Run quality checks
|
||||||
|
./scripts/quality.sh
|
||||||
|
|
||||||
|
# List all components
|
||||||
|
./scripts/discover.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- **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`.
|
||||||
|
- **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.
|
||||||
|
- **OpenAPI first:** Document endpoints in `spec.go` using `openapi.*` helpers. Mount with `application.EnableDocs(spec)`.
|
||||||
|
- **CSS variables:** All UI components use CSS custom properties (`var(--background)`, `var(--accent)`, etc.). Never hardcode colors.
|
||||||
|
- **Monorepo imports:** Go packages from `git.threesix.ai/jordan/slack-auth-1770277653/pkg/*`, TypeScript from `@slack-auth-1770277653/*`.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
slack-auth-1770277653/
|
||||||
|
├── services/ # Go API services (port 8001+)
|
||||||
|
├── workers/ # Background workers (no port)
|
||||||
|
├── apps/ # Frontend applications (port 3001+)
|
||||||
|
├── cli/ # CLI tools (no port)
|
||||||
|
├── packages/ # Shared TypeScript packages
|
||||||
|
│ ├── ui/ # UI components (@slack-auth-1770277653/ui)
|
||||||
|
│ ├── layout/ # Dashboard layout (@slack-auth-1770277653/layout)
|
||||||
|
│ ├── auth/ # Auth provider (@slack-auth-1770277653/auth)
|
||||||
|
│ ├── api-client/ # Typed API client (@slack-auth-1770277653/api-client)
|
||||||
|
│ └── logger/ # HTTP/console logger (@slack-auth-1770277653/logger)
|
||||||
|
├── pkg/ # Shared Go packages
|
||||||
|
│ ├── app/ # Service bootstrapper (Wrap, Bind, Health)
|
||||||
|
│ ├── chassis/ # Facade re-exporting app types
|
||||||
|
│ ├── openapi/ # OpenAPI 3.0 spec builder + Scalar docs
|
||||||
|
│ ├── httperror/ # Typed HTTP errors
|
||||||
|
│ ├── httpresponse/ # Response envelope helpers
|
||||||
|
│ ├── httpvalidation/ # Struct validation
|
||||||
|
│ ├── middleware/ # RequestID, CORS, Recovery, Logger
|
||||||
|
│ ├── auth/ # JWT, API key, middleware
|
||||||
|
│ ├── config/ # Viper-based configuration
|
||||||
|
│ ├── httpclient/ # Resilient HTTP client
|
||||||
|
│ └── logging/ # slog wrapper
|
||||||
|
└── scripts/ # Development & CI scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
| Slot | Language | Port Range | Purpose |
|
||||||
|
|------|----------|------------|---------|
|
||||||
|
| services/ | Go | 8001+ | REST APIs, backend services |
|
||||||
|
| workers/ | Go | none | Background jobs, queue consumers |
|
||||||
|
| apps/ | TypeScript | 3001+ | React, Next.js, Astro frontends |
|
||||||
|
| cli/ | Go | none | CLI tools, scripts |
|
||||||
|
| packages/ | TypeScript | none | Shared frontend packages |
|
||||||
|
| pkg/ | Go | none | Shared backend packages |
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
<!-- Components will be listed here as they're added -->
|
||||||
2
Procfile
Normal file
2
Procfile
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Local development processes
|
||||||
|
# Components will be added below as they're created
|
||||||
55
README.md
Normal file
55
README.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# slack-auth-1770277653
|
||||||
|
|
||||||
|
Slack Path 1: Authentication
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repo
|
||||||
|
git clone https://git.threesix.ai/jordan/slack-auth-1770277653.git
|
||||||
|
cd slack-auth-1770277653
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
./scripts/install.sh
|
||||||
|
|
||||||
|
# Start local development
|
||||||
|
./scripts/dev.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
slack-auth-1770277653/
|
||||||
|
├── services/ # Go API services
|
||||||
|
├── workers/ # Background workers
|
||||||
|
├── apps/ # Frontend applications
|
||||||
|
├── cli/ # CLI tools
|
||||||
|
├── packages/ # Shared TypeScript packages
|
||||||
|
├── pkg/ # Shared Go packages
|
||||||
|
└── scripts/ # Development scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
| Script | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `./scripts/dev.sh` | Start local development environment |
|
||||||
|
| `./scripts/install.sh` | Install all dependencies |
|
||||||
|
| `./scripts/quality.sh` | Run quality checks on all components |
|
||||||
|
| `./scripts/discover.sh` | List all components in the monorepo |
|
||||||
|
|
||||||
|
## Adding Components
|
||||||
|
|
||||||
|
Components are added via the rdev API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add a Go service
|
||||||
|
curl -X POST $RDEV_API_URL/projects/slack-auth-1770277653/components \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-d '{"type": "service", "name": "auth-api"}'
|
||||||
|
|
||||||
|
# Add a React app
|
||||||
|
curl -X POST $RDEV_API_URL/projects/slack-auth-1770277653/components \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-d '{"type": "app", "name": "dashboard", "template": "app-react"}'
|
||||||
|
```
|
||||||
1
apps/.gitkeep
Normal file
1
apps/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Frontend applications go here
|
||||||
1
cli/.gitkeep
Normal file
1
cli/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
# CLI tools go here
|
||||||
24
docker-compose.yml
Normal file
24
docker-compose.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: dev
|
||||||
|
POSTGRES_PASSWORD: dev
|
||||||
|
POSTGRES_DB: slack-auth-1770277653
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
4
go.work
Normal file
4
go.work
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
go 1.23
|
||||||
|
|
||||||
|
use ./pkg
|
||||||
|
// Component modules will be added below
|
||||||
12
package.json
Normal file
12
package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "slack-auth-1770277653",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"packageManager": "pnpm@9.15.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "pnpm -r dev",
|
||||||
|
"build": "pnpm -r build",
|
||||||
|
"lint": "pnpm -r lint",
|
||||||
|
"test": "pnpm -r test"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
packages/.gitkeep
Normal file
0
packages/.gitkeep
Normal file
17
packages/api-client/package.json
Normal file
17
packages/api-client/package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "@slack-auth-1770277653/api-client",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"generate": "../scripts/generate-client.sh",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"openapi-typescript": "^7.0.0",
|
||||||
|
"typescript": "^5.5.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
95
packages/api-client/src/client.ts
Normal file
95
packages/api-client/src/client.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* API Client Configuration
|
||||||
|
*/
|
||||||
|
export interface ClientConfig {
|
||||||
|
baseUrl: string;
|
||||||
|
apiKey?: string;
|
||||||
|
bearerToken?: string;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
onError?: (error: Error) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a typed API client
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const client = createClient({
|
||||||
|
* baseUrl: 'https://api.example.com',
|
||||||
|
* apiKey: 'your-api-key',
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* const users = await client.get('/users');
|
||||||
|
* const newUser = await client.post('/users', { name: 'John' });
|
||||||
|
*/
|
||||||
|
export function createClient(config: ClientConfig) {
|
||||||
|
const { baseUrl, apiKey, bearerToken, headers = {}, onError } = config;
|
||||||
|
|
||||||
|
async function request<T>(
|
||||||
|
method: string,
|
||||||
|
path: string,
|
||||||
|
options: {
|
||||||
|
body?: unknown;
|
||||||
|
params?: Record<string, string | number | boolean | undefined>;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
} = {}
|
||||||
|
): Promise<T> {
|
||||||
|
const url = new URL(path, baseUrl);
|
||||||
|
|
||||||
|
// Add query params
|
||||||
|
if (options.params) {
|
||||||
|
for (const [key, value] of Object.entries(options.params)) {
|
||||||
|
if (value !== undefined) {
|
||||||
|
url.searchParams.set(key, String(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build headers
|
||||||
|
const requestHeaders: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...headers,
|
||||||
|
...options.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (apiKey) {
|
||||||
|
requestHeaders['X-API-Key'] = apiKey;
|
||||||
|
}
|
||||||
|
if (bearerToken) {
|
||||||
|
requestHeaders['Authorization'] = `Bearer ${bearerToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method,
|
||||||
|
headers: requestHeaders,
|
||||||
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = new Error(`API error: ${response.status}`);
|
||||||
|
if (onError) {
|
||||||
|
onError(error);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle no-content responses
|
||||||
|
if (response.status === 204) {
|
||||||
|
return undefined as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get: <T>(path: string, params?: Record<string, string | number | boolean | undefined>) =>
|
||||||
|
request<T>('GET', path, { params }),
|
||||||
|
post: <T>(path: string, body?: unknown) =>
|
||||||
|
request<T>('POST', path, { body }),
|
||||||
|
put: <T>(path: string, body?: unknown) =>
|
||||||
|
request<T>('PUT', path, { body }),
|
||||||
|
patch: <T>(path: string, body?: unknown) =>
|
||||||
|
request<T>('PATCH', path, { body }),
|
||||||
|
delete: <T>(path: string) =>
|
||||||
|
request<T>('DELETE', path),
|
||||||
|
};
|
||||||
|
}
|
||||||
3
packages/api-client/src/index.ts
Normal file
3
packages/api-client/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './client';
|
||||||
|
// Note: schema.d.ts is generated by running `pnpm generate`
|
||||||
|
// export type { paths, components, operations } from './schema';
|
||||||
18
packages/api-client/tsconfig.json
Normal file
18
packages/api-client/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": ["ES2020", "DOM"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
22
packages/auth/package.json
Normal file
22
packages/auth/package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "@slack-auth-1770277653/auth",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"build": "tsc",
|
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"typescript": "^5.5.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
286
packages/auth/src/AuthProvider.tsx
Normal file
286
packages/auth/src/AuthProvider.tsx
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { createContext, useContext, useCallback, useMemo, useEffect, useState } from 'react';
|
||||||
|
import type { User, AuthState, LoginCredentials } from './types';
|
||||||
|
|
||||||
|
const TOKEN_STORAGE_KEY = 'auth_token';
|
||||||
|
const USER_STORAGE_KEY = 'auth_user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication context value.
|
||||||
|
*/
|
||||||
|
export interface AuthContextValue extends AuthState {
|
||||||
|
/** Log in with credentials */
|
||||||
|
login: (credentials: LoginCredentials) => Promise<void>;
|
||||||
|
/** Log in with a token directly */
|
||||||
|
loginWithToken: (token: string, user?: User) => void;
|
||||||
|
/** Log out the current user */
|
||||||
|
logout: () => void;
|
||||||
|
/** Get the current access token */
|
||||||
|
getToken: () => string | null;
|
||||||
|
/** Check if user has a specific role */
|
||||||
|
hasRole: (role: string) => boolean;
|
||||||
|
/** Check if user has a specific scope */
|
||||||
|
hasScope: (scope: string) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextValue | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth provider configuration.
|
||||||
|
*/
|
||||||
|
export interface AuthProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
/** API endpoint for login */
|
||||||
|
loginUrl?: string;
|
||||||
|
/** API endpoint for logout */
|
||||||
|
logoutUrl?: string;
|
||||||
|
/** API endpoint for fetching current user */
|
||||||
|
userUrl?: string;
|
||||||
|
/** Custom login handler */
|
||||||
|
onLogin?: (credentials: LoginCredentials) => Promise<{ token: string; user: User }>;
|
||||||
|
/** Custom logout handler */
|
||||||
|
onLogout?: () => Promise<void>;
|
||||||
|
/** Storage type for persisting auth state */
|
||||||
|
storage?: 'localStorage' | 'sessionStorage' | 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthProvider manages authentication state and provides auth methods.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage
|
||||||
|
* <AuthProvider loginUrl="/api/auth/login">
|
||||||
|
* <App />
|
||||||
|
* </AuthProvider>
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With custom handlers
|
||||||
|
* <AuthProvider
|
||||||
|
* onLogin={async (creds) => {
|
||||||
|
* const res = await myAuthService.login(creds);
|
||||||
|
* return { token: res.token, user: res.user };
|
||||||
|
* }}
|
||||||
|
* >
|
||||||
|
* <App />
|
||||||
|
* </AuthProvider>
|
||||||
|
*/
|
||||||
|
export function AuthProvider({
|
||||||
|
children,
|
||||||
|
loginUrl = '/api/auth/login',
|
||||||
|
logoutUrl = '/api/auth/logout',
|
||||||
|
userUrl = '/api/auth/me',
|
||||||
|
onLogin,
|
||||||
|
onLogout,
|
||||||
|
storage = 'localStorage',
|
||||||
|
}: AuthProviderProps) {
|
||||||
|
const [state, setState] = useState<AuthState>({
|
||||||
|
user: null,
|
||||||
|
isLoading: true,
|
||||||
|
isAuthenticated: false,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get storage implementation
|
||||||
|
const getStorage = useCallback(() => {
|
||||||
|
if (storage === 'none') return null;
|
||||||
|
return storage === 'sessionStorage' ? sessionStorage : localStorage;
|
||||||
|
}, [storage]);
|
||||||
|
|
||||||
|
// Initialize auth state from storage
|
||||||
|
useEffect(() => {
|
||||||
|
const store = getStorage();
|
||||||
|
if (!store) {
|
||||||
|
setState((s) => ({ ...s, isLoading: false }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = store.getItem(TOKEN_STORAGE_KEY);
|
||||||
|
const userJson = store.getItem(USER_STORAGE_KEY);
|
||||||
|
|
||||||
|
if (token && userJson) {
|
||||||
|
try {
|
||||||
|
const user = JSON.parse(userJson) as User;
|
||||||
|
setState({
|
||||||
|
user,
|
||||||
|
isLoading: false,
|
||||||
|
isAuthenticated: true,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Invalid stored data, clear it
|
||||||
|
store.removeItem(TOKEN_STORAGE_KEY);
|
||||||
|
store.removeItem(USER_STORAGE_KEY);
|
||||||
|
setState((s) => ({ ...s, isLoading: false }));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setState((s) => ({ ...s, isLoading: false }));
|
||||||
|
}
|
||||||
|
}, [getStorage]);
|
||||||
|
|
||||||
|
// Login with credentials
|
||||||
|
const login = useCallback(
|
||||||
|
async (credentials: LoginCredentials) => {
|
||||||
|
setState((s) => ({ ...s, isLoading: true, error: null }));
|
||||||
|
|
||||||
|
try {
|
||||||
|
let token: string;
|
||||||
|
let user: User;
|
||||||
|
|
||||||
|
if (onLogin) {
|
||||||
|
// Use custom login handler
|
||||||
|
const result = await onLogin(credentials);
|
||||||
|
token = result.token;
|
||||||
|
user = result.user;
|
||||||
|
} else {
|
||||||
|
// Use default API login
|
||||||
|
const response = await fetch(loginUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(credentials),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(error.message || 'Login failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
token = data.data?.token || data.token;
|
||||||
|
user = data.data?.user || data.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store token and user
|
||||||
|
const store = getStorage();
|
||||||
|
if (store) {
|
||||||
|
store.setItem(TOKEN_STORAGE_KEY, token);
|
||||||
|
store.setItem(USER_STORAGE_KEY, JSON.stringify(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
setState({
|
||||||
|
user,
|
||||||
|
isLoading: false,
|
||||||
|
isAuthenticated: true,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setState({
|
||||||
|
user: null,
|
||||||
|
isLoading: false,
|
||||||
|
isAuthenticated: false,
|
||||||
|
error: error instanceof Error ? error : new Error('Login failed'),
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[loginUrl, onLogin, getStorage]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Login with token directly
|
||||||
|
const loginWithToken = useCallback(
|
||||||
|
(token: string, user?: User) => {
|
||||||
|
const store = getStorage();
|
||||||
|
if (store) {
|
||||||
|
store.setItem(TOKEN_STORAGE_KEY, token);
|
||||||
|
if (user) {
|
||||||
|
store.setItem(USER_STORAGE_KEY, JSON.stringify(user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState({
|
||||||
|
user: user || null,
|
||||||
|
isLoading: false,
|
||||||
|
isAuthenticated: true,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[getStorage]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
const logout = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
if (onLogout) {
|
||||||
|
await onLogout();
|
||||||
|
} else if (logoutUrl) {
|
||||||
|
await fetch(logoutUrl, { method: 'POST' }).catch(() => {});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
const store = getStorage();
|
||||||
|
if (store) {
|
||||||
|
store.removeItem(TOKEN_STORAGE_KEY);
|
||||||
|
store.removeItem(USER_STORAGE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState({
|
||||||
|
user: null,
|
||||||
|
isLoading: false,
|
||||||
|
isAuthenticated: false,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [logoutUrl, onLogout, getStorage]);
|
||||||
|
|
||||||
|
// Get token
|
||||||
|
const getToken = useCallback(() => {
|
||||||
|
const store = getStorage();
|
||||||
|
return store ? store.getItem(TOKEN_STORAGE_KEY) : null;
|
||||||
|
}, [getStorage]);
|
||||||
|
|
||||||
|
// Role check
|
||||||
|
const hasRole = useCallback(
|
||||||
|
(role: string) => {
|
||||||
|
return state.user?.roles?.includes(role) ?? false;
|
||||||
|
},
|
||||||
|
[state.user]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scope check
|
||||||
|
const hasScope = useCallback(
|
||||||
|
(scope: string) => {
|
||||||
|
return state.user?.scopes?.includes(scope) ?? false;
|
||||||
|
},
|
||||||
|
[state.user]
|
||||||
|
);
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
(): AuthContextValue => ({
|
||||||
|
...state,
|
||||||
|
login,
|
||||||
|
loginWithToken,
|
||||||
|
logout,
|
||||||
|
getToken,
|
||||||
|
hasRole,
|
||||||
|
hasScope,
|
||||||
|
}),
|
||||||
|
[state, login, loginWithToken, logout, getToken, hasRole, hasScope]
|
||||||
|
);
|
||||||
|
|
||||||
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to access authentication state and methods.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* function Profile() {
|
||||||
|
* const { user, logout, isAuthenticated } = useAuth();
|
||||||
|
*
|
||||||
|
* if (!isAuthenticated) {
|
||||||
|
* return <LoginForm />;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <div>
|
||||||
|
* <p>Welcome, {user?.name}</p>
|
||||||
|
* <button onClick={logout}>Logout</button>
|
||||||
|
* </div>
|
||||||
|
* );
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export function useAuth(): AuthContextValue {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useAuth must be used within an AuthProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
131
packages/auth/src/ProtectedRoute.tsx
Normal file
131
packages/auth/src/ProtectedRoute.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useAuth } from './AuthProvider';
|
||||||
|
|
||||||
|
export interface ProtectedRouteProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
/** Component to render while loading */
|
||||||
|
fallback?: React.ReactNode;
|
||||||
|
/** Component to render if not authenticated */
|
||||||
|
unauthorized?: React.ReactNode;
|
||||||
|
/** Required role(s) - user must have at least one */
|
||||||
|
roles?: string[];
|
||||||
|
/** Required scope(s) - user must have at least one */
|
||||||
|
scopes?: string[];
|
||||||
|
/** Redirect path for unauthorized access (alternative to unauthorized component) */
|
||||||
|
redirectTo?: string;
|
||||||
|
/** Custom redirect function (e.g., router.push). Falls back to window.location.href. */
|
||||||
|
onRedirect?: (path: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProtectedRoute guards routes that require authentication.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic protection
|
||||||
|
* <Route path="/dashboard" element={
|
||||||
|
* <ProtectedRoute>
|
||||||
|
* <Dashboard />
|
||||||
|
* </ProtectedRoute>
|
||||||
|
* } />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With role requirement
|
||||||
|
* <Route path="/admin" element={
|
||||||
|
* <ProtectedRoute roles={['admin']}>
|
||||||
|
* <AdminPanel />
|
||||||
|
* </ProtectedRoute>
|
||||||
|
* } />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With custom unauthorized view
|
||||||
|
* <ProtectedRoute
|
||||||
|
* unauthorized={<AccessDenied />}
|
||||||
|
* fallback={<LoadingSpinner />}
|
||||||
|
* >
|
||||||
|
* <SecureContent />
|
||||||
|
* </ProtectedRoute>
|
||||||
|
*/
|
||||||
|
export function ProtectedRoute({
|
||||||
|
children,
|
||||||
|
fallback = <DefaultLoading />,
|
||||||
|
unauthorized = <DefaultUnauthorized />,
|
||||||
|
roles,
|
||||||
|
scopes,
|
||||||
|
redirectTo,
|
||||||
|
onRedirect,
|
||||||
|
}: ProtectedRouteProps) {
|
||||||
|
const { isLoading, isAuthenticated, user } = useAuth();
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
if (isLoading) {
|
||||||
|
return <>{fallback}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not authenticated
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
if (redirectTo) {
|
||||||
|
if (onRedirect) {
|
||||||
|
onRedirect(redirectTo);
|
||||||
|
} else {
|
||||||
|
window.location.href = redirectTo;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <>{unauthorized}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check role requirements
|
||||||
|
if (roles && roles.length > 0) {
|
||||||
|
const hasRequiredRole = roles.some((role) => user?.roles?.includes(role));
|
||||||
|
if (!hasRequiredRole) {
|
||||||
|
return <>{unauthorized}</>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check scope requirements
|
||||||
|
if (scopes && scopes.length > 0) {
|
||||||
|
const hasRequiredScope = scopes.some((scope) => user?.scopes?.includes(scope));
|
||||||
|
if (!hasRequiredScope) {
|
||||||
|
return <>{unauthorized}</>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default loading component
|
||||||
|
function DefaultLoading() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
minHeight: '100vh',
|
||||||
|
color: 'var(--text-muted)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default unauthorized component
|
||||||
|
function DefaultUnauthorized() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
minHeight: '100vh',
|
||||||
|
gap: '1rem',
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1 style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>Access Denied</h1>
|
||||||
|
<p style={{ color: 'var(--text-muted)' }}>You don't have permission to view this page.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
packages/auth/src/index.ts
Normal file
3
packages/auth/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { AuthProvider, useAuth, type AuthContextValue } from './AuthProvider';
|
||||||
|
export { ProtectedRoute } from './ProtectedRoute';
|
||||||
|
export type { User, AuthState, LoginCredentials } from './types';
|
||||||
43
packages/auth/src/types.ts
Normal file
43
packages/auth/src/types.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Represents an authenticated user.
|
||||||
|
*/
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
email?: string;
|
||||||
|
name?: string;
|
||||||
|
roles?: string[];
|
||||||
|
scopes?: string[];
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication state.
|
||||||
|
*/
|
||||||
|
export interface AuthState {
|
||||||
|
/** The authenticated user, or null if not authenticated */
|
||||||
|
user: User | null;
|
||||||
|
/** Whether authentication state is being loaded */
|
||||||
|
isLoading: boolean;
|
||||||
|
/** Whether the user is authenticated */
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
/** Any authentication error */
|
||||||
|
error: Error | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login credentials for username/password authentication.
|
||||||
|
*/
|
||||||
|
export interface LoginCredentials {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token response from the authentication API.
|
||||||
|
*/
|
||||||
|
export interface TokenResponse {
|
||||||
|
access_token: string;
|
||||||
|
refresh_token?: string;
|
||||||
|
token_type: string;
|
||||||
|
expires_in: number;
|
||||||
|
}
|
||||||
20
packages/auth/tsconfig.json
Normal file
20
packages/auth/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
26
packages/layout/package.json
Normal file
26
packages/layout/package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "@slack-auth-1770277653/layout",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"build": "tsc",
|
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@slack-auth-1770277653/ui": "workspace:*",
|
||||||
|
"lucide-react": "^0.395.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"typescript": "^5.5.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
78
packages/layout/src/DashboardShell.tsx
Normal file
78
packages/layout/src/DashboardShell.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { cn } from '@slack-auth-1770277653/ui';
|
||||||
|
|
||||||
|
export interface DashboardShellProps {
|
||||||
|
/** Sidebar element to render on the left */
|
||||||
|
sidebar?: React.ReactNode;
|
||||||
|
/** Header element to render at the top */
|
||||||
|
header?: React.ReactNode;
|
||||||
|
/** Main content */
|
||||||
|
children: React.ReactNode;
|
||||||
|
/** Width of the sidebar in pixels (default: 256) */
|
||||||
|
sidebarWidth?: number;
|
||||||
|
/** Height of the header in pixels (default: 64) */
|
||||||
|
headerHeight?: number;
|
||||||
|
/** Additional class names for the main content area */
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DashboardShell provides a standard layout for dashboard applications
|
||||||
|
* with a fixed sidebar, header, and scrollable main content area.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <DashboardShell
|
||||||
|
* sidebar={<AppSidebar />}
|
||||||
|
* header={<AppHeader />}
|
||||||
|
* >
|
||||||
|
* <MainContent />
|
||||||
|
* </DashboardShell>
|
||||||
|
*/
|
||||||
|
export function DashboardShell({
|
||||||
|
sidebar,
|
||||||
|
header,
|
||||||
|
children,
|
||||||
|
sidebarWidth = 256,
|
||||||
|
headerHeight = 64,
|
||||||
|
className,
|
||||||
|
}: DashboardShellProps) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[var(--background)]">
|
||||||
|
{/* Sidebar */}
|
||||||
|
{sidebar && (
|
||||||
|
<aside
|
||||||
|
className="fixed inset-y-0 left-0 z-[var(--z-sticky)] flex flex-col border-r border-[var(--border)] bg-[var(--background-secondary)]"
|
||||||
|
style={{ width: sidebarWidth }}
|
||||||
|
>
|
||||||
|
{sidebar}
|
||||||
|
</aside>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Main area */}
|
||||||
|
<div
|
||||||
|
className="flex flex-col min-h-screen"
|
||||||
|
style={{ marginLeft: sidebar ? sidebarWidth : 0 }}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
{header && (
|
||||||
|
<header
|
||||||
|
className="sticky top-0 z-[var(--z-sticky)] flex items-center border-b border-[var(--border)] bg-[var(--background)]/95 backdrop-blur supports-[backdrop-filter]:bg-[var(--background)]/60"
|
||||||
|
style={{ height: headerHeight }}
|
||||||
|
>
|
||||||
|
{header}
|
||||||
|
</header>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Main content */}
|
||||||
|
<main
|
||||||
|
className={cn(
|
||||||
|
'flex-1 overflow-auto p-6',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
118
packages/layout/src/Header.tsx
Normal file
118
packages/layout/src/Header.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { cn, Button, Input, Search } from '@slack-auth-1770277653/ui';
|
||||||
|
import { Bell, Menu } from 'lucide-react';
|
||||||
|
|
||||||
|
export interface HeaderProps {
|
||||||
|
/** Title to display in the header */
|
||||||
|
title?: string;
|
||||||
|
/** Breadcrumb or secondary navigation element */
|
||||||
|
breadcrumb?: React.ReactNode;
|
||||||
|
/** Whether to show the search input */
|
||||||
|
showSearch?: boolean;
|
||||||
|
/** Placeholder text for search input */
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
/** Search input change handler */
|
||||||
|
onSearch?: (value: string) => void;
|
||||||
|
/** Custom actions to display on the right side */
|
||||||
|
actions?: React.ReactNode;
|
||||||
|
/** User menu or avatar element */
|
||||||
|
userMenu?: React.ReactNode;
|
||||||
|
/** Mobile menu toggle handler */
|
||||||
|
onMenuToggle?: () => void;
|
||||||
|
/** Whether to show the menu toggle button (for mobile) */
|
||||||
|
showMenuToggle?: boolean;
|
||||||
|
/** Additional class names */
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header provides the top navigation bar for dashboard applications.
|
||||||
|
* Supports title, breadcrumbs, search, notifications, and user menu.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <Header
|
||||||
|
* title="Dashboard"
|
||||||
|
* showSearch
|
||||||
|
* onSearch={(value) => console.log(value)}
|
||||||
|
* userMenu={<UserDropdown />}
|
||||||
|
* />
|
||||||
|
*/
|
||||||
|
export function Header({
|
||||||
|
title,
|
||||||
|
breadcrumb,
|
||||||
|
showSearch = false,
|
||||||
|
searchPlaceholder = 'Search...',
|
||||||
|
onSearch,
|
||||||
|
actions,
|
||||||
|
userMenu,
|
||||||
|
onMenuToggle,
|
||||||
|
showMenuToggle = false,
|
||||||
|
className,
|
||||||
|
}: HeaderProps) {
|
||||||
|
const [searchValue, setSearchValue] = React.useState('');
|
||||||
|
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSearchValue(e.target.value);
|
||||||
|
onSearch?.(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('flex items-center justify-between w-full px-6', className)}>
|
||||||
|
{/* Left section */}
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{showMenuToggle && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={onMenuToggle}
|
||||||
|
className="lg:hidden"
|
||||||
|
>
|
||||||
|
<Menu className="h-5 w-5" />
|
||||||
|
<span className="sr-only">Toggle menu</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{breadcrumb ? (
|
||||||
|
<div className="flex items-center">{breadcrumb}</div>
|
||||||
|
) : title ? (
|
||||||
|
<h1 className="text-lg font-semibold">{title}</h1>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Center section - Search */}
|
||||||
|
{showSearch && (
|
||||||
|
<div className="hidden md:flex flex-1 max-w-md mx-8">
|
||||||
|
<div className="relative w-full">
|
||||||
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--text-muted)]" />
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
placeholder={searchPlaceholder}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
className="pl-9 bg-[var(--surface-50)] border-[var(--border-muted)]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Right section */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{actions}
|
||||||
|
|
||||||
|
{/* Notifications */}
|
||||||
|
<Button variant="ghost" size="icon" className="relative">
|
||||||
|
<Bell className="h-5 w-5" />
|
||||||
|
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-[var(--error)]" />
|
||||||
|
<span className="sr-only">Notifications</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* User menu */}
|
||||||
|
{userMenu && (
|
||||||
|
<div className="ml-2 border-l border-[var(--border-muted)] pl-4">
|
||||||
|
{userMenu}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
169
packages/layout/src/Sidebar.tsx
Normal file
169
packages/layout/src/Sidebar.tsx
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { cn } from '@slack-auth-1770277653/ui';
|
||||||
|
import { ChevronRight, type LucideIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
export interface NavItem {
|
||||||
|
/** Display label */
|
||||||
|
label: string;
|
||||||
|
/** URL to navigate to */
|
||||||
|
href: string;
|
||||||
|
/** Icon component from lucide-react */
|
||||||
|
icon?: LucideIcon;
|
||||||
|
/** Whether this item is currently active */
|
||||||
|
active?: boolean;
|
||||||
|
/** Badge text to display */
|
||||||
|
badge?: string;
|
||||||
|
/** Nested items for collapsible sections */
|
||||||
|
children?: NavItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SidebarProps {
|
||||||
|
/** Logo or brand element to display at the top */
|
||||||
|
logo?: React.ReactNode;
|
||||||
|
/** Navigation items */
|
||||||
|
items: NavItem[];
|
||||||
|
/** Footer element (e.g., user menu, settings) */
|
||||||
|
footer?: React.ReactNode;
|
||||||
|
/** Additional class names */
|
||||||
|
className?: string;
|
||||||
|
/** Click handler for navigation items */
|
||||||
|
onNavigate?: (href: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sidebar provides navigation for dashboard applications.
|
||||||
|
* Supports icons, nested items, badges, and active state highlighting.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <Sidebar
|
||||||
|
* logo={<Logo />}
|
||||||
|
* items={[
|
||||||
|
* { label: 'Dashboard', href: '/', icon: Home, active: true },
|
||||||
|
* { label: 'Users', href: '/users', icon: Users },
|
||||||
|
* { label: 'Settings', href: '/settings', icon: Settings },
|
||||||
|
* ]}
|
||||||
|
* footer={<UserMenu />}
|
||||||
|
* />
|
||||||
|
*/
|
||||||
|
export function Sidebar({
|
||||||
|
logo,
|
||||||
|
items,
|
||||||
|
footer,
|
||||||
|
className,
|
||||||
|
onNavigate,
|
||||||
|
}: SidebarProps) {
|
||||||
|
const [expanded, setExpanded] = React.useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
|
const toggleExpanded = (label: string) => {
|
||||||
|
setExpanded((prev) => ({ ...prev, [label]: !prev[label] }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = (item: NavItem, e: React.MouseEvent) => {
|
||||||
|
if (item.children) {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleExpanded(item.label);
|
||||||
|
} else if (onNavigate) {
|
||||||
|
e.preventDefault();
|
||||||
|
onNavigate(item.href);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('flex flex-col h-full', className)}>
|
||||||
|
{/* Logo area */}
|
||||||
|
{logo && (
|
||||||
|
<div className="flex h-16 items-center px-4 border-b border-[var(--border-muted)]">
|
||||||
|
{logo}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Navigation */}
|
||||||
|
<nav className="flex-1 overflow-y-auto py-4">
|
||||||
|
<ul className="space-y-1 px-2">
|
||||||
|
{items.map((item) => (
|
||||||
|
<NavItemComponent
|
||||||
|
key={item.href}
|
||||||
|
item={item}
|
||||||
|
isExpanded={expanded[item.label]}
|
||||||
|
onClick={handleClick}
|
||||||
|
onNavigate={onNavigate}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
{footer && (
|
||||||
|
<div className="border-t border-[var(--border-muted)] p-4">
|
||||||
|
{footer}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NavItemComponentProps {
|
||||||
|
item: NavItem;
|
||||||
|
isExpanded?: boolean;
|
||||||
|
depth?: number;
|
||||||
|
onClick: (item: NavItem, e: React.MouseEvent) => void;
|
||||||
|
onNavigate?: (href: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function NavItemComponent({
|
||||||
|
item,
|
||||||
|
isExpanded = false,
|
||||||
|
depth = 0,
|
||||||
|
onClick,
|
||||||
|
onNavigate,
|
||||||
|
}: NavItemComponentProps) {
|
||||||
|
const Icon = item.icon;
|
||||||
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={item.href}
|
||||||
|
onClick={(e) => onClick(item, e)}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
||||||
|
item.active
|
||||||
|
? 'bg-[var(--surface-200)] text-[var(--text-primary)]'
|
||||||
|
: 'text-[var(--text-secondary)] hover:bg-[var(--surface-100)] hover:text-[var(--text-primary)]',
|
||||||
|
depth > 0 && 'ml-6'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{Icon && <Icon className="h-4 w-4 shrink-0" />}
|
||||||
|
<span className="flex-1">{item.label}</span>
|
||||||
|
{item.badge && (
|
||||||
|
<span className="rounded-full bg-[var(--accent)] px-2 py-0.5 text-xs font-medium text-[var(--accent-foreground)]">
|
||||||
|
{item.badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{hasChildren && (
|
||||||
|
<ChevronRight
|
||||||
|
className={cn(
|
||||||
|
'h-4 w-4 transition-transform',
|
||||||
|
isExpanded && 'rotate-90'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Nested items */}
|
||||||
|
{hasChildren && isExpanded && (
|
||||||
|
<ul className="mt-1 space-y-1">
|
||||||
|
{item.children!.map((child) => (
|
||||||
|
<NavItemComponent
|
||||||
|
key={child.href}
|
||||||
|
item={child}
|
||||||
|
depth={depth + 1}
|
||||||
|
onClick={onClick}
|
||||||
|
onNavigate={onNavigate}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
packages/layout/src/index.ts
Normal file
3
packages/layout/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { DashboardShell, type DashboardShellProps } from './DashboardShell';
|
||||||
|
export { Sidebar, type SidebarProps, type NavItem } from './Sidebar';
|
||||||
|
export { Header, type HeaderProps } from './Header';
|
||||||
20
packages/layout/tsconfig.json
Normal file
20
packages/layout/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
15
packages/logger/package.json
Normal file
15
packages/logger/package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "@slack-auth-1770277653/logger",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.5.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
packages/logger/src/handlers.ts
Normal file
35
packages/logger/src/handlers.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { Logger } from './logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install global error handlers that route uncaught errors to the logger.
|
||||||
|
*
|
||||||
|
* Captures:
|
||||||
|
* - window.onerror (uncaught exceptions)
|
||||||
|
* - window.onunhandledrejection (unhandled promise rejections)
|
||||||
|
*
|
||||||
|
* Call once at app init. Returns a cleanup function.
|
||||||
|
*/
|
||||||
|
export function installGlobalHandlers(logger: Logger): () => void {
|
||||||
|
const onError = (event: ErrorEvent) => {
|
||||||
|
logger.error('Uncaught exception', event.error ?? new Error(event.message), {
|
||||||
|
source: event.filename,
|
||||||
|
line: event.lineno,
|
||||||
|
col: event.colno,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRejection = (event: PromiseRejectionEvent) => {
|
||||||
|
const err = event.reason instanceof Error
|
||||||
|
? event.reason
|
||||||
|
: new Error(String(event.reason));
|
||||||
|
logger.error('Unhandled promise rejection', err);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('error', onError);
|
||||||
|
window.addEventListener('unhandledrejection', onRejection);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('error', onError);
|
||||||
|
window.removeEventListener('unhandledrejection', onRejection);
|
||||||
|
};
|
||||||
|
}
|
||||||
3
packages/logger/src/index.ts
Normal file
3
packages/logger/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { createLogger, Logger } from './logger';
|
||||||
|
export { installGlobalHandlers } from './handlers';
|
||||||
|
export type { LogLevel, LogContext, LogEntry, LoggerConfig, LogTransport } from './types';
|
||||||
170
packages/logger/src/logger.ts
Normal file
170
packages/logger/src/logger.ts
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import type { LogLevel, LogContext, LogEntry, LoggerConfig, LogTransport } from './types';
|
||||||
|
|
||||||
|
const LEVEL_PRIORITY: Record<LogLevel, number> = {
|
||||||
|
debug: 0,
|
||||||
|
info: 1,
|
||||||
|
warn: 2,
|
||||||
|
error: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Default transport: sends batched logs via sendBeacon or fetch. */
|
||||||
|
class HttpTransport implements LogTransport {
|
||||||
|
constructor(private endpoint: string) {}
|
||||||
|
|
||||||
|
send(entries: LogEntry[]): void {
|
||||||
|
const payload = JSON.stringify(entries);
|
||||||
|
|
||||||
|
// sendBeacon is fire-and-forget, works during page unload
|
||||||
|
if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
|
||||||
|
const blob = new Blob([payload], { type: 'application/json' });
|
||||||
|
const sent = navigator.sendBeacon(this.endpoint, blob);
|
||||||
|
if (sent) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to fetch (non-blocking)
|
||||||
|
fetch(this.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: payload,
|
||||||
|
keepalive: true,
|
||||||
|
}).catch(() => {
|
||||||
|
// Silently drop - we don't want logging failures to break the app
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Console transport for development. */
|
||||||
|
class ConsoleTransport implements LogTransport {
|
||||||
|
send(entries: LogEntry[]): void {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const method = entry.level === 'debug' ? 'log' : entry.level;
|
||||||
|
const ctx = Object.keys(entry.context).length > 0 ? entry.context : undefined;
|
||||||
|
if (entry.error) {
|
||||||
|
console[method](`[${entry.level.toUpperCase()}] ${entry.message}`, entry.error, ctx);
|
||||||
|
} else if (ctx) {
|
||||||
|
console[method](`[${entry.level.toUpperCase()}] ${entry.message}`, ctx);
|
||||||
|
} else {
|
||||||
|
console[method](`[${entry.level.toUpperCase()}] ${entry.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
private buffer: LogEntry[] = [];
|
||||||
|
private timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
private transport: LogTransport;
|
||||||
|
private minLevel: number;
|
||||||
|
private baseContext: LogContext;
|
||||||
|
private batchSize: number;
|
||||||
|
private flushInterval: number;
|
||||||
|
|
||||||
|
constructor(config: LoggerConfig) {
|
||||||
|
this.minLevel = LEVEL_PRIORITY[config.level];
|
||||||
|
this.batchSize = config.batchSize ?? 20;
|
||||||
|
this.flushInterval = config.flushInterval ?? 5000;
|
||||||
|
this.baseContext = { service: config.service };
|
||||||
|
|
||||||
|
if (config.endpoint) {
|
||||||
|
this.transport = new HttpTransport(config.endpoint);
|
||||||
|
} else {
|
||||||
|
this.transport = new ConsoleTransport();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startFlushTimer();
|
||||||
|
|
||||||
|
// Flush on page unload
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'hidden') {
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('pagehide', () => this.flush());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a child logger with additional context fields. */
|
||||||
|
withContext(ctx: LogContext): Logger {
|
||||||
|
const child = Object.create(this) as Logger;
|
||||||
|
child.baseContext = { ...this.baseContext, ...ctx };
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(message: string, ctx?: LogContext): void {
|
||||||
|
this.log('debug', message, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
info(message: string, ctx?: LogContext): void {
|
||||||
|
this.log('info', message, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
warn(message: string, ctx?: LogContext): void {
|
||||||
|
this.log('warn', message, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(message: string, error?: Error | unknown, ctx?: LogContext): void {
|
||||||
|
const entry = this.createEntry('error', message, ctx);
|
||||||
|
if (error instanceof Error) {
|
||||||
|
entry.error = {
|
||||||
|
name: error.name,
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
};
|
||||||
|
} else if (error !== undefined) {
|
||||||
|
entry.error = {
|
||||||
|
name: 'UnknownError',
|
||||||
|
message: String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.enqueue(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Force-flush the buffer immediately. */
|
||||||
|
flush(): void {
|
||||||
|
if (this.buffer.length === 0) return;
|
||||||
|
const entries = this.buffer.splice(0);
|
||||||
|
this.transport.send(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stop the flush timer (call on teardown). */
|
||||||
|
destroy(): void {
|
||||||
|
this.flush();
|
||||||
|
if (this.timer) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private log(level: LogLevel, message: string, ctx?: LogContext): void {
|
||||||
|
if (LEVEL_PRIORITY[level] < this.minLevel) return;
|
||||||
|
this.enqueue(this.createEntry(level, message, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
private createEntry(level: LogLevel, message: string, ctx?: LogContext): LogEntry {
|
||||||
|
return {
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
context: { ...this.baseContext, ...ctx },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private enqueue(entry: LogEntry): void {
|
||||||
|
this.buffer.push(entry);
|
||||||
|
if (this.buffer.length >= this.batchSize) {
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private startFlushTimer(): void {
|
||||||
|
if (this.flushInterval > 0 && typeof setInterval !== 'undefined') {
|
||||||
|
this.timer = setInterval(() => this.flush(), this.flushInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a logger instance. */
|
||||||
|
export function createLogger(config: LoggerConfig): Logger {
|
||||||
|
return new Logger(config);
|
||||||
|
}
|
||||||
40
packages/logger/src/types.ts
Normal file
40
packages/logger/src/types.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
||||||
|
|
||||||
|
export interface LogContext {
|
||||||
|
trace_id?: string;
|
||||||
|
request_id?: string;
|
||||||
|
user_id?: string;
|
||||||
|
component?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogEntry {
|
||||||
|
level: LogLevel;
|
||||||
|
message: string;
|
||||||
|
timestamp: string;
|
||||||
|
context: LogContext;
|
||||||
|
error?: {
|
||||||
|
name: string;
|
||||||
|
message: string;
|
||||||
|
stack?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoggerConfig {
|
||||||
|
/** Minimum log level to emit */
|
||||||
|
level: LogLevel;
|
||||||
|
/** Service/app name for log context */
|
||||||
|
service: string;
|
||||||
|
/** Endpoint to send logs to (POST). Omit for console-only. */
|
||||||
|
endpoint?: string;
|
||||||
|
/** Max entries to buffer before flushing (default: 20) */
|
||||||
|
batchSize?: number;
|
||||||
|
/** Max ms to wait before flushing (default: 5000) */
|
||||||
|
flushInterval?: number;
|
||||||
|
/** Install global error/rejection handlers (default: true) */
|
||||||
|
captureGlobalErrors?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogTransport {
|
||||||
|
send(entries: LogEntry[]): void;
|
||||||
|
}
|
||||||
14
packages/logger/tsconfig.json
Normal file
14
packages/logger/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
41
packages/ui/package.json
Normal file
41
packages/ui/package.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "@slack-auth-1770277653/ui",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"import": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"./styles": "./src/styles/tokens.css"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"build": "tsc",
|
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.395.0",
|
||||||
|
"tailwind-merge": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"typescript": "^5.5.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
62
packages/ui/src/components/Alert.tsx
Normal file
62
packages/ui/src/components/Alert.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
import { cn } from '../utils/cn';
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-[var(--text-secondary)]',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-[var(--background)] text-[var(--text-primary)] border-[var(--border)]',
|
||||||
|
destructive:
|
||||||
|
'border-[var(--error-border)] bg-[var(--error-bg)] text-[var(--error)] [&>svg]:text-[var(--error)]',
|
||||||
|
success:
|
||||||
|
'border-[var(--success-border)] bg-[var(--success-bg)] text-[var(--success)] [&>svg]:text-[var(--success)]',
|
||||||
|
warning:
|
||||||
|
'border-[var(--warning-border)] bg-[var(--warning-bg)] text-[var(--warning)] [&>svg]:text-[var(--warning)]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Alert.displayName = 'Alert';
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertTitle.displayName = 'AlertTitle';
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-sm [&_p]:leading-relaxed', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertDescription.displayName = 'AlertDescription';
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription, alertVariants };
|
||||||
42
packages/ui/src/components/Badge.tsx
Normal file
42
packages/ui/src/components/Badge.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
import { cn } from '../utils/cn';
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--accent)] focus:ring-offset-2',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
'border-transparent bg-[var(--accent)] text-[var(--accent-foreground)]',
|
||||||
|
secondary:
|
||||||
|
'border-transparent bg-[var(--surface-200)] text-[var(--text-secondary)]',
|
||||||
|
outline:
|
||||||
|
'border-[var(--border)] text-[var(--text-secondary)]',
|
||||||
|
success:
|
||||||
|
'border-[var(--success-border)] bg-[var(--success-bg)] text-[var(--success)]',
|
||||||
|
warning:
|
||||||
|
'border-[var(--warning-border)] bg-[var(--warning-bg)] text-[var(--warning)]',
|
||||||
|
error:
|
||||||
|
'border-[var(--error-border)] bg-[var(--error-bg)] text-[var(--error)]',
|
||||||
|
info:
|
||||||
|
'border-[var(--info-border)] bg-[var(--info-bg)] text-[var(--info)]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface BadgeProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof badgeVariants> {}
|
||||||
|
|
||||||
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||||
|
return (
|
||||||
|
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge, badgeVariants };
|
||||||
63
packages/ui/src/components/Button.tsx
Normal file
63
packages/ui/src/components/Button.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Slot } from '@radix-ui/react-slot';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
import { cn } from '../utils/cn';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--background)] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
'bg-[var(--accent)] text-[var(--accent-foreground)] hover:bg-[var(--accent-hover)]',
|
||||||
|
destructive:
|
||||||
|
'bg-[var(--error)] text-white hover:bg-[var(--error)]/90',
|
||||||
|
outline:
|
||||||
|
'border border-[var(--border)] bg-transparent hover:bg-[var(--surface-100)] hover:border-[var(--border-hover)]',
|
||||||
|
secondary:
|
||||||
|
'bg-[var(--surface-200)] text-[var(--text-primary)] hover:bg-[var(--surface-300)]',
|
||||||
|
ghost:
|
||||||
|
'hover:bg-[var(--surface-100)] hover:text-[var(--text-primary)]',
|
||||||
|
link: 'text-[var(--accent)] underline-offset-4 hover:underline',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: 'h-9 px-4 py-2',
|
||||||
|
sm: 'h-8 rounded-md px-3 text-xs',
|
||||||
|
lg: 'h-10 rounded-md px-8',
|
||||||
|
icon: 'h-9 w-9',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, loading, children, disabled, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : 'button';
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
disabled={disabled || loading}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{loading && <Loader2 className="animate-spin" />}
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
|
export { Button, buttonVariants };
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user