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.
@@ -1,93 +1,271 @@
1
1
  ---
2
2
  name: hook-development
3
- version: 1.0.0
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
- ## Hook Types
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
- | Event | When | Use Case |
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
- ## File Structure
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/hooks/
23
- ├── run-hook.sh # Entry point (bash → bun/tsx fallback)
24
- ├── user-prompt-submit.ts # Prompt injection
25
- └── stop-validator.ts # Task completion gate
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
- ## Hook Input (stdin JSON)
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
- // UserPromptSubmit
32
- interface PromptInput {
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
- // Stop
38
- interface StopInput {
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
- ## Hook Output (stdout JSON)
64
+ interface PreToolUseInput extends HookInput {
65
+ tool_name: string; // "Bash" | "Edit" | "Write" | ...
66
+ tool_input: Record<string, unknown>;
67
+ }
45
68
 
46
- ```typescript
47
- // UserPromptSubmit — inject system message
48
- { "continue": true, "systemMessage": "WORKFLOW: ..." }
69
+ interface PostToolUseInput extends PreToolUseInput {
70
+ tool_response: unknown;
71
+ tool_error?: string;
72
+ }
49
73
 
50
- // Stop approve or block
51
- { "continue": false, "decision": "approve", "reason": "All checks passed" }
52
- { "continue": true, "decision": "block", "reason": "Uncommitted files" }
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
- ## Template: Stop Validator
87
+ ## 4. Stdout contract (per event)
56
88
 
57
- ```typescript
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
- function cmd(c: string): string {
62
- try { return execSync(c, { encoding: 'utf8' }).trim(); } catch { return ''; }
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
- const branch = cmd('git rev-parse --abbrev-ref HEAD');
66
- const dirty = cmd('git status --porcelain');
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
- const result = (!dirty && (branch === 'main' || branch === 'master'))
69
- ? { continue: false, decision: 'approve', reason: 'Clean main branch' }
70
- : { continue: true, decision: 'block', reason: `Branch: ${branch}, dirty: ${!!dirty}` };
108
+ Stop block prevents finalization until issues are fixed:
109
+ ```json
110
+ { "continue": true, "decision": "block", "reason": "Uncommitted files" }
111
+ ```
71
112
 
72
- console.log(JSON.stringify(result));
113
+ SessionStart — inject context:
114
+ ```json
115
+ { "systemMessage": "ROUTINE: research → plan → implement → test → commit" }
73
116
  ```
74
117
 
75
- ## settings.json Registration
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
- "Stop": [{ "hooks": [{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" stop-validator", "timeout": 30 }] }],
81
- "UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" user-prompt-submit", "timeout": 10 }] }]
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
- ## Rules
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
- 1. **Always use `run-hook.sh` as entry** handles bun/tsx fallback
89
- 2. **Read stdin with timeout** hooks must not hang
90
- 3. **Exit 0 always** non-zero kills the session
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: 1.0.0
4
- description: Structured logging, correlation IDs, OpenTelemetry tracing, error tracking (Sentry), metrics, and PII redaction. Invoke when adding logging, instrumentation, or debugging production issues.
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: 1.0.0
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.