@caddis/cli 0.0.0 → 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 +150 -1
- package/bundles/antigravity-plugin/agents/code-reviewer.md +47 -0
- package/bundles/antigravity-plugin/agents/preflight.md +53 -0
- package/bundles/antigravity-plugin/guard_agy.py +338 -0
- package/bundles/antigravity-plugin/hooks.json +36 -0
- package/bundles/antigravity-plugin/mcp_config.json +8 -0
- package/bundles/antigravity-plugin/mcp_ping_server.py +55 -0
- package/bundles/antigravity-plugin/plugin.json +5 -0
- package/bundles/antigravity-plugin/session_end_agy.py +57 -0
- package/bundles/antigravity-plugin/skills/_registry.md +115 -0
- package/bundles/antigravity-plugin/skills/add-rules/SKILL.md +45 -0
- package/bundles/antigravity-plugin/skills/api-design/SKILL.md +249 -0
- package/bundles/antigravity-plugin/skills/backend-development/SKILL.md +305 -0
- package/bundles/antigravity-plugin/skills/best-practices/SKILL.md +500 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/best-practices-referencer.md +263 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/codebase-context-builder.md +326 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/task-intent-analyzer.md +245 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/anti-patterns.md +571 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/before-after-examples.md +1114 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/best-practices-guide.md +513 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/common-workflows.md +692 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/prompt-patterns.md +547 -0
- package/bundles/antigravity-plugin/skills/brainstorming/SKILL.md +57 -0
- package/bundles/antigravity-plugin/skills/ci-cd-pipeline/SKILL.md +315 -0
- package/bundles/antigravity-plugin/skills/code-documentation/SKILL.md +271 -0
- package/bundles/antigravity-plugin/skills/code-review/SKILL.md +122 -0
- package/bundles/antigravity-plugin/skills/codebase-audit/SKILL.md +204 -0
- package/bundles/antigravity-plugin/skills/context-curator/SKILL.md +157 -0
- package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +40 -0
- package/bundles/antigravity-plugin/skills/css-architecture/SKILL.md +305 -0
- package/bundles/antigravity-plugin/skills/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
- package/bundles/antigravity-plugin/skills/database-design/SKILL.md +177 -0
- package/bundles/antigravity-plugin/skills/db-diagram/SKILL.md +148 -0
- package/bundles/antigravity-plugin/skills/db-diagram/scripts/sql_to_graph.py +1212 -0
- package/bundles/antigravity-plugin/skills/digress/SKILL.md +61 -0
- package/bundles/antigravity-plugin/skills/draw-io/SKILL.md +162 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/aws-icons.md +677 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/layout-guidelines.md +142 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/troubleshooting.md +118 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/workflows.md +103 -0
- package/bundles/antigravity-plugin/skills/draw-io/scripts/convert-drawio-to-png.sh +25 -0
- package/bundles/antigravity-plugin/skills/draw-io/scripts/find_aws_icon.py +79 -0
- package/bundles/antigravity-plugin/skills/error-handling/SKILL.md +260 -0
- package/bundles/antigravity-plugin/skills/excalidraw-db/SKILL.md +38 -0
- package/bundles/antigravity-plugin/skills/fastapi-dev/SKILL.md +300 -0
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +198 -0
- package/bundles/antigravity-plugin/skills/frontend-design/SKILL.md +193 -0
- package/bundles/antigravity-plugin/skills/frontend-design/references/my-tech-stack.md +127 -0
- package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +195 -0
- package/bundles/antigravity-plugin/skills/git-commit/SKILL.md +285 -0
- package/bundles/antigravity-plugin/skills/golden-plan/SKILL.md +577 -0
- package/bundles/antigravity-plugin/skills/handoff/SKILL.md +100 -0
- package/bundles/antigravity-plugin/skills/implement/SKILL.md +114 -0
- package/bundles/antigravity-plugin/skills/javascript-typescript/SKILL.md +142 -0
- package/bundles/antigravity-plugin/skills/kb/SKILL.md +60 -0
- package/bundles/antigravity-plugin/skills/mermaid-db/SKILL.md +33 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/SKILL.md +236 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/advanced-features.md +556 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/architecture-diagrams.md +192 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/c4-diagrams.md +410 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/class-diagrams.md +361 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/erd-diagrams.md +510 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/flowcharts.md +450 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/sequence-diagrams.md +394 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/troubleshooting.md +335 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/workflows.md +418 -0
- package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +68 -0
- package/bundles/antigravity-plugin/skills/mockup/SKILL.md +242 -0
- package/bundles/antigravity-plugin/skills/particle-art/SKILL.md +243 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/canvas-utils.ts +171 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/dot-field.template.tsx +203 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/flow-field.template.tsx +263 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/node-shape.template.tsx +261 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/shape-sampler.ts +281 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/stipple-morph.template.tsx +167 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/stipple.template.tsx +175 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/trail-ghost.template.tsx +266 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/usage-examples.md +320 -0
- package/bundles/antigravity-plugin/skills/playwright/API_REFERENCE.md +653 -0
- package/bundles/antigravity-plugin/skills/playwright/SKILL.md +454 -0
- package/bundles/antigravity-plugin/skills/playwright/lib/helpers.js +441 -0
- package/bundles/antigravity-plugin/skills/playwright/package.json +26 -0
- package/bundles/antigravity-plugin/skills/playwright/run.js +228 -0
- package/bundles/antigravity-plugin/skills/prd/SKILL.md +107 -0
- package/bundles/antigravity-plugin/skills/preflight/SKILL.md +435 -0
- package/bundles/antigravity-plugin/skills/python/SKILL.md +388 -0
- package/bundles/antigravity-plugin/skills/react-best-practices/SKILL.md +269 -0
- package/bundles/antigravity-plugin/skills/react-dev/README.md +404 -0
- package/bundles/antigravity-plugin/skills/react-dev/SKILL.md +459 -0
- package/bundles/antigravity-plugin/skills/react-dev/examples/generic-components.md +579 -0
- package/bundles/antigravity-plugin/skills/react-dev/examples/server-components.md +579 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/event-handlers.md +574 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/hooks.md +456 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/react-19-patterns.md +638 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/react-router.md +1002 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/tanstack-router.md +587 -0
- package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +486 -0
- package/bundles/antigravity-plugin/skills/resume/SKILL.md +36 -0
- package/bundles/antigravity-plugin/skills/security-review/SKILL.md +196 -0
- package/bundles/antigravity-plugin/skills/setup-project-ai/SKILL.md +61 -0
- package/bundles/antigravity-plugin/skills/ship/SKILL.md +107 -0
- package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +103 -0
- package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +102 -0
- package/bundles/antigravity-plugin/skills/skill-creator/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin/skills/skill-creator/SKILL.md +491 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/analyzer.md +274 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/comparator.md +202 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/grader.md +223 -0
- package/bundles/antigravity-plugin/skills/skill-creator/assets/eval_review.html +146 -0
- package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/bundles/antigravity-plugin/skills/skill-creator/references/schemas.md +430 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/__init__.py +0 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/generate_report.py +326 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/improve_description.py +247 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/package_skill.py +136 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_eval.py +310 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_loop.py +328 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/utils.py +47 -0
- package/bundles/antigravity-plugin/skills/sql/SKILL.md +321 -0
- package/bundles/antigravity-plugin/skills/tdd/SKILL.md +37 -0
- package/bundles/antigravity-plugin/skills/tdd-workflow/SKILL.md +188 -0
- package/bundles/antigravity-plugin/skills/technical-writing/SKILL.md +286 -0
- package/bundles/antigravity-plugin/skills/test-strategy/SKILL.md +155 -0
- package/bundles/antigravity-plugin/skills/ui-brief/SKILL.md +84 -0
- package/bundles/antigravity-plugin/skills/ui-review/SKILL.md +176 -0
- package/bundles/antigravity-plugin/skills/ui-review/references/framework-fixes.md +471 -0
- package/bundles/antigravity-plugin/skills/ui-review/references/visual-checklist.md +236 -0
- package/bundles/antigravity-plugin/skills/usage-review/SKILL.md +77 -0
- package/bundles/antigravity-plugin/skills/use-model/SKILL.md +64 -0
- package/bundles/antigravity-plugin/skills/using-git-worktrees/SKILL.md +217 -0
- package/bundles/antigravity-plugin/skills/version/SKILL.md +18 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/SKILL.md +732 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/SKILL.md +97 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/console_logging.py +35 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/scripts/with_server.py +106 -0
- package/bundles/antigravity-plugin/skills/windows-deployment/SKILL.md +880 -0
- package/bundles/antigravity-plugin/skills/writing-plans/SKILL.md +384 -0
- package/bundles/antigravity-plugin/statusline-command-agy.sh +91 -0
- package/bundles/antigravity-plugin/warm_start_agy.py +149 -0
- package/bundles/manifest.json +6 -0
- package/dist/cli.js +5363 -0
- package/package.json +61 -4
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: error-handling
|
|
3
|
+
description: Error handling patterns for Python and TypeScript applications. Use when designing error hierarchies, implementing retry logic, building error boundaries, or establishing logging strategies. Covers custom exceptions, result types, circuit breakers, and user-facing error messages.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Error Handling Skill
|
|
7
|
+
|
|
8
|
+
Errors are not exceptional — they are part of the contract. This skill defines how to classify, propagate, and recover from errors consistently.
|
|
9
|
+
|
|
10
|
+
## 1. When to Apply This Skill
|
|
11
|
+
|
|
12
|
+
**Trigger conditions:**
|
|
13
|
+
- Designing error handling for a new service or feature
|
|
14
|
+
- "How should we handle errors in this module?"
|
|
15
|
+
- Building retry logic or circuit breakers
|
|
16
|
+
- Reviewing code with bare `except:` or swallowed errors
|
|
17
|
+
- Establishing error handling conventions for a project
|
|
18
|
+
|
|
19
|
+
## 2. Error Classification
|
|
20
|
+
|
|
21
|
+
Every error falls into one of three categories. Handle each differently.
|
|
22
|
+
|
|
23
|
+
| Category | Retryable? | User Action? | Example |
|
|
24
|
+
|----------|-----------|-------------|---------|
|
|
25
|
+
| **Transient** | Yes | None (auto-retry) | Network timeout, DB connection dropped, rate limit |
|
|
26
|
+
| **Operational** | No | Inform user | Invalid input, resource not found, insufficient permissions |
|
|
27
|
+
| **Fatal** | No | Alert team | Corrupted state, config missing, unrecoverable assertion |
|
|
28
|
+
|
|
29
|
+
## 3. Python Exception Hierarchy
|
|
30
|
+
|
|
31
|
+
Build domain-specific exceptions. Never raise raw `Exception`.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
class AppError(Exception):
|
|
35
|
+
"""Base error for the application."""
|
|
36
|
+
def __init__(self, message: str, code: str | None = None):
|
|
37
|
+
self.message = message
|
|
38
|
+
self.code = code or self.__class__.__name__
|
|
39
|
+
super().__init__(self.message)
|
|
40
|
+
|
|
41
|
+
# Operational errors (expected, handled gracefully)
|
|
42
|
+
class NotFoundError(AppError):
|
|
43
|
+
"""Resource does not exist."""
|
|
44
|
+
|
|
45
|
+
class ValidationError(AppError):
|
|
46
|
+
"""Input validation failed."""
|
|
47
|
+
|
|
48
|
+
class AuthorizationError(AppError):
|
|
49
|
+
"""User not authorized for this action."""
|
|
50
|
+
|
|
51
|
+
# Transient errors (retry-eligible)
|
|
52
|
+
class TransientError(AppError):
|
|
53
|
+
"""Temporary failure — safe to retry."""
|
|
54
|
+
|
|
55
|
+
class ExternalServiceError(TransientError):
|
|
56
|
+
"""External API call failed."""
|
|
57
|
+
|
|
58
|
+
class DatabaseConnectionError(TransientError):
|
|
59
|
+
"""Database connection lost."""
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Usage Patterns
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
# ✅ GOOD: Specific exception with context
|
|
66
|
+
def get_user(user_id: str) -> User:
|
|
67
|
+
user = repo.find_by_id(user_id)
|
|
68
|
+
if user is None:
|
|
69
|
+
raise NotFoundError(f"User {user_id} not found")
|
|
70
|
+
return user
|
|
71
|
+
|
|
72
|
+
# ✅ GOOD: Catch specific, re-raise as domain error
|
|
73
|
+
def fetch_external_data(url: str) -> dict:
|
|
74
|
+
try:
|
|
75
|
+
response = httpx.get(url, timeout=10)
|
|
76
|
+
response.raise_for_status()
|
|
77
|
+
return response.json()
|
|
78
|
+
except httpx.TimeoutException as e:
|
|
79
|
+
raise ExternalServiceError(f"Timeout calling {url}") from e
|
|
80
|
+
except httpx.HTTPStatusError as e:
|
|
81
|
+
raise ExternalServiceError(f"HTTP {e.response.status_code} from {url}") from e
|
|
82
|
+
|
|
83
|
+
# ❌ BAD: Bare except swallows everything
|
|
84
|
+
try:
|
|
85
|
+
result = process()
|
|
86
|
+
except:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
# ❌ BAD: Catching too broadly
|
|
90
|
+
try:
|
|
91
|
+
result = process()
|
|
92
|
+
except Exception:
|
|
93
|
+
logger.error("Something went wrong")
|
|
94
|
+
return None # Hides the real error
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 4. TypeScript Error Patterns
|
|
98
|
+
|
|
99
|
+
### Result Type (for expected failures)
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
type Result<T, E = Error> =
|
|
103
|
+
| { ok: true; value: T }
|
|
104
|
+
| { ok: false; error: E };
|
|
105
|
+
|
|
106
|
+
function parseConfig(raw: string): Result<Config, ValidationError> {
|
|
107
|
+
try {
|
|
108
|
+
const parsed = JSON.parse(raw);
|
|
109
|
+
if (!isValidConfig(parsed)) {
|
|
110
|
+
return { ok: false, error: new ValidationError("Invalid config shape") };
|
|
111
|
+
}
|
|
112
|
+
return { ok: true, value: parsed as Config };
|
|
113
|
+
} catch {
|
|
114
|
+
return { ok: false, error: new ValidationError("Invalid JSON") };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Usage — caller must handle both cases
|
|
119
|
+
const result = parseConfig(rawInput);
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
showError(result.error.message);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
useConfig(result.value);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Error Boundaries (React)
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
'use client';
|
|
131
|
+
import { Component, type ReactNode } from 'react';
|
|
132
|
+
|
|
133
|
+
interface Props { children: ReactNode; fallback: ReactNode; }
|
|
134
|
+
interface State { hasError: boolean; }
|
|
135
|
+
|
|
136
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
137
|
+
state: State = { hasError: false };
|
|
138
|
+
|
|
139
|
+
static getDerivedStateFromError(): State {
|
|
140
|
+
return { hasError: true };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
|
144
|
+
console.error('Component error:', error, info.componentStack);
|
|
145
|
+
// Report to error tracking service
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
render() {
|
|
149
|
+
return this.state.hasError ? this.props.fallback : this.props.children;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 5. Retry with Exponential Backoff
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import asyncio
|
|
158
|
+
import random
|
|
159
|
+
from loguru import logger
|
|
160
|
+
|
|
161
|
+
async def retry_with_backoff(
|
|
162
|
+
fn,
|
|
163
|
+
max_retries: int = 3,
|
|
164
|
+
base_delay: float = 1.0,
|
|
165
|
+
max_delay: float = 30.0,
|
|
166
|
+
retryable_exceptions: tuple = (TransientError,),
|
|
167
|
+
):
|
|
168
|
+
"""Retry a function with exponential backoff and jitter."""
|
|
169
|
+
for attempt in range(max_retries + 1):
|
|
170
|
+
try:
|
|
171
|
+
return await fn()
|
|
172
|
+
except retryable_exceptions as e:
|
|
173
|
+
if attempt == max_retries:
|
|
174
|
+
logger.error(f"All {max_retries} retries exhausted: {e}")
|
|
175
|
+
raise
|
|
176
|
+
delay = min(base_delay * (2 ** attempt), max_delay)
|
|
177
|
+
jitter = random.uniform(0, delay * 0.1)
|
|
178
|
+
logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay:.1f}s")
|
|
179
|
+
await asyncio.sleep(delay + jitter)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## 6. FastAPI Error Handling
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from fastapi import FastAPI, Request
|
|
186
|
+
from fastapi.responses import JSONResponse
|
|
187
|
+
from loguru import logger
|
|
188
|
+
|
|
189
|
+
app = FastAPI()
|
|
190
|
+
|
|
191
|
+
@app.exception_handler(NotFoundError)
|
|
192
|
+
async def not_found_handler(request: Request, exc: NotFoundError):
|
|
193
|
+
return JSONResponse(status_code=404, content={"error": exc.code, "message": exc.message})
|
|
194
|
+
|
|
195
|
+
@app.exception_handler(ValidationError)
|
|
196
|
+
async def validation_handler(request: Request, exc: ValidationError):
|
|
197
|
+
return JSONResponse(status_code=422, content={"error": exc.code, "message": exc.message})
|
|
198
|
+
|
|
199
|
+
@app.exception_handler(AuthorizationError)
|
|
200
|
+
async def auth_handler(request: Request, exc: AuthorizationError):
|
|
201
|
+
return JSONResponse(status_code=403, content={"error": exc.code, "message": exc.message})
|
|
202
|
+
|
|
203
|
+
@app.exception_handler(Exception)
|
|
204
|
+
async def unhandled_handler(request: Request, exc: Exception):
|
|
205
|
+
logger.exception(f"Unhandled error on {request.method} {request.url}")
|
|
206
|
+
# Never expose internal details to the client
|
|
207
|
+
return JSONResponse(status_code=500, content={"error": "InternalError", "message": "An internal error occurred."})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## 7. Logging Errors Correctly
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from loguru import logger
|
|
214
|
+
|
|
215
|
+
# ✅ GOOD: Log with context, not just the message
|
|
216
|
+
logger.error(f"Failed to process order {order_id}: {e}", order_id=order_id, customer_id=customer_id)
|
|
217
|
+
|
|
218
|
+
# ✅ GOOD: Use exception() to capture stack trace
|
|
219
|
+
try:
|
|
220
|
+
process_order(order)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.exception(f"Order processing failed for {order.id}")
|
|
223
|
+
raise
|
|
224
|
+
|
|
225
|
+
# ❌ BAD: Logging sensitive data
|
|
226
|
+
logger.error(f"Auth failed for {username} with password {password}")
|
|
227
|
+
|
|
228
|
+
# ❌ BAD: Logging without context
|
|
229
|
+
logger.error("Something failed")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 8. Error Message Guidelines
|
|
233
|
+
|
|
234
|
+
| Audience | Include | Exclude |
|
|
235
|
+
|----------|---------|---------|
|
|
236
|
+
| **End user** | What happened, what to do next | Stack traces, internal IDs, SQL errors |
|
|
237
|
+
| **Developer (logs)** | Full context, request ID, stack trace | User passwords, tokens, PII |
|
|
238
|
+
| **API consumer** | Error code, human message, field-level detail | Server internals, file paths |
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# User-facing message
|
|
242
|
+
"Unable to load your dashboard. Please try again in a few minutes."
|
|
243
|
+
|
|
244
|
+
# API response
|
|
245
|
+
{"error": "ValidationError", "message": "Email format is invalid", "field": "email"}
|
|
246
|
+
|
|
247
|
+
# Log entry
|
|
248
|
+
"Validation failed on POST /api/users: email='not-an-email' — rejected by regex validator"
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## 9. Anti-Patterns
|
|
252
|
+
|
|
253
|
+
| Anti-Pattern | Problem | Fix |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| Bare `except:` / `except Exception:` | Catches KeyboardInterrupt, SystemExit | Catch specific exceptions |
|
|
256
|
+
| `return None` on error | Caller has no idea what went wrong | Raise typed exception |
|
|
257
|
+
| Logging and re-raising | Double logging, noisy | Log at the boundary, not at every layer |
|
|
258
|
+
| String error codes | Typos, no autocomplete | Enum or class-based error codes |
|
|
259
|
+
| Swallowing errors in background tasks | Silent failures, data loss | Log + alert + dead-letter queue |
|
|
260
|
+
| Generic "Something went wrong" | User can't self-serve | Specific message + action hint |
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: excalidraw-db
|
|
3
|
+
description: Turn a SQL artifact into an Excalidraw diagram for a design review / ARB pack / slide — higher-level, drag-the-boxes format
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /excalidraw-db — diagram a SQL artifact as Excalidraw
|
|
7
|
+
|
|
8
|
+
Explain a SQL artifact as an **Excalidraw** diagram — for a design review, an ARB pack, or a slide deck,
|
|
9
|
+
anywhere someone needs to drag boxes around rather than read a text file. For a git-tracked reference
|
|
10
|
+
diagram, prefer `/mermaid-db` instead.
|
|
11
|
+
|
|
12
|
+
Input: **$ARGUMENTS**
|
|
13
|
+
|
|
14
|
+
## Do this
|
|
15
|
+
|
|
16
|
+
Load and follow the **`db-diagram`** skill; produce the Excalidraw output specifically. In short:
|
|
17
|
+
|
|
18
|
+
1. **Get the SQL** — same resolution as `/mermaid-db` (file / DB object via MCP or read-only `sqlcmd` /
|
|
19
|
+
context / pasted). Multiple objects → ONE diagram of their relationships.
|
|
20
|
+
2. **Generate the `.excalidraw` directly** — run the skill's extractor with
|
|
21
|
+
`scripts/sql_to_graph.py --file <path> --format excalidraw` and save the output as a `.excalidraw` file.
|
|
22
|
+
The generator lays out deterministically on a **white canvas**: a query becomes a **left-to-right data
|
|
23
|
+
flow** (sources → filters → result → projection) with container-bound text that stays inside every box;
|
|
24
|
+
schema DDL becomes an **ER diagram** where each entity has a distinct **header + bulleted columns** and
|
|
25
|
+
every FK arrow is **edge-anchored and orthogonally routed so it never crosses (or hides behind) a box** —
|
|
26
|
+
even on hub / chain / self-reference schemas. No separate drawing skill, no MCP server.
|
|
27
|
+
3. **Optional no-app preview** — for a shareable page, `--format html` emits a self-contained HTML (inline
|
|
28
|
+
SVG, a light/dark toggle that seeds from the OS and never half-applies, no external requests);
|
|
29
|
+
`--format svg` emits a standalone SVG.
|
|
30
|
+
4. **Narrate + caveat** — include the business context and the execution-plan caveat (verbatim, per the
|
|
31
|
+
skill).
|
|
32
|
+
|
|
33
|
+
## Rules (from the skill — non-negotiable)
|
|
34
|
+
|
|
35
|
+
- **Read-only.** Never run DDL/DML.
|
|
36
|
+
- **Never guess schema.** Mark anything inferred-from-SQL-only.
|
|
37
|
+
- **Don't maintain both formats for one artifact.** If a Mermaid diagram already exists for this object,
|
|
38
|
+
say so and ask: a one-off review copy, or a replacement?
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fastapi-dev
|
|
3
|
+
description: Build FastAPI backends with standard patterns, error handling, and testing
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# FastAPI Development
|
|
7
|
+
|
|
8
|
+
> **Project Context** — Read `project-config.md` in the repo root for brand tokens, shared-library paths, and deployment targets.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
Invoke this skill when:
|
|
13
|
+
- Creating a new FastAPI backend for a project
|
|
14
|
+
- Adding API endpoints for Streamlit frontends
|
|
15
|
+
- Implementing repository pattern with SQL Server
|
|
16
|
+
- Setting up dependency injection chains
|
|
17
|
+
- Configuring CORS for Streamlit ↔ FastAPI communication
|
|
18
|
+
- Deploying FastAPI on IIS/on-premise infrastructure
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Project Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
backend/
|
|
26
|
+
├── app/
|
|
27
|
+
│ ├── __init__.py
|
|
28
|
+
│ ├── main.py # FastAPI application entry
|
|
29
|
+
│ ├── config.py # Settings (pydantic-settings)
|
|
30
|
+
│ ├── dependencies.py # Shared DI providers
|
|
31
|
+
│ ├── routers/
|
|
32
|
+
│ │ ├── __init__.py
|
|
33
|
+
│ │ ├── records.py # Records endpoints
|
|
34
|
+
│ │ └── analytics.py # Analytics endpoints
|
|
35
|
+
│ ├── models/
|
|
36
|
+
│ │ ├── __init__.py
|
|
37
|
+
│ │ ├── record.py # Pydantic request/response models
|
|
38
|
+
│ │ └── user.py
|
|
39
|
+
│ ├── services/
|
|
40
|
+
│ │ ├── __init__.py
|
|
41
|
+
│ │ └── record_service.py
|
|
42
|
+
│ └── repositories/
|
|
43
|
+
│ ├── __init__.py
|
|
44
|
+
│ └── record_repo.py
|
|
45
|
+
├── tests/
|
|
46
|
+
├── .env
|
|
47
|
+
└── requirements.txt
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Steps
|
|
53
|
+
|
|
54
|
+
### Step 1: Configuration (pydantic-settings)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# app/config.py
|
|
58
|
+
from functools import lru_cache
|
|
59
|
+
from pydantic_settings import BaseSettings
|
|
60
|
+
from pydantic import Field
|
|
61
|
+
|
|
62
|
+
class Settings(BaseSettings):
|
|
63
|
+
app_name: str = "API"
|
|
64
|
+
debug: bool = False
|
|
65
|
+
db_server: str = Field(..., description="SQL Server host")
|
|
66
|
+
db_name: str = Field(..., description="Database name")
|
|
67
|
+
db_password: str = Field(..., repr=False)
|
|
68
|
+
api_key: str = Field(..., repr=False)
|
|
69
|
+
allowed_origins: list[str] = ["http://localhost:8501"]
|
|
70
|
+
ollama_base_url: str = "http://localhost:11434"
|
|
71
|
+
ollama_model: str = "llama3.1"
|
|
72
|
+
|
|
73
|
+
class Config:
|
|
74
|
+
env_file = ".env"
|
|
75
|
+
|
|
76
|
+
@lru_cache
|
|
77
|
+
def get_settings() -> Settings:
|
|
78
|
+
return Settings()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 2: Pydantic Models (Base/Create/Update/Response pattern)
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# app/models/record.py
|
|
85
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
86
|
+
from uuid import UUID
|
|
87
|
+
from datetime import datetime
|
|
88
|
+
from typing import Literal
|
|
89
|
+
|
|
90
|
+
class RecordBase(BaseModel):
|
|
91
|
+
customer_id: str = Field(..., min_length=1, max_length=50, example="CUST001")
|
|
92
|
+
record_type: Literal["billing", "service", "technical"]
|
|
93
|
+
description: str = Field(..., min_length=10, max_length=5000)
|
|
94
|
+
priority: int = Field(default=3, ge=1, le=5)
|
|
95
|
+
|
|
96
|
+
class RecordCreate(RecordBase):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
class RecordUpdate(BaseModel):
|
|
100
|
+
status: Literal["open", "in_progress", "resolved"] | None = None
|
|
101
|
+
resolution_notes: str | None = Field(None, max_length=2000)
|
|
102
|
+
priority: int | None = Field(None, ge=1, le=5)
|
|
103
|
+
|
|
104
|
+
class RecordResponse(RecordBase):
|
|
105
|
+
model_config = ConfigDict(from_attributes=True)
|
|
106
|
+
id: UUID
|
|
107
|
+
status: str
|
|
108
|
+
created_at: datetime
|
|
109
|
+
updated_at: datetime | None = None
|
|
110
|
+
|
|
111
|
+
class RecordList(BaseModel):
|
|
112
|
+
items: list[RecordResponse]
|
|
113
|
+
total: int
|
|
114
|
+
page: int
|
|
115
|
+
per_page: int
|
|
116
|
+
pages: int
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Step 3: Dependency Injection Chain
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# app/dependencies.py
|
|
123
|
+
from typing import Annotated, Generator
|
|
124
|
+
from fastapi import Depends, Header, HTTPException
|
|
125
|
+
from libs.data import DatabaseAdapter
|
|
126
|
+
|
|
127
|
+
def get_db_adapter() -> Generator[DatabaseAdapter, None, None]:
|
|
128
|
+
adapter = DatabaseAdapter()
|
|
129
|
+
try:
|
|
130
|
+
yield adapter
|
|
131
|
+
finally:
|
|
132
|
+
adapter.close()
|
|
133
|
+
|
|
134
|
+
def get_record_repository(
|
|
135
|
+
db: Annotated[DatabaseAdapter, Depends(get_db_adapter)]
|
|
136
|
+
) -> RecordRepository:
|
|
137
|
+
return RecordRepository(db)
|
|
138
|
+
|
|
139
|
+
def get_record_service(
|
|
140
|
+
repo: Annotated[RecordRepository, Depends(get_record_repository)]
|
|
141
|
+
) -> RecordService:
|
|
142
|
+
return RecordService(repo)
|
|
143
|
+
|
|
144
|
+
async def verify_api_key(
|
|
145
|
+
x_api_key: Annotated[str, Header(..., description="API Key")]
|
|
146
|
+
) -> str:
|
|
147
|
+
if x_api_key != get_settings().api_key:
|
|
148
|
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
|
149
|
+
return x_api_key
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Step 4: Router Pattern
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# app/routers/records.py
|
|
156
|
+
from fastapi import APIRouter, Depends, Query
|
|
157
|
+
from typing import Annotated
|
|
158
|
+
|
|
159
|
+
router = APIRouter(prefix="/records", tags=["records"])
|
|
160
|
+
|
|
161
|
+
@router.get("/", response_model=RecordList)
|
|
162
|
+
async def list_records(
|
|
163
|
+
service: Annotated[RecordService, Depends(get_record_service)],
|
|
164
|
+
page: int = Query(1, ge=1),
|
|
165
|
+
per_page: int = Query(25, ge=1, le=100),
|
|
166
|
+
status: str | None = None,
|
|
167
|
+
):
|
|
168
|
+
return await service.list_records(page=page, per_page=per_page, status=status)
|
|
169
|
+
|
|
170
|
+
@router.post("/", response_model=RecordResponse, status_code=201)
|
|
171
|
+
async def create_record(
|
|
172
|
+
record: RecordCreate,
|
|
173
|
+
service: Annotated[RecordService, Depends(get_record_service)],
|
|
174
|
+
):
|
|
175
|
+
return await service.create_record(record)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Step 5: Application Entry
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# app/main.py
|
|
182
|
+
from fastapi import FastAPI, Request
|
|
183
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
184
|
+
from fastapi.responses import JSONResponse
|
|
185
|
+
from loguru import logger
|
|
186
|
+
|
|
187
|
+
app = FastAPI(title="API", version="1.0.0")
|
|
188
|
+
|
|
189
|
+
# CORS for Streamlit frontends
|
|
190
|
+
app.add_middleware(
|
|
191
|
+
CORSMiddleware,
|
|
192
|
+
allow_origins=get_settings().allowed_origins,
|
|
193
|
+
allow_credentials=True,
|
|
194
|
+
allow_methods=["*"],
|
|
195
|
+
allow_headers=["*"],
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
app.include_router(records_router)
|
|
199
|
+
|
|
200
|
+
@app.get("/health")
|
|
201
|
+
async def health_check():
|
|
202
|
+
return {"status": "healthy", "service": "API"}
|
|
203
|
+
|
|
204
|
+
@app.exception_handler(Exception)
|
|
205
|
+
async def global_exception_handler(request: Request, exc: Exception):
|
|
206
|
+
logger.exception(f"Unhandled error on {request.url.path}: {exc}")
|
|
207
|
+
return JSONResponse(status_code=500, content={"detail": "An internal error occurred"})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Step 6: SQL Server Connection
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
# Using pyodbc for MSSQL with Windows Auth
|
|
214
|
+
import pyodbc
|
|
215
|
+
|
|
216
|
+
conn_str = (
|
|
217
|
+
"DRIVER={ODBC Driver 18 for SQL Server};"
|
|
218
|
+
f"SERVER={settings.db_server};"
|
|
219
|
+
f"DATABASE={settings.db_name};"
|
|
220
|
+
"Trusted_Connection=yes;"
|
|
221
|
+
"Encrypt=yes;"
|
|
222
|
+
"TrustServerCertificate=yes;"
|
|
223
|
+
)
|
|
224
|
+
conn = pyodbc.connect(conn_str, timeout=10)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Patterns and Examples
|
|
230
|
+
|
|
231
|
+
### Repository Pattern
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
class RecordRepository:
|
|
235
|
+
def __init__(self, adapter: DatabaseAdapter):
|
|
236
|
+
self._adapter = adapter
|
|
237
|
+
|
|
238
|
+
def get_by_id(self, record_id: UUID) -> dict | None:
|
|
239
|
+
query = "SELECT * FROM AppRecords WHERE CaseID = ?"
|
|
240
|
+
return self._adapter.fetch_one(query, (str(record_id),))
|
|
241
|
+
|
|
242
|
+
def get_paginated(self, page: int, per_page: int, status: str | None) -> tuple[list, int]:
|
|
243
|
+
count_query = "SELECT COUNT(*) FROM AppRecords"
|
|
244
|
+
params = []
|
|
245
|
+
if status:
|
|
246
|
+
count_query += " WHERE Status = ?"
|
|
247
|
+
params.append(status)
|
|
248
|
+
total = self._adapter.fetch_scalar(count_query, params)
|
|
249
|
+
|
|
250
|
+
query = "SELECT * FROM AppRecords"
|
|
251
|
+
if status:
|
|
252
|
+
query += " WHERE Status = ?"
|
|
253
|
+
query += " ORDER BY [Created Date Time] DESC OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"
|
|
254
|
+
params.extend([(page - 1) * per_page, per_page])
|
|
255
|
+
items = self._adapter.fetch_all(query, params)
|
|
256
|
+
return items, total
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Error Handling with loguru
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from loguru import logger
|
|
263
|
+
|
|
264
|
+
@router.post("/")
|
|
265
|
+
async def create_record(record: RecordCreate, service: ...):
|
|
266
|
+
try:
|
|
267
|
+
result = await service.create_record(record)
|
|
268
|
+
logger.info(f"Created record {result.id} for customer {record.customer_id}")
|
|
269
|
+
return result
|
|
270
|
+
except ValidationError as e:
|
|
271
|
+
logger.warning(f"Validation error: {e}")
|
|
272
|
+
raise HTTPException(status_code=422, detail=str(e))
|
|
273
|
+
except Exception as e:
|
|
274
|
+
logger.exception(f"Failed to create record: {e}")
|
|
275
|
+
raise HTTPException(status_code=500, detail="Internal error")
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Checklist
|
|
281
|
+
|
|
282
|
+
- [ ] Project structure follows standard pattern (routers/models/services/repositories)
|
|
283
|
+
- [ ] Pydantic models use Base/Create/Update/Response hierarchy
|
|
284
|
+
- [ ] Dependency injection chain: adapter -> repo -> service
|
|
285
|
+
- [ ] CORS configured for Streamlit frontend origins
|
|
286
|
+
- [ ] Health check endpoint at `/health`
|
|
287
|
+
- [ ] Global exception handler with loguru logging
|
|
288
|
+
- [ ] SQL queries use parameterized inputs (no f-strings)
|
|
289
|
+
- [ ] Configuration via pydantic-settings and .env file
|
|
290
|
+
- [ ] API key verification on protected endpoints
|
|
291
|
+
- [ ] All endpoints have proper response_model types
|
|
292
|
+
|
|
293
|
+
## Related Resources
|
|
294
|
+
|
|
295
|
+
| Resource | Path |
|
|
296
|
+
|----------|------|
|
|
297
|
+
| FastAPI instructions | `instructions/fastapi.instructions.md` |
|
|
298
|
+
| SQL expert skill | `skills/coding/sql/SKILL.md` |
|
|
299
|
+
| Python instructions | `instructions/python.instructions.md` |
|
|
300
|
+
| Security instructions | `instructions/security.instructions.md` |
|