start-vibing-stacks 2.15.0 → 2.17.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/package.json +1 -1
- package/stacks/_shared/skills/docker-patterns/SKILL.md +184 -31
- package/stacks/_shared/skills/git-workflow/SKILL.md +140 -17
- package/stacks/_shared/skills/hook-development/SKILL.md +230 -52
- package/stacks/_shared/skills/observability/SKILL.md +72 -2
- package/stacks/_shared/skills/openapi-design/SKILL.md +111 -3
- package/stacks/_shared/skills/playwright-automation/SKILL.md +173 -27
- package/stacks/_shared/skills/postgres-patterns/SKILL.md +85 -4
- package/stacks/_shared/skills/secrets-management/SKILL.md +98 -12
- package/stacks/_shared/skills/security-baseline/SKILL.md +115 -18
- package/stacks/_shared/skills/ui-ux-audit/SKILL.md +94 -35
- package/stacks/php/skills/composer-workflow/SKILL.md +118 -34
- package/stacks/php/skills/external-api-patterns/SKILL.md +171 -89
- package/stacks/php/skills/laravel-octane/SKILL.md +74 -2
- package/stacks/php/skills/mariadb-octane/SKILL.md +96 -4
- package/stacks/php/skills/php-patterns/SKILL.md +98 -5
- package/stacks/php/skills/phpstan-analysis/SKILL.md +136 -30
- package/stacks/php/skills/phpunit-testing/SKILL.md +247 -61
- package/stacks/php/skills/security-scan-php/SKILL.md +96 -3
|
@@ -1,93 +1,271 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hook-development
|
|
3
|
-
version:
|
|
3
|
+
version: 2.0.0
|
|
4
|
+
description: "Claude Code hook development for 2026. Covers all 9 supported events (UserPromptSubmit, PreToolUse, PostToolUse, Notification, Stop, SubagentStop, SessionStart, SessionEnd, PreCompact), stdin/stdout JSON contract, decision/continue/systemMessage outputs, matchers (tool patterns), exit-code semantics, cycle detection (stop_hook_active), settings.json registration, run-hook.sh dispatcher (bun → tsx → node fallback). Invoke when creating or modifying any .claude/hooks/* file or settings.json hook entry."
|
|
4
5
|
---
|
|
5
6
|
|
|
6
|
-
# Hook Development — Claude Code Hooks
|
|
7
|
+
# Hook Development — Claude Code Hooks (2026)
|
|
7
8
|
|
|
8
9
|
**ALWAYS invoke when creating or modifying Claude Code hooks.**
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
> Hooks are user-controlled deterministic gates around the model. Anything you want **enforced**, not asked nicely — put in a hook. The agent cannot disable a hook from inside the conversation.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|-------|------|----------|
|
|
14
|
-
| `UserPromptSubmit` | Before prompt is sent | Inject workflow, validate input |
|
|
15
|
-
| `Stop` | Before task completion | Validate state, block if dirty |
|
|
16
|
-
| `PreToolUse` | Before tool execution | Approve/block dangerous tools |
|
|
17
|
-
| `PostToolUse` | After tool execution | Log, validate output |
|
|
13
|
+
## 1. Supported events (Claude Code, 2026)
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
| Event | Fires | Common use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `SessionStart` | When a session begins (new chat or resume) | Inject onboarding context, restore state |
|
|
18
|
+
| `SessionEnd` | When a session ends cleanly | Flush logs, persist state, run cleanup |
|
|
19
|
+
| `UserPromptSubmit` | After user submits, before model receives | Inject workflow rules, block forbidden phrases |
|
|
20
|
+
| `PreToolUse` | Before any tool is invoked | Approve / block / rewrite tool calls (security gate) |
|
|
21
|
+
| `PostToolUse` | After tool completes | Log, validate output, react to specific tool results |
|
|
22
|
+
| `Notification` | When the agent is idle and notifies (e.g. waiting for input) | Desktop notifications, status forwarding |
|
|
23
|
+
| `Stop` | Before the agent ends its turn | Validate completion (clean tree, tests pass, docs updated) |
|
|
24
|
+
| `SubagentStop` | When a Task() subagent finishes | Validate subagent output, persist artifacts |
|
|
25
|
+
| `PreCompact` | Before transcript compaction is triggered | Snapshot/export full transcript first |
|
|
26
|
+
|
|
27
|
+
`Stop` and `Subagent` events are the most commonly customized. `PreToolUse` is the security workhorse.
|
|
28
|
+
|
|
29
|
+
## 2. File layout
|
|
20
30
|
|
|
21
31
|
```
|
|
22
|
-
.claude/
|
|
23
|
-
├──
|
|
24
|
-
├──
|
|
25
|
-
|
|
32
|
+
.claude/
|
|
33
|
+
├── hooks/
|
|
34
|
+
│ ├── run-hook.sh # Entry point: bun → tsx → node fallback
|
|
35
|
+
│ ├── session-start.ts
|
|
36
|
+
│ ├── session-end.ts
|
|
37
|
+
│ ├── user-prompt-submit.ts
|
|
38
|
+
│ ├── pre-tool-use.ts
|
|
39
|
+
│ ├── post-tool-use.ts
|
|
40
|
+
│ ├── stop-validator.ts
|
|
41
|
+
│ ├── subagent-stop.ts
|
|
42
|
+
│ ├── pre-compact.ts
|
|
43
|
+
│ └── lib/ # shared helpers (cmd(), readStdin(), etc.)
|
|
44
|
+
└── settings.json # registers hooks
|
|
26
45
|
```
|
|
27
46
|
|
|
28
|
-
##
|
|
47
|
+
## 3. Stdin contract (per event)
|
|
48
|
+
|
|
49
|
+
All hook input arrives as **a single JSON object on stdin**. Read with a timeout — never hang.
|
|
29
50
|
|
|
30
51
|
```typescript
|
|
31
|
-
//
|
|
32
|
-
interface
|
|
33
|
-
user_prompt: string;
|
|
52
|
+
// Common envelope (most events)
|
|
53
|
+
interface HookInput {
|
|
34
54
|
session_id: string;
|
|
55
|
+
cwd: string;
|
|
56
|
+
transcript_path?: string;
|
|
57
|
+
hook_event_name: string; // e.g. "Stop"
|
|
35
58
|
}
|
|
36
59
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
stop_hook_active?: boolean; // Cycle detection
|
|
40
|
-
transcript?: string;
|
|
60
|
+
interface UserPromptSubmitInput extends HookInput {
|
|
61
|
+
user_prompt: string;
|
|
41
62
|
}
|
|
42
|
-
```
|
|
43
63
|
|
|
44
|
-
|
|
64
|
+
interface PreToolUseInput extends HookInput {
|
|
65
|
+
tool_name: string; // "Bash" | "Edit" | "Write" | ...
|
|
66
|
+
tool_input: Record<string, unknown>;
|
|
67
|
+
}
|
|
45
68
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
interface PostToolUseInput extends PreToolUseInput {
|
|
70
|
+
tool_response: unknown;
|
|
71
|
+
tool_error?: string;
|
|
72
|
+
}
|
|
49
73
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
interface StopInput extends HookInput {
|
|
75
|
+
stop_hook_active?: boolean; // TRUE if a previous Stop hook already blocked — don't loop
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface SubagentStopInput extends StopInput {
|
|
79
|
+
subagent_id: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface PreCompactInput extends HookInput {
|
|
83
|
+
trigger: "manual" | "auto";
|
|
84
|
+
}
|
|
53
85
|
```
|
|
54
86
|
|
|
55
|
-
##
|
|
87
|
+
## 4. Stdout contract (per event)
|
|
56
88
|
|
|
57
|
-
|
|
58
|
-
#!/usr/bin/env node
|
|
59
|
-
import { execSync } from 'child_process';
|
|
89
|
+
Output a **single JSON object on stdout**. Anything else (or invalid JSON) is treated as advisory only.
|
|
60
90
|
|
|
61
|
-
|
|
62
|
-
|
|
91
|
+
```typescript
|
|
92
|
+
// Universal fields
|
|
93
|
+
interface HookOutput {
|
|
94
|
+
continue?: boolean; // false = stop the agent now (Stop family)
|
|
95
|
+
decision?: "approve" | "block"; // gate verdict
|
|
96
|
+
reason?: string; // shown to user / logged
|
|
97
|
+
systemMessage?: string; // injected as system message (UserPromptSubmit, SessionStart)
|
|
98
|
+
hookSpecificOutput?: Record<string, unknown>;
|
|
63
99
|
}
|
|
100
|
+
```
|
|
64
101
|
|
|
65
|
-
|
|
66
|
-
|
|
102
|
+
PreToolUse — block / approve / rewrite a tool call:
|
|
103
|
+
```json
|
|
104
|
+
{ "decision": "approve", "reason": "Edit on src/ allowed" }
|
|
105
|
+
{ "decision": "block", "reason": "Write outside src/ rejected" }
|
|
106
|
+
```
|
|
67
107
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
108
|
+
Stop — block prevents finalization until issues are fixed:
|
|
109
|
+
```json
|
|
110
|
+
{ "continue": true, "decision": "block", "reason": "Uncommitted files" }
|
|
111
|
+
```
|
|
71
112
|
|
|
72
|
-
|
|
113
|
+
SessionStart — inject context:
|
|
114
|
+
```json
|
|
115
|
+
{ "systemMessage": "ROUTINE: research → plan → implement → test → commit" }
|
|
73
116
|
```
|
|
74
117
|
|
|
75
|
-
##
|
|
118
|
+
## 5. Exit-code semantics
|
|
119
|
+
|
|
120
|
+
| Exit | Meaning |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `0` | Success — JSON output applied if present |
|
|
123
|
+
| `2` | **Blocking** — message printed to stderr is shown to the user (alternative to `decision: "block"`) |
|
|
124
|
+
| anything else | Non-blocking error — logged, agent continues |
|
|
125
|
+
|
|
126
|
+
**Never** exit non-zero on a transient error (e.g. network) unless you intend to block. Hooks must be deterministic.
|
|
127
|
+
|
|
128
|
+
## 6. `settings.json` registration
|
|
76
129
|
|
|
77
130
|
```json
|
|
78
131
|
{
|
|
79
132
|
"hooks": {
|
|
80
|
-
"
|
|
81
|
-
|
|
133
|
+
"SessionStart": [
|
|
134
|
+
{
|
|
135
|
+
"hooks": [{
|
|
136
|
+
"type": "command",
|
|
137
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" session-start",
|
|
138
|
+
"timeout": 10
|
|
139
|
+
}]
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"UserPromptSubmit": [
|
|
143
|
+
{
|
|
144
|
+
"matcher": "",
|
|
145
|
+
"hooks": [{
|
|
146
|
+
"type": "command",
|
|
147
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" user-prompt-submit",
|
|
148
|
+
"timeout": 10
|
|
149
|
+
}]
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"PreToolUse": [
|
|
153
|
+
{
|
|
154
|
+
"matcher": "Bash|Write|Edit",
|
|
155
|
+
"hooks": [{
|
|
156
|
+
"type": "command",
|
|
157
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" pre-tool-use",
|
|
158
|
+
"timeout": 10
|
|
159
|
+
}]
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
"Stop": [
|
|
163
|
+
{
|
|
164
|
+
"hooks": [{
|
|
165
|
+
"type": "command",
|
|
166
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" stop-validator",
|
|
167
|
+
"timeout": 30
|
|
168
|
+
}]
|
|
169
|
+
}
|
|
170
|
+
]
|
|
82
171
|
}
|
|
83
172
|
}
|
|
84
173
|
```
|
|
85
174
|
|
|
86
|
-
|
|
175
|
+
`matcher` is a regex against `tool_name` (PreToolUse / PostToolUse) or empty for "all". Multiple entries per event are run in registration order.
|
|
176
|
+
|
|
177
|
+
## 7. `run-hook.sh` dispatcher
|
|
178
|
+
|
|
179
|
+
Survives whichever runtime the user has installed. Always-zero exit so hook errors don't kill the session.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
#!/usr/bin/env bash
|
|
183
|
+
# .claude/hooks/run-hook.sh
|
|
184
|
+
set -uo pipefail
|
|
185
|
+
HOOK_NAME="${1:?missing hook name}"
|
|
186
|
+
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
187
|
+
HOOK_FILE="$HOOK_DIR/${HOOK_NAME}.ts"
|
|
188
|
+
|
|
189
|
+
[[ -f "$HOOK_FILE" ]] || { echo '{"continue":true}'; exit 0; }
|
|
190
|
+
|
|
191
|
+
if command -v bun >/dev/null 2>&1; then bun "$HOOK_FILE"
|
|
192
|
+
elif command -v tsx >/dev/null 2>&1; then tsx "$HOOK_FILE"
|
|
193
|
+
elif command -v node >/dev/null 2>&1; then npx --yes tsx "$HOOK_FILE"
|
|
194
|
+
else echo '{"continue":true}'; exit 0
|
|
195
|
+
fi
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 8. Stop validator template (gate-aware, with cycle detection)
|
|
199
|
+
|
|
200
|
+
Use `execFileSync` (not `execSync` with shell strings) — passes args as an array, immune to shell injection.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
#!/usr/bin/env node
|
|
204
|
+
import { execFileSync } from 'node:child_process';
|
|
205
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
206
|
+
|
|
207
|
+
function git(...args: string[]): string {
|
|
208
|
+
try { return execFileSync('git', args, { encoding: 'utf8' }).trim(); } catch { return ''; }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const input = JSON.parse(readFileSync(0, 'utf8')); // stdin
|
|
212
|
+
|
|
213
|
+
// Cycle detection — if we already blocked once, approve to avoid infinite loop
|
|
214
|
+
if (input.stop_hook_active) {
|
|
215
|
+
console.log(JSON.stringify({ continue: false, decision: 'approve' }));
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const dirty = git('status', '--porcelain');
|
|
220
|
+
const issues: string[] = [];
|
|
221
|
+
|
|
222
|
+
if (dirty) issues.push(`GIT_TREE_NOT_CLEAN: ${dirty.split('\n').length} file(s)`);
|
|
223
|
+
if (existsSync('CLAUDE.md') &&
|
|
224
|
+
!readFileSync('CLAUDE.md', 'utf8').includes('Last Change')) issues.push('CLAUDE_MD_NOT_UPDATED');
|
|
225
|
+
|
|
226
|
+
const result = issues.length === 0
|
|
227
|
+
? { continue: false, decision: 'approve', reason: 'All checks passed' }
|
|
228
|
+
: { continue: true, decision: 'block', reason: issues.join(' | ') };
|
|
229
|
+
|
|
230
|
+
console.log(JSON.stringify(result));
|
|
231
|
+
process.exit(0);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## 9. Performance & safety rules
|
|
235
|
+
|
|
236
|
+
1. **Timeouts:** 10s for `UserPromptSubmit` / `PreToolUse` / `SessionStart`; 30s for `Stop` / `SubagentStop`.
|
|
237
|
+
2. **Always exit 0** unless intentionally blocking with exit 2 — non-zero kills the session.
|
|
238
|
+
3. **Read stdin with timeout** — `readFileSync(0, 'utf8')` is fine (it returns immediately when EOF).
|
|
239
|
+
4. **Output valid JSON** — invalid JSON is treated as advisory and silently dropped.
|
|
240
|
+
5. **Cycle detection** — check `stop_hook_active` in `Stop` / `SubagentStop`. Without this, a buggy hook can lock the agent in an infinite block-fix-block loop.
|
|
241
|
+
6. **No network calls** in hot-path hooks (`PreToolUse`, `UserPromptSubmit`) — they fire on every tool call / prompt.
|
|
242
|
+
7. **Idempotent** — hooks may be invoked twice (retries, restarts). Don't write to immutable state without a guard.
|
|
243
|
+
8. **Use `execFileSync`, never `execSync` with a shell string** — the latter is shell-injection-prone if any input ever flows in.
|
|
244
|
+
|
|
245
|
+
## 10. Common patterns
|
|
246
|
+
|
|
247
|
+
| Goal | Event | Pattern |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| "Inject the current routine on every prompt" | `UserPromptSubmit` | `{ continue: true, systemMessage: "ROUTINE: ..." }` |
|
|
250
|
+
| "Block writes outside `src/`" | `PreToolUse` | match `Write\|Edit`, inspect `tool_input.path`, `decision: "block"` if outside |
|
|
251
|
+
| "Run quality gate before finishing" | `Stop` | run typecheck/lint/tests, `decision: "block"` if any fail |
|
|
252
|
+
| "Persist subagent output to disk" | `SubagentStop` | parse `tool_response`, write to `.claude/subagent-outputs/{id}.json` |
|
|
253
|
+
| "Snapshot transcript before compaction" | `PreCompact` | copy `transcript_path` to `.claude/transcripts/{session_id}.jsonl` |
|
|
254
|
+
|
|
255
|
+
## FORBIDDEN
|
|
256
|
+
|
|
257
|
+
| Pattern | Why |
|
|
258
|
+
|---|---|
|
|
259
|
+
| `process.exit(1)` on transient error | Kills the session |
|
|
260
|
+
| Hook output not valid JSON | Dropped silently — no enforcement |
|
|
261
|
+
| Network call in `PreToolUse` | Adds latency to every tool call |
|
|
262
|
+
| Forgetting `stop_hook_active` check | Infinite block loop |
|
|
263
|
+
| Writing to `CLAUDE.md` from `Stop` hook | Triggers another `Stop` cycle |
|
|
264
|
+
| Hardcoded user paths (`~/Users/me/...`) | Won't run on other machines |
|
|
265
|
+
| Shell-string `execSync` for git/fs commands | Shell-injection surface |
|
|
266
|
+
|
|
267
|
+
## See Also
|
|
87
268
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
4. **Output valid JSON to stdout** — Claude Code parses it
|
|
92
|
-
5. **Cycle detection** — check `stop_hook_active` flag in Stop hooks
|
|
93
|
-
6. **Keep hooks fast** — timeout applies (10s for prompt, 30s for stop)
|
|
269
|
+
- `quality-gate` — the Stop validator's typical payload
|
|
270
|
+
- `git-workflow` — branch / dirty-tree checks
|
|
271
|
+
- `commit-manager` — pairs with Stop validator (validator detects, commit-manager fixes)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: observability
|
|
3
|
-
version:
|
|
4
|
-
description: Structured logging, correlation IDs, OpenTelemetry tracing, error tracking (Sentry), metrics,
|
|
3
|
+
version: 2.0.0
|
|
4
|
+
description: "Structured logging, correlation IDs, OpenTelemetry tracing (semconv 1.41+, including GenAI conventions for Anthropic / OpenAI / AWS Bedrock / Azure AI / MCP), error tracking (Sentry), metrics, PII redaction, audit log separation. Invoke when adding logging, instrumentation, AI/LLM observability, or debugging production issues."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Observability — Logs, Traces, Metrics
|
|
@@ -285,6 +285,75 @@ Cardinality rule: never tag with user_id, email, or unbounded values — explode
|
|
|
285
285
|
|
|
286
286
|
---
|
|
287
287
|
|
|
288
|
+
## 6.5. AI / LLM Observability — OTel GenAI Semantic Conventions *(2026)*
|
|
289
|
+
|
|
290
|
+
OpenTelemetry GenAI conventions (semconv ≥ 1.37, current ≥ 1.41) standardize how LLM calls are traced. They cover: **model spans**, **agent spans**, **events** (inputs/outputs), and provider-specific conventions for **Anthropic, OpenAI, AWS Bedrock, Azure AI Inference, and MCP (Model Context Protocol)**.
|
|
291
|
+
|
|
292
|
+
> Status: Development (not yet stable). Set `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental` to opt in to current attribute names. **Do not use deprecated `gen_ai.prompt` / `gen_ai.completion` attributes** — they were removed in 1.28 in favor of log-based events.
|
|
293
|
+
|
|
294
|
+
### Required attributes on every LLM span
|
|
295
|
+
|
|
296
|
+
| Attribute | Example |
|
|
297
|
+
|---|---|
|
|
298
|
+
| `gen_ai.system` | `"anthropic"`, `"openai"`, `"aws.bedrock"`, `"azure.ai.inference"` |
|
|
299
|
+
| `gen_ai.operation.name` | `"chat"`, `"text_completion"`, `"embeddings"` |
|
|
300
|
+
| `gen_ai.request.model` | `"claude-opus-4-5"`, `"gpt-5"`, `"sonnet-4-5"` |
|
|
301
|
+
| `gen_ai.response.model` | The actual model that served the request (may differ on Bedrock/Gateway) |
|
|
302
|
+
| `gen_ai.usage.input_tokens` | Numeric |
|
|
303
|
+
| `gen_ai.usage.output_tokens` | Numeric |
|
|
304
|
+
| `gen_ai.response.finish_reasons` | `["stop"]`, `["length"]`, `["tool_calls"]` |
|
|
305
|
+
|
|
306
|
+
### Recording inputs/outputs as **events** (not attributes)
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
import { trace, SpanKind } from '@opentelemetry/api';
|
|
310
|
+
const tracer = trace.getTracer('llm');
|
|
311
|
+
|
|
312
|
+
await tracer.startActiveSpan('chat anthropic', { kind: SpanKind.CLIENT }, async (span) => {
|
|
313
|
+
span.setAttributes({
|
|
314
|
+
'gen_ai.system': 'anthropic',
|
|
315
|
+
'gen_ai.operation.name': 'chat',
|
|
316
|
+
'gen_ai.request.model': 'claude-opus-4-5',
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Inputs/outputs go as EVENTS, not attributes — keeps spans cheap, allows redaction
|
|
320
|
+
span.addEvent('gen_ai.user.message', { 'gen_ai.message.content': redact(userMsg) });
|
|
321
|
+
|
|
322
|
+
const res = await anthropic.messages.create({ /* ... */ });
|
|
323
|
+
|
|
324
|
+
span.setAttributes({
|
|
325
|
+
'gen_ai.response.model': res.model,
|
|
326
|
+
'gen_ai.usage.input_tokens': res.usage.input_tokens,
|
|
327
|
+
'gen_ai.usage.output_tokens': res.usage.output_tokens,
|
|
328
|
+
'gen_ai.response.finish_reasons': [res.stop_reason],
|
|
329
|
+
});
|
|
330
|
+
span.addEvent('gen_ai.assistant.message', { 'gen_ai.message.content': redact(res.content) });
|
|
331
|
+
span.end();
|
|
332
|
+
return res;
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### MCP (Model Context Protocol) spans
|
|
337
|
+
|
|
338
|
+
For agentic flows that call MCP servers:
|
|
339
|
+
- `gen_ai.system`: `"mcp"`
|
|
340
|
+
- `gen_ai.tool.name`: tool invoked
|
|
341
|
+
- Span kind: `CLIENT` (your agent → MCP server)
|
|
342
|
+
|
|
343
|
+
### Cost & token metrics
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
const meter = metrics.getMeter('llm');
|
|
347
|
+
const tokensUsed = meter.createCounter('gen_ai.client.token.usage', { unit: '{token}' });
|
|
348
|
+
|
|
349
|
+
tokensUsed.add(res.usage.input_tokens, { 'gen_ai.system': 'anthropic', 'gen_ai.token.type': 'input', 'gen_ai.request.model': 'claude-opus-4-5' });
|
|
350
|
+
tokensUsed.add(res.usage.output_tokens, { 'gen_ai.system': 'anthropic', 'gen_ai.token.type': 'output', 'gen_ai.request.model': 'claude-opus-4-5' });
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**PII rules still apply** — redact user content in events the same way you redact request bodies in HTTP logs.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
288
357
|
## 7. Health Checks
|
|
289
358
|
|
|
290
359
|
Two endpoints, distinct semantics:
|
|
@@ -331,6 +400,7 @@ Different retention (often longer), different access (security team), different
|
|
|
331
400
|
- [ ] Sentry initialized with `beforeSend` scrubber
|
|
332
401
|
- [ ] Tracing initialized at process start (before app code)
|
|
333
402
|
- [ ] `/healthz` + `/readyz` endpoints exist
|
|
403
|
+
- [ ] LLM calls emit `gen_ai.*` attributes + token-usage metrics; user content redacted
|
|
334
404
|
|
|
335
405
|
## FORBIDDEN
|
|
336
406
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: openapi-design
|
|
3
|
-
version:
|
|
4
|
-
description: OpenAPI 3.1 spec design — resource naming, status codes, problem+json (RFC 9457) errors, pagination, versioning, security schemes, idempotency, deprecation. Stack-agnostic. Invoke when designing or documenting any HTTP/REST API that publishes an OpenAPI spec, regardless of framework (Fastify, Hono, FastAPI, Laravel, Express, etc.).
|
|
3
|
+
version: 2.0.0
|
|
4
|
+
description: "OpenAPI 3.1 / 3.2 spec design — resource naming, status codes, problem+json (RFC 9457) errors, pagination, versioning, security schemes, idempotency, deprecation. Covers OpenAPI 3.2 (Sept 2025) deltas: native Server-Sent Events (SSE), JSON Lines streaming, hierarchical tags (parent/kind), QUERY HTTP method, additionalOperations, querystring location, $self document URI, OAuth2 device flow. Fully backward-compatible with 3.1 (no breaking changes). Stack-agnostic. Invoke when designing or documenting any HTTP/REST API that publishes an OpenAPI spec, regardless of framework (Fastify, Hono, FastAPI, Laravel, Express, etc.)."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# OpenAPI Design — Universal API Contract
|
|
7
|
+
# OpenAPI Design — Universal API Contract (3.1 + 3.2)
|
|
8
8
|
|
|
9
9
|
**Invoke before adding a new endpoint, before publishing a public API, and during code review of any route handler.**
|
|
10
10
|
|
|
@@ -12,6 +12,27 @@ description: OpenAPI 3.1 spec design — resource naming, status codes, problem+
|
|
|
12
12
|
|
|
13
13
|
This skill covers **what the spec should look like**. For **how to generate it** in your stack, see the per-framework skill (`fastify-api`, `hono-api`, `fastapi-patterns`, `laravel-patterns`, etc.).
|
|
14
14
|
|
|
15
|
+
## Version policy (2026)
|
|
16
|
+
|
|
17
|
+
- **OpenAPI 3.2** (Sept 2025): adopt for new specs — fully backward-compatible with 3.1, just bump the `openapi` field. **Zero breaking changes.**
|
|
18
|
+
- **OpenAPI 3.1** (Feb 2021): full JSON Schema 2020-12 — still fine for stable APIs.
|
|
19
|
+
- **OpenAPI 3.0**: legacy. Migrate when convenient.
|
|
20
|
+
|
|
21
|
+
### What 3.2 adds (cheat sheet)
|
|
22
|
+
|
|
23
|
+
| Feature | Use when |
|
|
24
|
+
|---|---|
|
|
25
|
+
| **Server-Sent Events (SSE)** as a first-class media type | Long-running progress, AI streaming responses, live dashboards |
|
|
26
|
+
| **JSON Lines** (`application/jsonl`) | Streaming row-oriented data (logs, exports) |
|
|
27
|
+
| **Multipart streaming** | Upload progress with metadata |
|
|
28
|
+
| **Hierarchical tags** (`parent`, `kind`, `summary`) | Replaces vendor-extensions `x-tagGroups`, `x-displayName`. Native nav for large APIs. |
|
|
29
|
+
| **`QUERY` HTTP method** | Safe & idempotent reads with a request body (search APIs that exceed URL limits) |
|
|
30
|
+
| **`additionalOperations`** field | Document custom HTTP verbs without spec extensions |
|
|
31
|
+
| **`querystring` parameter location** | Describe an entire query string as a single typed parameter |
|
|
32
|
+
| **`$self`** document URI | Solves multi-document `$ref` resolution |
|
|
33
|
+
| **OAuth2 Device Authorization flow** | CLI / IoT auth |
|
|
34
|
+
| **Discriminator `defaultMapping`** | Cleaner polymorphic schemas |
|
|
35
|
+
|
|
15
36
|
---
|
|
16
37
|
|
|
17
38
|
## 1. Resource Naming
|
|
@@ -52,6 +73,93 @@ The action sub-resource pattern (`/orders/{id}/cancel`) is the escape hatch when
|
|
|
52
73
|
|
|
53
74
|
---
|
|
54
75
|
|
|
76
|
+
## 2.5. Streaming Responses *(OpenAPI 3.2)*
|
|
77
|
+
|
|
78
|
+
Stop documenting streams as `200 application/json` with hand-rolled extensions. 3.2 lets you describe SSE, JSON Lines, and multipart streams natively.
|
|
79
|
+
|
|
80
|
+
### Server-Sent Events (SSE)
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
paths:
|
|
84
|
+
/chat/{id}/stream:
|
|
85
|
+
get:
|
|
86
|
+
summary: Stream assistant tokens as they generate
|
|
87
|
+
responses:
|
|
88
|
+
'200':
|
|
89
|
+
description: SSE stream
|
|
90
|
+
content:
|
|
91
|
+
text/event-stream:
|
|
92
|
+
schema:
|
|
93
|
+
type: object
|
|
94
|
+
properties:
|
|
95
|
+
event: { type: string, enum: [token, tool_call, done] }
|
|
96
|
+
data: { type: object }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### JSON Lines (newline-delimited JSON)
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
paths:
|
|
103
|
+
/exports/{id}:
|
|
104
|
+
get:
|
|
105
|
+
responses:
|
|
106
|
+
'200':
|
|
107
|
+
description: One JSON object per line
|
|
108
|
+
content:
|
|
109
|
+
application/jsonl:
|
|
110
|
+
schema:
|
|
111
|
+
$ref: '#/components/schemas/Order'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use SSE for **client subscribes to live updates** (chat tokens, progress); JSON Lines for **bulk row export** (logs, analytics dumps). Document the terminator (`event: done` for SSE; EOF for JSON Lines) so codegen clients know when to stop reading.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 2.6. Hierarchical Tags *(OpenAPI 3.2)*
|
|
119
|
+
|
|
120
|
+
Replaces the legacy `x-tagGroups` / `x-displayName` extensions. Native nested navigation in tools that support 3.2.
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
tags:
|
|
124
|
+
- name: billing
|
|
125
|
+
summary: Billing
|
|
126
|
+
kind: nav # 'nav' = navigation group only, no operations directly under it
|
|
127
|
+
- name: invoices
|
|
128
|
+
parent: billing
|
|
129
|
+
summary: Invoices
|
|
130
|
+
- name: payments
|
|
131
|
+
parent: billing
|
|
132
|
+
summary: Payments
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For tools still on 3.1, the same effect is achieved with `x-tagGroups` / `x-displayName`. Specs published as 3.2 should drop the extensions.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 2.7. The `QUERY` HTTP method *(OpenAPI 3.2)*
|
|
140
|
+
|
|
141
|
+
For **safe & idempotent** reads whose parameters exceed URL length limits (complex search, large filter sets). Bodies on `GET` are technically legal but proxies eat them; `QUERY` is the standardized escape hatch.
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
paths:
|
|
145
|
+
/search:
|
|
146
|
+
additionalOperations:
|
|
147
|
+
QUERY:
|
|
148
|
+
summary: Complex search
|
|
149
|
+
requestBody:
|
|
150
|
+
required: true
|
|
151
|
+
content:
|
|
152
|
+
application/json:
|
|
153
|
+
schema:
|
|
154
|
+
$ref: '#/components/schemas/SearchRequest'
|
|
155
|
+
responses:
|
|
156
|
+
'200': { $ref: '#/components/responses/SearchResults' }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`QUERY` is **safe** (no side effects) and **idempotent** (same body → same response) — clients and caches treat it like `GET`.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
55
163
|
## 3. Error Envelope — `application/problem+json` (RFC 9457)
|
|
56
164
|
|
|
57
165
|
One shape for every error response in the entire API. Codegen clients can match a single discriminator.
|