oh-my-customcode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +287 -0
- package/dist/cli/index.js +13299 -0
- package/dist/index.js +927 -0
- package/package.json +74 -0
- package/templates/.claude/contexts/dev.md +20 -0
- package/templates/.claude/contexts/ecomode.md +63 -0
- package/templates/.claude/contexts/index.yaml +41 -0
- package/templates/.claude/contexts/research.md +28 -0
- package/templates/.claude/contexts/review.md +23 -0
- package/templates/.claude/hooks/hooks.json +185 -0
- package/templates/.claude/hooks/hud/index.yaml +27 -0
- package/templates/.claude/hooks/hud/update-status.sh +32 -0
- package/templates/.claude/hooks/index.yaml +46 -0
- package/templates/.claude/hooks/memory-persistence/pre-compact.sh +37 -0
- package/templates/.claude/hooks/memory-persistence/session-end.sh +64 -0
- package/templates/.claude/hooks/memory-persistence/session-start.sh +41 -0
- package/templates/.claude/hooks/strategic-compact/suggest-compact.sh +50 -0
- package/templates/.claude/install-hooks.sh +100 -0
- package/templates/.claude/rules/MAY-optimization.md +93 -0
- package/templates/.claude/rules/MUST-agent-design.md +107 -0
- package/templates/.claude/rules/MUST-agent-identification.md +108 -0
- package/templates/.claude/rules/MUST-continuous-improvement.md +132 -0
- package/templates/.claude/rules/MUST-intent-transparency.md +199 -0
- package/templates/.claude/rules/MUST-language-policy.md +62 -0
- package/templates/.claude/rules/MUST-orchestrator-coordination.md +266 -0
- package/templates/.claude/rules/MUST-parallel-execution.md +341 -0
- package/templates/.claude/rules/MUST-permissions.md +84 -0
- package/templates/.claude/rules/MUST-safety.md +69 -0
- package/templates/.claude/rules/MUST-sync-verification.md +219 -0
- package/templates/.claude/rules/MUST-tool-identification.md +112 -0
- package/templates/.claude/rules/SHOULD-ecomode.md +145 -0
- package/templates/.claude/rules/SHOULD-error-handling.md +102 -0
- package/templates/.claude/rules/SHOULD-hud-statusline.md +89 -0
- package/templates/.claude/rules/SHOULD-interaction.md +103 -0
- package/templates/.claude/rules/SHOULD-memory-integration.md +114 -0
- package/templates/.claude/rules/SHOULD-pipeline-mode.md +165 -0
- package/templates/.claude/rules/index.yaml +125 -0
- package/templates/.claude/uninstall-hooks.sh +52 -0
- package/templates/CLAUDE.md.en +259 -0
- package/templates/CLAUDE.md.ko +259 -0
- package/templates/agents/index.yaml +237 -0
- package/templates/agents/infra-engineer/aws-expert/AGENT.md +47 -0
- package/templates/agents/infra-engineer/aws-expert/index.yaml +27 -0
- package/templates/agents/infra-engineer/docker-expert/AGENT.md +47 -0
- package/templates/agents/infra-engineer/docker-expert/index.yaml +27 -0
- package/templates/agents/manager/creator/AGENT.md +274 -0
- package/templates/agents/manager/creator/index.yaml +66 -0
- package/templates/agents/manager/gitnerd/AGENT.md +91 -0
- package/templates/agents/manager/gitnerd/index.yaml +55 -0
- package/templates/agents/manager/sauron/AGENT.md +153 -0
- package/templates/agents/manager/sauron/index.yaml +52 -0
- package/templates/agents/manager/supplier/AGENT.md +142 -0
- package/templates/agents/manager/supplier/index.yaml +31 -0
- package/templates/agents/manager/sync-checker/AGENT.md +34 -0
- package/templates/agents/manager/sync-checker/index.yaml +32 -0
- package/templates/agents/manager/updater/AGENT.md +125 -0
- package/templates/agents/manager/updater/index.yaml +31 -0
- package/templates/agents/orchestrator/dev-lead/AGENT.md +116 -0
- package/templates/agents/orchestrator/dev-lead/index.yaml +73 -0
- package/templates/agents/orchestrator/planner/AGENT.md +102 -0
- package/templates/agents/orchestrator/planner/index.yaml +38 -0
- package/templates/agents/orchestrator/qa-lead/AGENT.md +92 -0
- package/templates/agents/orchestrator/qa-lead/index.yaml +40 -0
- package/templates/agents/orchestrator/secretary/AGENT.md +132 -0
- package/templates/agents/orchestrator/secretary/index.yaml +55 -0
- package/templates/agents/qa-team/qa-engineer/AGENT.md +98 -0
- package/templates/agents/qa-team/qa-engineer/index.yaml +59 -0
- package/templates/agents/qa-team/qa-planner/AGENT.md +75 -0
- package/templates/agents/qa-team/qa-planner/index.yaml +47 -0
- package/templates/agents/qa-team/qa-writer/AGENT.md +98 -0
- package/templates/agents/qa-team/qa-writer/index.yaml +44 -0
- package/templates/agents/sw-architect/documenter/AGENT.md +120 -0
- package/templates/agents/sw-architect/documenter/index.yaml +39 -0
- package/templates/agents/sw-architect/speckit-agent/AGENT.md +127 -0
- package/templates/agents/sw-architect/speckit-agent/index.yaml +78 -0
- package/templates/agents/sw-engineer/backend/express-expert/AGENT.md +132 -0
- package/templates/agents/sw-engineer/backend/express-expert/index.yaml +36 -0
- package/templates/agents/sw-engineer/backend/fastapi-expert/AGENT.md +47 -0
- package/templates/agents/sw-engineer/backend/fastapi-expert/index.yaml +27 -0
- package/templates/agents/sw-engineer/backend/go-backend-expert/AGENT.md +47 -0
- package/templates/agents/sw-engineer/backend/go-backend-expert/index.yaml +27 -0
- package/templates/agents/sw-engineer/backend/nestjs-expert/AGENT.md +107 -0
- package/templates/agents/sw-engineer/backend/nestjs-expert/index.yaml +43 -0
- package/templates/agents/sw-engineer/backend/springboot-expert/AGENT.md +103 -0
- package/templates/agents/sw-engineer/backend/springboot-expert/index.yaml +69 -0
- package/templates/agents/sw-engineer/frontend/svelte-agent/AGENT.md +71 -0
- package/templates/agents/sw-engineer/frontend/svelte-agent/index.yaml +41 -0
- package/templates/agents/sw-engineer/frontend/vercel-agent/AGENT.md +67 -0
- package/templates/agents/sw-engineer/frontend/vercel-agent/index.yaml +43 -0
- package/templates/agents/sw-engineer/frontend/vuejs-agent/AGENT.md +71 -0
- package/templates/agents/sw-engineer/frontend/vuejs-agent/index.yaml +48 -0
- package/templates/agents/sw-engineer/language/golang-expert/AGENT.md +47 -0
- package/templates/agents/sw-engineer/language/golang-expert/index.yaml +27 -0
- package/templates/agents/sw-engineer/language/java21-expert/AGENT.md +122 -0
- package/templates/agents/sw-engineer/language/java21-expert/index.yaml +51 -0
- package/templates/agents/sw-engineer/language/kotlin-expert/AGENT.md +47 -0
- package/templates/agents/sw-engineer/language/kotlin-expert/index.yaml +27 -0
- package/templates/agents/sw-engineer/language/python-expert/AGENT.md +47 -0
- package/templates/agents/sw-engineer/language/python-expert/index.yaml +27 -0
- package/templates/agents/sw-engineer/language/rust-expert/AGENT.md +47 -0
- package/templates/agents/sw-engineer/language/rust-expert/index.yaml +27 -0
- package/templates/agents/sw-engineer/language/typescript-expert/AGENT.md +47 -0
- package/templates/agents/sw-engineer/language/typescript-expert/index.yaml +27 -0
- package/templates/agents/sw-engineer/tooling/bun-expert/AGENT.md +73 -0
- package/templates/agents/sw-engineer/tooling/bun-expert/index.yaml +46 -0
- package/templates/agents/sw-engineer/tooling/npm-expert/AGENT.md +160 -0
- package/templates/agents/sw-engineer/tooling/npm-expert/index.yaml +45 -0
- package/templates/agents/sw-engineer/tooling/optimizer/AGENT.md +170 -0
- package/templates/agents/sw-engineer/tooling/optimizer/index.yaml +45 -0
- package/templates/agents/system/memory-keeper/AGENT.md +126 -0
- package/templates/agents/system/memory-keeper/index.yaml +45 -0
- package/templates/agents/system/naggy/AGENT.md +72 -0
- package/templates/agents/system/naggy/index.yaml +35 -0
- package/templates/commands/COMMANDS.md +136 -0
- package/templates/commands/creator/agent.md +121 -0
- package/templates/commands/dev/refactor.md +126 -0
- package/templates/commands/dev/review.md +82 -0
- package/templates/commands/git/branch.yaml +8 -0
- package/templates/commands/git/commit.yaml +4 -0
- package/templates/commands/git/pr.yaml +4 -0
- package/templates/commands/git/status.yaml +4 -0
- package/templates/commands/git/sync.yaml +4 -0
- package/templates/commands/index.yaml +225 -0
- package/templates/commands/intent/explain.md +144 -0
- package/templates/commands/memory/recall.md +164 -0
- package/templates/commands/memory/save.md +128 -0
- package/templates/commands/naggy/add.yaml +8 -0
- package/templates/commands/naggy/done.yaml +8 -0
- package/templates/commands/naggy/list.yaml +4 -0
- package/templates/commands/naggy/priority.yaml +11 -0
- package/templates/commands/naggy/remind.yaml +4 -0
- package/templates/commands/npm/audit.yaml +62 -0
- package/templates/commands/npm/publish.yaml +52 -0
- package/templates/commands/npm/version.yaml +62 -0
- package/templates/commands/optimize/analyze.yaml +34 -0
- package/templates/commands/optimize/bundle.yaml +50 -0
- package/templates/commands/optimize/report.yaml +56 -0
- package/templates/commands/pipeline/list.md +81 -0
- package/templates/commands/pipeline/run.md +127 -0
- package/templates/commands/sauron/quick.yaml +4 -0
- package/templates/commands/sauron/report.yaml +4 -0
- package/templates/commands/sauron/watch.yaml +4 -0
- package/templates/commands/supplier/audit.md +133 -0
- package/templates/commands/supplier/fix.md +121 -0
- package/templates/commands/sync/agents.yaml +4 -0
- package/templates/commands/sync/check.yaml +4 -0
- package/templates/commands/sync/commands.yaml +4 -0
- package/templates/commands/sync/docs.yaml +4 -0
- package/templates/commands/sync/fix.yaml +4 -0
- package/templates/commands/system/help.md +137 -0
- package/templates/commands/system/lists.md +86 -0
- package/templates/commands/system/status.md +163 -0
- package/templates/commands/updater/docs.md +165 -0
- package/templates/commands/updater/external.md +214 -0
- package/templates/guides/aws/common-patterns.md +169 -0
- package/templates/guides/aws/index.yaml +26 -0
- package/templates/guides/aws/well-architected.md +143 -0
- package/templates/guides/claude-code/01-overview.md +42 -0
- package/templates/guides/claude-code/03-tools.md +107 -0
- package/templates/guides/claude-code/04-agent-skills.md +90 -0
- package/templates/guides/claude-code/05-agent-sdk.md +129 -0
- package/templates/guides/claude-code/06-mcp.md +165 -0
- package/templates/guides/claude-code/07-prompt-engineering.md +100 -0
- package/templates/guides/claude-code/08-testing.md +58 -0
- package/templates/guides/claude-code/09-guardrails.md +80 -0
- package/templates/guides/claude-code/10-monitoring.md +89 -0
- package/templates/guides/claude-code/index.yaml +51 -0
- package/templates/guides/docker/compose-best-practices.md +284 -0
- package/templates/guides/docker/dockerfile-best-practices.md +262 -0
- package/templates/guides/docker/index.yaml +26 -0
- package/templates/guides/fastapi/best-practices.md +232 -0
- package/templates/guides/fastapi/index.yaml +21 -0
- package/templates/guides/go-backend/index.yaml +26 -0
- package/templates/guides/go-backend/project-layout.md +243 -0
- package/templates/guides/go-backend/uber-style.md +212 -0
- package/templates/guides/golang/concurrency.md +282 -0
- package/templates/guides/golang/effective-go.md +309 -0
- package/templates/guides/golang/error-handling.md +250 -0
- package/templates/guides/golang/index.yaml +27 -0
- package/templates/guides/index.yaml +101 -0
- package/templates/guides/kotlin/coding-conventions.md +247 -0
- package/templates/guides/kotlin/idioms.md +234 -0
- package/templates/guides/kotlin/index.yaml +26 -0
- package/templates/guides/python/index.yaml +26 -0
- package/templates/guides/python/pep8-style-guide.md +202 -0
- package/templates/guides/python/zen-of-python.md +79 -0
- package/templates/guides/rust/error-handling.md +262 -0
- package/templates/guides/rust/index.yaml +26 -0
- package/templates/guides/rust/ownership.md +180 -0
- package/templates/guides/springboot/best-practices.md +361 -0
- package/templates/guides/springboot/index.yaml +22 -0
- package/templates/guides/typescript/advanced-types.md +225 -0
- package/templates/guides/typescript/index.yaml +26 -0
- package/templates/guides/typescript/type-system.md +219 -0
- package/templates/guides/web-design/accessibility.md +66 -0
- package/templates/guides/web-design/index.yaml +20 -0
- package/templates/guides/web-design/performance.md +102 -0
- package/templates/pipelines/examples/code-review.yaml +66 -0
- package/templates/pipelines/index.yaml +18 -0
- package/templates/pipelines/templates/pipeline-template.yaml +50 -0
- package/templates/skills/backend/fastapi-best-practices/SKILL.md +269 -0
- package/templates/skills/backend/fastapi-best-practices/index.yaml +25 -0
- package/templates/skills/backend/go-backend-best-practices/SKILL.md +337 -0
- package/templates/skills/backend/go-backend-best-practices/index.yaml +26 -0
- package/templates/skills/backend/springboot-best-practices/SKILL.md +356 -0
- package/templates/skills/backend/springboot-best-practices/index.yaml +27 -0
- package/templates/skills/development/go-best-practices/SKILL.md +202 -0
- package/templates/skills/development/go-best-practices/index.yaml +25 -0
- package/templates/skills/development/kotlin-best-practices/SKILL.md +255 -0
- package/templates/skills/development/kotlin-best-practices/index.yaml +27 -0
- package/templates/skills/development/python-best-practices/SKILL.md +221 -0
- package/templates/skills/development/python-best-practices/index.yaml +25 -0
- package/templates/skills/development/react-best-practices/SKILL.md +100 -0
- package/templates/skills/development/react-best-practices/index.yaml +39 -0
- package/templates/skills/development/rust-best-practices/SKILL.md +266 -0
- package/templates/skills/development/rust-best-practices/index.yaml +26 -0
- package/templates/skills/development/typescript-best-practices/SKILL.md +320 -0
- package/templates/skills/development/typescript-best-practices/index.yaml +28 -0
- package/templates/skills/development/vercel-deploy/SKILL.md +73 -0
- package/templates/skills/development/vercel-deploy/index.yaml +30 -0
- package/templates/skills/development/web-design-guidelines/SKILL.md +117 -0
- package/templates/skills/development/web-design-guidelines/index.yaml +34 -0
- package/templates/skills/index.yaml +129 -0
- package/templates/skills/infrastructure/aws-best-practices/SKILL.md +279 -0
- package/templates/skills/infrastructure/aws-best-practices/index.yaml +27 -0
- package/templates/skills/infrastructure/docker-best-practices/SKILL.md +274 -0
- package/templates/skills/infrastructure/docker-best-practices/index.yaml +26 -0
- package/templates/skills/orchestration/intent-detection/SKILL.md +214 -0
- package/templates/skills/orchestration/intent-detection/index.yaml +30 -0
- package/templates/skills/orchestration/intent-detection/patterns/agent-triggers.yaml +333 -0
- package/templates/skills/orchestration/pipeline-execution/SKILL.md +188 -0
- package/templates/skills/orchestration/pipeline-execution/index.yaml +27 -0
- package/templates/skills/system/memory-management/SKILL.md +194 -0
- package/templates/skills/system/memory-management/index.yaml +30 -0
- package/templates/skills/system/result-aggregation/SKILL.md +163 -0
- package/templates/skills/system/result-aggregation/index.yaml +36 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Standard Go Project Layout
|
|
2
|
+
|
|
3
|
+
> Source: https://github.com/golang-standards/project-layout
|
|
4
|
+
|
|
5
|
+
## Directory Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
project/
|
|
9
|
+
├── cmd/ # Main applications
|
|
10
|
+
│ ├── server/
|
|
11
|
+
│ │ └── main.go
|
|
12
|
+
│ └── worker/
|
|
13
|
+
│ └── main.go
|
|
14
|
+
├── internal/ # Private code
|
|
15
|
+
│ ├── handler/
|
|
16
|
+
│ ├── service/
|
|
17
|
+
│ ├── repository/
|
|
18
|
+
│ ├── model/
|
|
19
|
+
│ └── config/
|
|
20
|
+
├── pkg/ # Public library code
|
|
21
|
+
│ └── validator/
|
|
22
|
+
├── api/ # API definitions
|
|
23
|
+
│ ├── openapi.yaml
|
|
24
|
+
│ └── proto/
|
|
25
|
+
├── configs/ # Configuration files
|
|
26
|
+
│ └── config.yaml
|
|
27
|
+
├── scripts/ # Build scripts
|
|
28
|
+
│ └── build.sh
|
|
29
|
+
├── test/ # Additional test data
|
|
30
|
+
│ └── testdata/
|
|
31
|
+
├── docs/ # Documentation
|
|
32
|
+
├── Dockerfile
|
|
33
|
+
├── Makefile
|
|
34
|
+
├── go.mod
|
|
35
|
+
└── go.sum
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Directory Descriptions
|
|
39
|
+
|
|
40
|
+
### `/cmd`
|
|
41
|
+
|
|
42
|
+
Main applications for this project. Each application has its own subdirectory.
|
|
43
|
+
|
|
44
|
+
```go
|
|
45
|
+
// cmd/server/main.go
|
|
46
|
+
package main
|
|
47
|
+
|
|
48
|
+
import (
|
|
49
|
+
"log"
|
|
50
|
+
"myapp/internal/config"
|
|
51
|
+
"myapp/internal/handler"
|
|
52
|
+
"myapp/internal/service"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
func main() {
|
|
56
|
+
cfg, err := config.Load()
|
|
57
|
+
if err != nil {
|
|
58
|
+
log.Fatal(err)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
svc := service.New(cfg)
|
|
62
|
+
h := handler.New(svc)
|
|
63
|
+
|
|
64
|
+
log.Fatal(h.ListenAndServe(cfg.Addr))
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `/internal`
|
|
69
|
+
|
|
70
|
+
Private application and library code. Not importable by other projects.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
internal/
|
|
74
|
+
├── handler/ # HTTP/gRPC handlers
|
|
75
|
+
│ └── user.go
|
|
76
|
+
├── service/ # Business logic
|
|
77
|
+
│ └── user.go
|
|
78
|
+
├── repository/ # Data access
|
|
79
|
+
│ └── user.go
|
|
80
|
+
├── model/ # Domain models
|
|
81
|
+
│ └── user.go
|
|
82
|
+
└── config/ # Configuration
|
|
83
|
+
└── config.go
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `/pkg`
|
|
87
|
+
|
|
88
|
+
Library code safe for external use.
|
|
89
|
+
|
|
90
|
+
```go
|
|
91
|
+
// pkg/validator/validator.go
|
|
92
|
+
package validator
|
|
93
|
+
|
|
94
|
+
func ValidateEmail(email string) bool {
|
|
95
|
+
// validation logic
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `/api`
|
|
100
|
+
|
|
101
|
+
API definitions (OpenAPI/Swagger, Protocol Buffers).
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
# api/openapi.yaml
|
|
105
|
+
openapi: "3.0.0"
|
|
106
|
+
info:
|
|
107
|
+
title: "My API"
|
|
108
|
+
version: "1.0.0"
|
|
109
|
+
paths:
|
|
110
|
+
/users:
|
|
111
|
+
get:
|
|
112
|
+
summary: "List users"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Common Patterns
|
|
116
|
+
|
|
117
|
+
### Application Structure
|
|
118
|
+
|
|
119
|
+
```go
|
|
120
|
+
// internal/app/app.go
|
|
121
|
+
type App struct {
|
|
122
|
+
config *config.Config
|
|
123
|
+
db *sql.DB
|
|
124
|
+
cache *redis.Client
|
|
125
|
+
handler *handler.Handler
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func New(cfg *config.Config) (*App, error) {
|
|
129
|
+
db, err := sql.Open("postgres", cfg.DatabaseURL)
|
|
130
|
+
if err != nil {
|
|
131
|
+
return nil, fmt.Errorf("open db: %w", err)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
cache := redis.NewClient(&redis.Options{
|
|
135
|
+
Addr: cfg.RedisURL,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
repo := repository.New(db)
|
|
139
|
+
svc := service.New(repo, cache)
|
|
140
|
+
h := handler.New(svc)
|
|
141
|
+
|
|
142
|
+
return &App{
|
|
143
|
+
config: cfg,
|
|
144
|
+
db: db,
|
|
145
|
+
cache: cache,
|
|
146
|
+
handler: h,
|
|
147
|
+
}, nil
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func (a *App) Run() error {
|
|
151
|
+
return http.ListenAndServe(a.config.Addr, a.handler.Router())
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
func (a *App) Shutdown(ctx context.Context) error {
|
|
155
|
+
if err := a.db.Close(); err != nil {
|
|
156
|
+
return err
|
|
157
|
+
}
|
|
158
|
+
return a.cache.Close()
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Main Entry Point
|
|
163
|
+
|
|
164
|
+
```go
|
|
165
|
+
// cmd/server/main.go
|
|
166
|
+
package main
|
|
167
|
+
|
|
168
|
+
import (
|
|
169
|
+
"context"
|
|
170
|
+
"log/slog"
|
|
171
|
+
"os"
|
|
172
|
+
"os/signal"
|
|
173
|
+
"syscall"
|
|
174
|
+
|
|
175
|
+
"myapp/internal/app"
|
|
176
|
+
"myapp/internal/config"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
func main() {
|
|
180
|
+
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
|
181
|
+
slog.SetDefault(logger)
|
|
182
|
+
|
|
183
|
+
cfg, err := config.Load()
|
|
184
|
+
if err != nil {
|
|
185
|
+
slog.Error("load config", "error", err)
|
|
186
|
+
os.Exit(1)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
application, err := app.New(cfg)
|
|
190
|
+
if err != nil {
|
|
191
|
+
slog.Error("create app", "error", err)
|
|
192
|
+
os.Exit(1)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Graceful shutdown
|
|
196
|
+
ctx, stop := signal.NotifyContext(
|
|
197
|
+
context.Background(),
|
|
198
|
+
syscall.SIGINT, syscall.SIGTERM,
|
|
199
|
+
)
|
|
200
|
+
defer stop()
|
|
201
|
+
|
|
202
|
+
go func() {
|
|
203
|
+
slog.Info("starting server", "addr", cfg.Addr)
|
|
204
|
+
if err := application.Run(); err != nil {
|
|
205
|
+
slog.Error("server error", "error", err)
|
|
206
|
+
}
|
|
207
|
+
}()
|
|
208
|
+
|
|
209
|
+
<-ctx.Done()
|
|
210
|
+
slog.Info("shutting down")
|
|
211
|
+
|
|
212
|
+
shutdownCtx, cancel := context.WithTimeout(
|
|
213
|
+
context.Background(),
|
|
214
|
+
30*time.Second,
|
|
215
|
+
)
|
|
216
|
+
defer cancel()
|
|
217
|
+
|
|
218
|
+
if err := application.Shutdown(shutdownCtx); err != nil {
|
|
219
|
+
slog.Error("shutdown error", "error", err)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Makefile
|
|
225
|
+
|
|
226
|
+
```makefile
|
|
227
|
+
.PHONY: build run test lint
|
|
228
|
+
|
|
229
|
+
build:
|
|
230
|
+
go build -o bin/server ./cmd/server
|
|
231
|
+
|
|
232
|
+
run:
|
|
233
|
+
go run ./cmd/server
|
|
234
|
+
|
|
235
|
+
test:
|
|
236
|
+
go test -v -race ./...
|
|
237
|
+
|
|
238
|
+
lint:
|
|
239
|
+
golangci-lint run
|
|
240
|
+
|
|
241
|
+
docker:
|
|
242
|
+
docker build -t myapp .
|
|
243
|
+
```
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Uber Go Style Guide
|
|
2
|
+
|
|
3
|
+
> Source: https://github.com/uber-go/guide/blob/master/style.md
|
|
4
|
+
|
|
5
|
+
## Guidelines
|
|
6
|
+
|
|
7
|
+
### Verify Interface Compliance
|
|
8
|
+
|
|
9
|
+
```go
|
|
10
|
+
// Compile-time check
|
|
11
|
+
var _ http.Handler = (*Handler)(nil)
|
|
12
|
+
var _ io.Reader = (*MyReader)(nil)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Receiver Type
|
|
16
|
+
|
|
17
|
+
```go
|
|
18
|
+
// Value receiver: doesn't modify state
|
|
19
|
+
func (s Stack) Length() int {
|
|
20
|
+
return len(s.items)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Pointer receiver: modifies state or is large
|
|
24
|
+
func (s *Stack) Push(item int) {
|
|
25
|
+
s.items = append(s.items, item)
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Zero-value Mutexes
|
|
30
|
+
|
|
31
|
+
```go
|
|
32
|
+
// Good: zero-value is valid
|
|
33
|
+
var mu sync.Mutex
|
|
34
|
+
|
|
35
|
+
// Good: embedded in struct
|
|
36
|
+
type Store struct {
|
|
37
|
+
mu sync.Mutex
|
|
38
|
+
items map[string]Item
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Copy Slices and Maps
|
|
43
|
+
|
|
44
|
+
```go
|
|
45
|
+
// Returning: copy to prevent external modification
|
|
46
|
+
func (s *Store) GetItems() []Item {
|
|
47
|
+
s.mu.RLock()
|
|
48
|
+
defer s.mu.RUnlock()
|
|
49
|
+
items := make([]Item, len(s.items))
|
|
50
|
+
copy(items, s.items)
|
|
51
|
+
return items
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Receiving: copy to prevent caller modification
|
|
55
|
+
func (s *Store) SetItems(items []Item) {
|
|
56
|
+
s.mu.Lock()
|
|
57
|
+
defer s.mu.Unlock()
|
|
58
|
+
s.items = make([]Item, len(items))
|
|
59
|
+
copy(s.items, items)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Defer for Cleanup
|
|
64
|
+
|
|
65
|
+
```go
|
|
66
|
+
func readFile(path string) ([]byte, error) {
|
|
67
|
+
f, err := os.Open(path)
|
|
68
|
+
if err != nil {
|
|
69
|
+
return nil, err
|
|
70
|
+
}
|
|
71
|
+
defer f.Close()
|
|
72
|
+
return io.ReadAll(f)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func process() {
|
|
76
|
+
mu.Lock()
|
|
77
|
+
defer mu.Unlock()
|
|
78
|
+
// ...
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Channel Size
|
|
83
|
+
|
|
84
|
+
```go
|
|
85
|
+
// Good: unbuffered or 1
|
|
86
|
+
ch := make(chan int)
|
|
87
|
+
ch := make(chan int, 1)
|
|
88
|
+
|
|
89
|
+
// Requires justification
|
|
90
|
+
ch := make(chan int, 100) // Why 100?
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Start Enums at One
|
|
94
|
+
|
|
95
|
+
```go
|
|
96
|
+
type Operation int
|
|
97
|
+
|
|
98
|
+
const (
|
|
99
|
+
Add Operation = iota + 1
|
|
100
|
+
Subtract
|
|
101
|
+
Multiply
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Error Types
|
|
106
|
+
|
|
107
|
+
```go
|
|
108
|
+
// Sentinel errors
|
|
109
|
+
var ErrNotFound = errors.New("not found")
|
|
110
|
+
|
|
111
|
+
// Error wrapping
|
|
112
|
+
if err != nil {
|
|
113
|
+
return fmt.Errorf("failed to get user: %w", err)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Checking errors
|
|
117
|
+
if errors.Is(err, ErrNotFound) {
|
|
118
|
+
// handle not found
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Handle Errors Once
|
|
123
|
+
|
|
124
|
+
```go
|
|
125
|
+
// Bad: logs AND returns
|
|
126
|
+
if err != nil {
|
|
127
|
+
log.Printf("error: %v", err)
|
|
128
|
+
return err
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Good: return with context
|
|
132
|
+
if err != nil {
|
|
133
|
+
return fmt.Errorf("process: %w", err)
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Use strconv Over fmt
|
|
138
|
+
|
|
139
|
+
```go
|
|
140
|
+
// Good
|
|
141
|
+
s := strconv.Itoa(n)
|
|
142
|
+
n, err := strconv.Atoi(s)
|
|
143
|
+
|
|
144
|
+
// Slower
|
|
145
|
+
s := fmt.Sprintf("%d", n)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Table-Driven Tests
|
|
149
|
+
|
|
150
|
+
```go
|
|
151
|
+
func TestSplit(t *testing.T) {
|
|
152
|
+
tests := []struct {
|
|
153
|
+
name string
|
|
154
|
+
input string
|
|
155
|
+
sep string
|
|
156
|
+
want []string
|
|
157
|
+
}{
|
|
158
|
+
{
|
|
159
|
+
name: "simple",
|
|
160
|
+
input: "a/b/c",
|
|
161
|
+
sep: "/",
|
|
162
|
+
want: []string{"a", "b", "c"},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "empty",
|
|
166
|
+
input: "",
|
|
167
|
+
sep: "/",
|
|
168
|
+
want: []string{""},
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for _, tt := range tests {
|
|
173
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
174
|
+
got := strings.Split(tt.input, tt.sep)
|
|
175
|
+
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
176
|
+
t.Errorf("mismatch (-want +got):\n%s", diff)
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Functional Options
|
|
184
|
+
|
|
185
|
+
```go
|
|
186
|
+
type Server struct {
|
|
187
|
+
addr string
|
|
188
|
+
timeout time.Duration
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
type Option func(*Server)
|
|
192
|
+
|
|
193
|
+
func WithTimeout(d time.Duration) Option {
|
|
194
|
+
return func(s *Server) {
|
|
195
|
+
s.timeout = d
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
func NewServer(addr string, opts ...Option) *Server {
|
|
200
|
+
s := &Server{
|
|
201
|
+
addr: addr,
|
|
202
|
+
timeout: time.Second * 30,
|
|
203
|
+
}
|
|
204
|
+
for _, opt := range opts {
|
|
205
|
+
opt(s)
|
|
206
|
+
}
|
|
207
|
+
return s
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Usage
|
|
211
|
+
server := NewServer(":8080", WithTimeout(time.Minute))
|
|
212
|
+
```
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Go Concurrency Patterns
|
|
2
|
+
|
|
3
|
+
> Reference for concurrent programming in Go
|
|
4
|
+
|
|
5
|
+
## Core Philosophy
|
|
6
|
+
|
|
7
|
+
> Do not communicate by sharing memory; instead, share memory by communicating.
|
|
8
|
+
|
|
9
|
+
Go's approach to concurrency differs from traditional threading models. Channels provide a way to safely communicate between goroutines without explicit locks.
|
|
10
|
+
|
|
11
|
+
## Goroutines
|
|
12
|
+
|
|
13
|
+
A goroutine is a lightweight thread managed by the Go runtime.
|
|
14
|
+
|
|
15
|
+
```go
|
|
16
|
+
// Start a goroutine
|
|
17
|
+
go doSomething()
|
|
18
|
+
|
|
19
|
+
// With anonymous function
|
|
20
|
+
go func() {
|
|
21
|
+
// concurrent work
|
|
22
|
+
}()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Goroutine Lifecycle
|
|
26
|
+
|
|
27
|
+
- Created with `go` keyword
|
|
28
|
+
- Runs concurrently with calling goroutine
|
|
29
|
+
- Exits when function returns
|
|
30
|
+
- Main goroutine exit = program exit
|
|
31
|
+
|
|
32
|
+
## Channels
|
|
33
|
+
|
|
34
|
+
### Basic Operations
|
|
35
|
+
|
|
36
|
+
```go
|
|
37
|
+
// Create
|
|
38
|
+
ch := make(chan int) // unbuffered
|
|
39
|
+
ch := make(chan int, 10) // buffered, capacity 10
|
|
40
|
+
|
|
41
|
+
// Send
|
|
42
|
+
ch <- value
|
|
43
|
+
|
|
44
|
+
// Receive
|
|
45
|
+
value := <-ch
|
|
46
|
+
value, ok := <-ch // ok is false if channel closed
|
|
47
|
+
|
|
48
|
+
// Close
|
|
49
|
+
close(ch)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Unbuffered vs Buffered
|
|
53
|
+
|
|
54
|
+
| Type | Behavior |
|
|
55
|
+
|------|----------|
|
|
56
|
+
| Unbuffered | Sender blocks until receiver ready |
|
|
57
|
+
| Buffered | Sender blocks only when buffer full |
|
|
58
|
+
|
|
59
|
+
### Directional Channels
|
|
60
|
+
|
|
61
|
+
```go
|
|
62
|
+
func sender(ch chan<- int) {
|
|
63
|
+
ch <- 42 // send-only
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func receiver(ch <-chan int) {
|
|
67
|
+
v := <-ch // receive-only
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Common Patterns
|
|
72
|
+
|
|
73
|
+
### Worker Pool
|
|
74
|
+
|
|
75
|
+
```go
|
|
76
|
+
func worker(id int, jobs <-chan int, results chan<- int) {
|
|
77
|
+
for j := range jobs {
|
|
78
|
+
results <- j * 2
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func main() {
|
|
83
|
+
jobs := make(chan int, 100)
|
|
84
|
+
results := make(chan int, 100)
|
|
85
|
+
|
|
86
|
+
// Start 3 workers
|
|
87
|
+
for w := 1; w <= 3; w++ {
|
|
88
|
+
go worker(w, jobs, results)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Send jobs
|
|
92
|
+
for j := 1; j <= 9; j++ {
|
|
93
|
+
jobs <- j
|
|
94
|
+
}
|
|
95
|
+
close(jobs)
|
|
96
|
+
|
|
97
|
+
// Collect results
|
|
98
|
+
for a := 1; a <= 9; a++ {
|
|
99
|
+
<-results
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Fan-Out, Fan-In
|
|
105
|
+
|
|
106
|
+
```go
|
|
107
|
+
// Fan-out: multiple goroutines read from same channel
|
|
108
|
+
// Fan-in: single goroutine reads from multiple channels
|
|
109
|
+
|
|
110
|
+
func fanIn(ch1, ch2 <-chan int) <-chan int {
|
|
111
|
+
out := make(chan int)
|
|
112
|
+
go func() {
|
|
113
|
+
for {
|
|
114
|
+
select {
|
|
115
|
+
case v := <-ch1:
|
|
116
|
+
out <- v
|
|
117
|
+
case v := <-ch2:
|
|
118
|
+
out <- v
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}()
|
|
122
|
+
return out
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Pipeline
|
|
127
|
+
|
|
128
|
+
```go
|
|
129
|
+
func gen(nums ...int) <-chan int {
|
|
130
|
+
out := make(chan int)
|
|
131
|
+
go func() {
|
|
132
|
+
for _, n := range nums {
|
|
133
|
+
out <- n
|
|
134
|
+
}
|
|
135
|
+
close(out)
|
|
136
|
+
}()
|
|
137
|
+
return out
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func sq(in <-chan int) <-chan int {
|
|
141
|
+
out := make(chan int)
|
|
142
|
+
go func() {
|
|
143
|
+
for n := range in {
|
|
144
|
+
out <- n * n
|
|
145
|
+
}
|
|
146
|
+
close(out)
|
|
147
|
+
}()
|
|
148
|
+
return out
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Usage: for v := range sq(sq(gen(2, 3))) { ... }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Context for Cancellation
|
|
155
|
+
|
|
156
|
+
```go
|
|
157
|
+
func operation(ctx context.Context) error {
|
|
158
|
+
select {
|
|
159
|
+
case <-time.After(time.Second):
|
|
160
|
+
return nil // completed
|
|
161
|
+
case <-ctx.Done():
|
|
162
|
+
return ctx.Err() // cancelled
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func main() {
|
|
167
|
+
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
|
168
|
+
defer cancel()
|
|
169
|
+
|
|
170
|
+
if err := operation(ctx); err != nil {
|
|
171
|
+
log.Fatal(err)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Select Statement
|
|
177
|
+
|
|
178
|
+
```go
|
|
179
|
+
select {
|
|
180
|
+
case v := <-ch1:
|
|
181
|
+
// received from ch1
|
|
182
|
+
case ch2 <- x:
|
|
183
|
+
// sent to ch2
|
|
184
|
+
case <-time.After(time.Second):
|
|
185
|
+
// timeout
|
|
186
|
+
default:
|
|
187
|
+
// non-blocking
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Synchronization
|
|
192
|
+
|
|
193
|
+
### sync.WaitGroup
|
|
194
|
+
|
|
195
|
+
```go
|
|
196
|
+
var wg sync.WaitGroup
|
|
197
|
+
|
|
198
|
+
for i := 0; i < 5; i++ {
|
|
199
|
+
wg.Add(1)
|
|
200
|
+
go func(id int) {
|
|
201
|
+
defer wg.Done()
|
|
202
|
+
// work
|
|
203
|
+
}(i)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
wg.Wait() // blocks until all done
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### sync.Mutex
|
|
210
|
+
|
|
211
|
+
```go
|
|
212
|
+
var (
|
|
213
|
+
mu sync.Mutex
|
|
214
|
+
counter int
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
func increment() {
|
|
218
|
+
mu.Lock()
|
|
219
|
+
defer mu.Unlock()
|
|
220
|
+
counter++
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### sync.Once
|
|
225
|
+
|
|
226
|
+
```go
|
|
227
|
+
var once sync.Once
|
|
228
|
+
|
|
229
|
+
func initialize() {
|
|
230
|
+
once.Do(func() {
|
|
231
|
+
// runs exactly once
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Avoiding Common Pitfalls
|
|
237
|
+
|
|
238
|
+
### Goroutine Leaks
|
|
239
|
+
|
|
240
|
+
```go
|
|
241
|
+
// BAD: goroutine never exits
|
|
242
|
+
go func() {
|
|
243
|
+
for {
|
|
244
|
+
// infinite loop with no exit
|
|
245
|
+
}
|
|
246
|
+
}()
|
|
247
|
+
|
|
248
|
+
// GOOD: use context or done channel
|
|
249
|
+
go func(ctx context.Context) {
|
|
250
|
+
for {
|
|
251
|
+
select {
|
|
252
|
+
case <-ctx.Done():
|
|
253
|
+
return
|
|
254
|
+
default:
|
|
255
|
+
// work
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}(ctx)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Race Conditions
|
|
262
|
+
|
|
263
|
+
```go
|
|
264
|
+
// BAD: race condition
|
|
265
|
+
go func() { x++ }()
|
|
266
|
+
go func() { x++ }()
|
|
267
|
+
|
|
268
|
+
// GOOD: use channels or mutex
|
|
269
|
+
go func() { ch <- 1 }()
|
|
270
|
+
go func() { ch <- 1 }()
|
|
271
|
+
x += <-ch + <-ch
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Closing Channels
|
|
275
|
+
|
|
276
|
+
```go
|
|
277
|
+
// Only sender should close
|
|
278
|
+
// Closing already-closed channel panics
|
|
279
|
+
// Sending to closed channel panics
|
|
280
|
+
|
|
281
|
+
// Pattern: use sync.Once or single sender
|
|
282
|
+
```
|