agentic-team-templates 0.3.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/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Patterns for handling errors gracefully and providing helpful feedback to users.
|
|
4
|
+
|
|
5
|
+
## Exit Codes
|
|
6
|
+
|
|
7
|
+
### Standard Conventions
|
|
8
|
+
|
|
9
|
+
| Code | Meaning | When to Use |
|
|
10
|
+
|------|---------|-------------|
|
|
11
|
+
| 0 | Success | Command completed successfully |
|
|
12
|
+
| 1 | General error | Catch-all for failures |
|
|
13
|
+
| 2 | Misuse | Invalid arguments, bad syntax |
|
|
14
|
+
| 64-78 | BSD sysexits | Specific error categories |
|
|
15
|
+
| 126 | Cannot execute | Permission denied |
|
|
16
|
+
| 127 | Not found | Command/file not found |
|
|
17
|
+
| 128+N | Signal N | Terminated by signal (e.g., 130 = SIGINT) |
|
|
18
|
+
|
|
19
|
+
### Implementing Exit Codes
|
|
20
|
+
|
|
21
|
+
```go
|
|
22
|
+
// Go: Define exit codes
|
|
23
|
+
const (
|
|
24
|
+
ExitSuccess = 0
|
|
25
|
+
ExitGeneralError = 1
|
|
26
|
+
ExitUsageError = 2
|
|
27
|
+
ExitConfigError = 64
|
|
28
|
+
ExitInputError = 65
|
|
29
|
+
ExitNoPermission = 77
|
|
30
|
+
ExitInterrupted = 130
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
func main() {
|
|
34
|
+
if err := run(); err != nil {
|
|
35
|
+
var exitCode int
|
|
36
|
+
|
|
37
|
+
switch {
|
|
38
|
+
case errors.Is(err, ErrInvalidArgs):
|
|
39
|
+
exitCode = ExitUsageError
|
|
40
|
+
case errors.Is(err, ErrConfigNotFound):
|
|
41
|
+
exitCode = ExitConfigError
|
|
42
|
+
case errors.Is(err, context.Canceled):
|
|
43
|
+
exitCode = ExitInterrupted
|
|
44
|
+
default:
|
|
45
|
+
exitCode = ExitGeneralError
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
49
|
+
os.Exit(exitCode)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// TypeScript: Exit codes
|
|
56
|
+
const EXIT_SUCCESS = 0;
|
|
57
|
+
const EXIT_ERROR = 1;
|
|
58
|
+
const EXIT_USAGE = 2;
|
|
59
|
+
const EXIT_CONFIG = 64;
|
|
60
|
+
|
|
61
|
+
function main(): void {
|
|
62
|
+
try {
|
|
63
|
+
run();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof UsageError) {
|
|
66
|
+
console.error(`Usage error: ${error.message}`);
|
|
67
|
+
process.exit(EXIT_USAGE);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (error instanceof ConfigError) {
|
|
71
|
+
console.error(`Config error: ${error.message}`);
|
|
72
|
+
process.exit(EXIT_CONFIG);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.error(`Error: ${error.message}`);
|
|
76
|
+
process.exit(EXIT_ERROR);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Error Messages
|
|
82
|
+
|
|
83
|
+
### Anatomy of a Good Error Message
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Error: cannot read config file
|
|
87
|
+
|
|
88
|
+
File: /home/user/.config/mytool/config.yaml
|
|
89
|
+
Cause: permission denied
|
|
90
|
+
|
|
91
|
+
Try:
|
|
92
|
+
• Check file permissions: ls -la ~/.config/mytool/
|
|
93
|
+
• Create with correct permissions: chmod 600 ~/.config/mytool/config.yaml
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Error Message Guidelines
|
|
97
|
+
|
|
98
|
+
1. **What happened** - Clear description of the failure
|
|
99
|
+
2. **Context** - Relevant details (file path, input value)
|
|
100
|
+
3. **Why** - Root cause if known
|
|
101
|
+
4. **How to fix** - Actionable suggestions
|
|
102
|
+
|
|
103
|
+
```go
|
|
104
|
+
// Go: Structured error with suggestions
|
|
105
|
+
type CLIError struct {
|
|
106
|
+
Message string
|
|
107
|
+
Details string
|
|
108
|
+
Suggestions []string
|
|
109
|
+
Cause error
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func (e *CLIError) Error() string {
|
|
113
|
+
return e.Message
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func (e *CLIError) Format() string {
|
|
117
|
+
var b strings.Builder
|
|
118
|
+
|
|
119
|
+
b.WriteString(color.RedString("Error: "))
|
|
120
|
+
b.WriteString(e.Message)
|
|
121
|
+
b.WriteString("\n")
|
|
122
|
+
|
|
123
|
+
if e.Details != "" {
|
|
124
|
+
b.WriteString("\n")
|
|
125
|
+
b.WriteString(color.DimString(e.Details))
|
|
126
|
+
b.WriteString("\n")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if e.Cause != nil {
|
|
130
|
+
b.WriteString("\n")
|
|
131
|
+
b.WriteString(color.DimString(fmt.Sprintf("Cause: %v", e.Cause)))
|
|
132
|
+
b.WriteString("\n")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if len(e.Suggestions) > 0 {
|
|
136
|
+
b.WriteString("\n")
|
|
137
|
+
b.WriteString(color.YellowString("Try:\n"))
|
|
138
|
+
for _, s := range e.Suggestions {
|
|
139
|
+
b.WriteString(fmt.Sprintf(" • %s\n", s))
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return b.String()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Usage
|
|
147
|
+
func loadConfig(path string) (*Config, error) {
|
|
148
|
+
data, err := os.ReadFile(path)
|
|
149
|
+
if err != nil {
|
|
150
|
+
if os.IsNotExist(err) {
|
|
151
|
+
return nil, &CLIError{
|
|
152
|
+
Message: "config file not found",
|
|
153
|
+
Details: fmt.Sprintf("Path: %s", path),
|
|
154
|
+
Suggestions: []string{
|
|
155
|
+
fmt.Sprintf("Create a config file: %s init", os.Args[0]),
|
|
156
|
+
"Copy from example: cp config.example.yaml " + path,
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if os.IsPermission(err) {
|
|
161
|
+
return nil, &CLIError{
|
|
162
|
+
Message: "cannot read config file",
|
|
163
|
+
Details: fmt.Sprintf("Path: %s", path),
|
|
164
|
+
Cause: err,
|
|
165
|
+
Suggestions: []string{
|
|
166
|
+
"Check file permissions: ls -la " + path,
|
|
167
|
+
"Fix permissions: chmod 600 " + path,
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return nil, &CLIError{
|
|
172
|
+
Message: "failed to read config file",
|
|
173
|
+
Cause: err,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ...
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Common Error Patterns
|
|
181
|
+
|
|
182
|
+
```go
|
|
183
|
+
// File not found
|
|
184
|
+
&CLIError{
|
|
185
|
+
Message: fmt.Sprintf("file not found: %s", path),
|
|
186
|
+
Suggestions: []string{
|
|
187
|
+
"Check if the file exists",
|
|
188
|
+
"Verify the path is correct",
|
|
189
|
+
fmt.Sprintf("Create the file: touch %s", path),
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Invalid argument
|
|
194
|
+
&CLIError{
|
|
195
|
+
Message: fmt.Sprintf("invalid value for --port: %q", value),
|
|
196
|
+
Details: "Port must be a number between 1 and 65535",
|
|
197
|
+
Suggestions: []string{
|
|
198
|
+
"Use a valid port number: --port 8080",
|
|
199
|
+
"Check available ports: netstat -tlnp",
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Network error
|
|
204
|
+
&CLIError{
|
|
205
|
+
Message: "failed to connect to API",
|
|
206
|
+
Details: fmt.Sprintf("URL: %s", url),
|
|
207
|
+
Cause: err,
|
|
208
|
+
Suggestions: []string{
|
|
209
|
+
"Check your internet connection",
|
|
210
|
+
"Verify the API URL is correct",
|
|
211
|
+
"Try again later if the service is down",
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Authentication error
|
|
216
|
+
&CLIError{
|
|
217
|
+
Message: "authentication failed",
|
|
218
|
+
Suggestions: []string{
|
|
219
|
+
fmt.Sprintf("Login first: %s login", os.Args[0]),
|
|
220
|
+
"Check if your token has expired",
|
|
221
|
+
"Verify your credentials are correct",
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Error Wrapping
|
|
227
|
+
|
|
228
|
+
### Adding Context
|
|
229
|
+
|
|
230
|
+
```go
|
|
231
|
+
// Go: Wrap errors with context
|
|
232
|
+
func deployToEnvironment(env string) error {
|
|
233
|
+
config, err := loadConfig(env)
|
|
234
|
+
if err != nil {
|
|
235
|
+
return fmt.Errorf("deploy to %s: %w", env, err)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if err := validateConfig(config); err != nil {
|
|
239
|
+
return fmt.Errorf("deploy to %s: invalid config: %w", env, err)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if err := runDeploy(config); err != nil {
|
|
243
|
+
return fmt.Errorf("deploy to %s: %w", env, err)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return nil
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Result: "deploy to prod: invalid config: missing required field 'api_key'"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// TypeScript: Error wrapping
|
|
254
|
+
class WrappedError extends Error {
|
|
255
|
+
constructor(message: string, public cause: Error) {
|
|
256
|
+
super(`${message}: ${cause.message}`);
|
|
257
|
+
this.name = 'WrappedError';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function deployToEnvironment(env: string): Promise<void> {
|
|
262
|
+
let config: Config;
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
config = await loadConfig(env);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
throw new WrappedError(`deploy to ${env}`, error);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
await runDeploy(config);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
throw new WrappedError(`deploy to ${env}`, error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Unwrapping for Specific Handling
|
|
279
|
+
|
|
280
|
+
```go
|
|
281
|
+
// Go: Check for specific errors
|
|
282
|
+
func handleError(err error) {
|
|
283
|
+
var cliErr *CLIError
|
|
284
|
+
if errors.As(err, &cliErr) {
|
|
285
|
+
fmt.Fprint(os.Stderr, cliErr.Format())
|
|
286
|
+
os.Exit(1)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if errors.Is(err, context.Canceled) {
|
|
290
|
+
fmt.Fprintln(os.Stderr, "Operation cancelled")
|
|
291
|
+
os.Exit(130)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Unknown error
|
|
295
|
+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
296
|
+
os.Exit(1)
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Graceful Degradation
|
|
301
|
+
|
|
302
|
+
### Partial Failures
|
|
303
|
+
|
|
304
|
+
```go
|
|
305
|
+
// Go: Continue on partial failures
|
|
306
|
+
func processFiles(files []string) error {
|
|
307
|
+
var errs []error
|
|
308
|
+
|
|
309
|
+
for _, file := range files {
|
|
310
|
+
if err := processFile(file); err != nil {
|
|
311
|
+
// Log error but continue
|
|
312
|
+
fmt.Fprintf(os.Stderr, "Warning: failed to process %s: %v\n", file, err)
|
|
313
|
+
errs = append(errs, err)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if len(errs) > 0 {
|
|
318
|
+
return fmt.Errorf("failed to process %d/%d files", len(errs), len(files))
|
|
319
|
+
}
|
|
320
|
+
return nil
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Fallbacks
|
|
325
|
+
|
|
326
|
+
```go
|
|
327
|
+
// Go: Try alternatives
|
|
328
|
+
func getAPIEndpoint() string {
|
|
329
|
+
// Try environment variable first
|
|
330
|
+
if endpoint := os.Getenv("API_ENDPOINT"); endpoint != "" {
|
|
331
|
+
return endpoint
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Try config file
|
|
335
|
+
if config, err := loadConfig(); err == nil && config.APIEndpoint != "" {
|
|
336
|
+
return config.APIEndpoint
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Fall back to default
|
|
340
|
+
return "https://api.example.com"
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Debugging Support
|
|
345
|
+
|
|
346
|
+
### Verbose Error Output
|
|
347
|
+
|
|
348
|
+
```go
|
|
349
|
+
// Go: Show stack trace in debug mode
|
|
350
|
+
func handleError(err error) {
|
|
351
|
+
if debugMode {
|
|
352
|
+
fmt.Fprintf(os.Stderr, "Error: %+v\n", err) // With stack trace
|
|
353
|
+
} else {
|
|
354
|
+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Error Context in Verbose Mode
|
|
360
|
+
|
|
361
|
+
```go
|
|
362
|
+
func processFile(path string) error {
|
|
363
|
+
logger.Debug("Processing file", "path", path)
|
|
364
|
+
|
|
365
|
+
data, err := os.ReadFile(path)
|
|
366
|
+
if err != nil {
|
|
367
|
+
logger.Debug("Failed to read file", "path", path, "error", err)
|
|
368
|
+
return fmt.Errorf("read %s: %w", path, err)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
logger.Debug("Read file successfully", "path", path, "size", len(data))
|
|
372
|
+
// ...
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Anti-Patterns
|
|
377
|
+
|
|
378
|
+
### Silent Failures
|
|
379
|
+
|
|
380
|
+
```go
|
|
381
|
+
// Bad: Error is swallowed
|
|
382
|
+
func maybeDoSomething() {
|
|
383
|
+
result, _ := riskyOperation() // Error ignored!
|
|
384
|
+
use(result)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Good: Handle or propagate
|
|
388
|
+
func maybeDoSomething() error {
|
|
389
|
+
result, err := riskyOperation()
|
|
390
|
+
if err != nil {
|
|
391
|
+
return fmt.Errorf("risky operation: %w", err)
|
|
392
|
+
}
|
|
393
|
+
use(result)
|
|
394
|
+
return nil
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Generic Error Messages
|
|
399
|
+
|
|
400
|
+
```go
|
|
401
|
+
// Bad: Not helpful
|
|
402
|
+
return errors.New("operation failed")
|
|
403
|
+
|
|
404
|
+
// Good: Specific and actionable
|
|
405
|
+
return &CLIError{
|
|
406
|
+
Message: "failed to connect to database",
|
|
407
|
+
Details: fmt.Sprintf("Host: %s, Port: %d", host, port),
|
|
408
|
+
Cause: err,
|
|
409
|
+
Suggestions: []string{
|
|
410
|
+
"Verify the database is running",
|
|
411
|
+
"Check your connection settings",
|
|
412
|
+
},
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Panic Instead of Error
|
|
417
|
+
|
|
418
|
+
```go
|
|
419
|
+
// Bad: Panic for recoverable errors
|
|
420
|
+
func loadConfig(path string) *Config {
|
|
421
|
+
data, err := os.ReadFile(path)
|
|
422
|
+
if err != nil {
|
|
423
|
+
panic(err) // Don't do this!
|
|
424
|
+
}
|
|
425
|
+
// ...
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Good: Return errors
|
|
429
|
+
func loadConfig(path string) (*Config, error) {
|
|
430
|
+
data, err := os.ReadFile(path)
|
|
431
|
+
if err != nil {
|
|
432
|
+
return nil, fmt.Errorf("load config: %w", err)
|
|
433
|
+
}
|
|
434
|
+
// ...
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Error Strings as Control Flow
|
|
439
|
+
|
|
440
|
+
```go
|
|
441
|
+
// Bad: String matching for error handling
|
|
442
|
+
if err.Error() == "file not found" {
|
|
443
|
+
// ...
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Good: Use sentinel errors or types
|
|
447
|
+
if errors.Is(err, os.ErrNotExist) {
|
|
448
|
+
// ...
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
var ErrConfigNotFound = errors.New("config not found")
|
|
452
|
+
if errors.Is(err, ErrConfigNotFound) {
|
|
453
|
+
// ...
|
|
454
|
+
}
|
|
455
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# CLI Tools Development Overview
|
|
2
|
+
|
|
3
|
+
Staff-level guidelines for building professional command-line interfaces that are intuitive, robust, and maintainable.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This template applies to:
|
|
8
|
+
|
|
9
|
+
- Command-line applications and utilities
|
|
10
|
+
- Developer tools and build systems
|
|
11
|
+
- System administration scripts
|
|
12
|
+
- Automation and DevOps tooling
|
|
13
|
+
- Interactive terminal applications
|
|
14
|
+
|
|
15
|
+
## Core Principles
|
|
16
|
+
|
|
17
|
+
### 1. Human-First Design
|
|
18
|
+
|
|
19
|
+
CLIs should be intuitive for humans while remaining scriptable.
|
|
20
|
+
|
|
21
|
+
- Provide helpful error messages that suggest fixes
|
|
22
|
+
- Use sensible defaults that work for common cases
|
|
23
|
+
- Support both interactive and non-interactive modes
|
|
24
|
+
- Follow platform conventions (POSIX, Windows)
|
|
25
|
+
|
|
26
|
+
### 2. Composability
|
|
27
|
+
|
|
28
|
+
Unix philosophy: do one thing well, compose with others.
|
|
29
|
+
|
|
30
|
+
- Accept input from stdin, produce output to stdout
|
|
31
|
+
- Use stderr for diagnostics, stdout for data
|
|
32
|
+
- Support piping and redirection
|
|
33
|
+
- Return meaningful exit codes
|
|
34
|
+
|
|
35
|
+
### 3. Predictability
|
|
36
|
+
|
|
37
|
+
Behavior should be consistent and unsurprising.
|
|
38
|
+
|
|
39
|
+
- Same inputs → same outputs (deterministic)
|
|
40
|
+
- Respect environment variables and config files
|
|
41
|
+
- Document all side effects
|
|
42
|
+
- Never silently fail
|
|
43
|
+
|
|
44
|
+
### 4. Progressive Disclosure
|
|
45
|
+
|
|
46
|
+
Simple things simple, complex things possible.
|
|
47
|
+
|
|
48
|
+
- Common operations require minimal arguments
|
|
49
|
+
- Advanced features available but not required
|
|
50
|
+
- Help text at multiple levels of detail
|
|
51
|
+
- Examples for every command
|
|
52
|
+
|
|
53
|
+
## Project Structure
|
|
54
|
+
|
|
55
|
+
### Node.js / TypeScript
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
cli/
|
|
59
|
+
├── src/
|
|
60
|
+
│ ├── index.ts # Entry point
|
|
61
|
+
│ ├── cli.ts # Command definitions
|
|
62
|
+
│ ├── commands/ # Command implementations
|
|
63
|
+
│ │ ├── init.ts
|
|
64
|
+
│ │ ├── build.ts
|
|
65
|
+
│ │ └── deploy.ts
|
|
66
|
+
│ ├── lib/ # Business logic
|
|
67
|
+
│ ├── utils/ # Utilities (output, prompts)
|
|
68
|
+
│ └── types.ts # Type definitions
|
|
69
|
+
├── bin/
|
|
70
|
+
│ └── cli.js # Executable entry
|
|
71
|
+
├── tests/
|
|
72
|
+
│ ├── unit/
|
|
73
|
+
│ ├── integration/
|
|
74
|
+
│ └── fixtures/
|
|
75
|
+
├── package.json
|
|
76
|
+
└── tsconfig.json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Go
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
cli/
|
|
83
|
+
├── cmd/
|
|
84
|
+
│ ├── root.go # Root command
|
|
85
|
+
│ ├── init.go # Subcommands
|
|
86
|
+
│ ├── build.go
|
|
87
|
+
│ └── deploy.go
|
|
88
|
+
├── internal/
|
|
89
|
+
│ ├── config/ # Configuration
|
|
90
|
+
│ ├── output/ # Output formatting
|
|
91
|
+
│ └── core/ # Business logic
|
|
92
|
+
├── pkg/ # Public packages
|
|
93
|
+
├── main.go
|
|
94
|
+
├── go.mod
|
|
95
|
+
└── Makefile
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Python
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
cli/
|
|
102
|
+
├── src/
|
|
103
|
+
│ └── mycli/
|
|
104
|
+
│ ├── __init__.py
|
|
105
|
+
│ ├── __main__.py # Entry point
|
|
106
|
+
│ ├── cli.py # Click/Typer definitions
|
|
107
|
+
│ ├── commands/
|
|
108
|
+
│ └── utils/
|
|
109
|
+
├── tests/
|
|
110
|
+
├── pyproject.toml
|
|
111
|
+
└── setup.py
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Technology Recommendations
|
|
115
|
+
|
|
116
|
+
| Language | Framework | Config | Why |
|
|
117
|
+
|----------|-----------|--------|-----|
|
|
118
|
+
| Node.js | Commander.js | cosmiconfig | Zero deps, TypeScript support |
|
|
119
|
+
| Go | Cobra + Viper | Viper | Industry standard, powerful |
|
|
120
|
+
| Python | Typer or Click | pydantic | Type hints, auto-docs |
|
|
121
|
+
| Rust | Clap | config-rs | Derive macros, excellent help |
|
|
122
|
+
|
|
123
|
+
## Definition of Done
|
|
124
|
+
|
|
125
|
+
A CLI tool is ready for release when:
|
|
126
|
+
|
|
127
|
+
- [ ] All commands have help text and examples
|
|
128
|
+
- [ ] Error messages suggest corrective actions
|
|
129
|
+
- [ ] Exit codes follow conventions (0=success, 1=error)
|
|
130
|
+
- [ ] Works in non-interactive environments (CI/CD)
|
|
131
|
+
- [ ] Supports both stdin/stdout piping and file arguments
|
|
132
|
+
- [ ] Configuration precedence documented (flags > env > file)
|
|
133
|
+
- [ ] Shell completions available (bash, zsh, fish)
|
|
134
|
+
- [ ] Version flag shows useful info (`--version`)
|
|
135
|
+
- [ ] Tests cover happy path and error cases
|
|
136
|
+
- [ ] Documentation includes installation and quickstart
|