litclaude-ai 0.2.2

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.
Files changed (156) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/LICENSE +21 -0
  3. package/README.md +369 -0
  4. package/README_ko-KR.md +374 -0
  5. package/RELEASE_CHECKLIST.md +165 -0
  6. package/bin/litclaude-ai.js +643 -0
  7. package/cover.png +0 -0
  8. package/docs/agents.md +67 -0
  9. package/docs/hooks.md +134 -0
  10. package/docs/lsp.md +40 -0
  11. package/docs/migration.md +209 -0
  12. package/docs/workflow-compatibility-audit.md +119 -0
  13. package/generate_cover.py +123 -0
  14. package/package.json +48 -0
  15. package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
  16. package/plugins/litclaude/.lsp.json +13 -0
  17. package/plugins/litclaude/.mcp.json +9 -0
  18. package/plugins/litclaude/agents/boulder-executor.md +12 -0
  19. package/plugins/litclaude/agents/librarian-researcher.md +15 -0
  20. package/plugins/litclaude/agents/oracle-verifier.md +16 -0
  21. package/plugins/litclaude/agents/prometheus-planner.md +13 -0
  22. package/plugins/litclaude/agents/qa-runner.md +16 -0
  23. package/plugins/litclaude/agents/quality-reviewer.md +17 -0
  24. package/plugins/litclaude/bin/litclaude-hook.js +110 -0
  25. package/plugins/litclaude/bin/litclaude-hud.js +271 -0
  26. package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
  27. package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
  28. package/plugins/litclaude/commands/deep-interview.md +21 -0
  29. package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
  30. package/plugins/litclaude/commands/lit-loop.md +40 -0
  31. package/plugins/litclaude/commands/lit-plan.md +35 -0
  32. package/plugins/litclaude/commands/litgoal.md +30 -0
  33. package/plugins/litclaude/commands/review-work.md +35 -0
  34. package/plugins/litclaude/commands/start-work.md +36 -0
  35. package/plugins/litclaude/hooks/hooks.json +54 -0
  36. package/plugins/litclaude/lib/context-pressure.mjs +25 -0
  37. package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
  38. package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
  39. package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
  40. package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
  41. package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
  42. package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
  43. package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
  44. package/plugins/litclaude/lib/workflow-check.mjs +83 -0
  45. package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
  46. package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
  47. package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
  48. package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
  49. package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
  50. package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
  51. package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
  52. package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
  53. package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
  54. package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
  55. package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
  56. package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
  57. package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
  58. package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
  59. package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
  60. package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
  61. package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
  62. package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
  63. package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
  64. package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
  65. package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
  66. package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
  67. package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
  68. package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
  69. package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
  70. package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
  71. package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
  72. package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
  73. package/plugins/litclaude/skills/programming/SKILL.md +106 -0
  74. package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
  75. package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
  76. package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
  77. package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
  78. package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
  79. package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
  80. package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
  81. package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
  82. package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
  83. package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
  84. package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
  85. package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
  86. package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
  87. package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
  88. package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
  89. package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
  90. package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
  91. package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
  92. package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
  93. package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
  94. package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
  95. package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
  96. package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
  97. package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
  98. package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
  99. package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
  100. package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
  101. package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
  102. package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
  103. package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
  104. package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
  105. package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
  106. package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
  107. package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
  108. package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
  109. package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
  110. package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
  111. package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
  112. package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
  113. package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
  114. package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
  115. package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
  116. package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
  117. package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
  118. package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
  119. package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
  120. package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
  121. package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
  122. package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
  123. package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
  124. package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
  125. package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
  126. package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
  127. package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
  128. package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
  129. package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
  130. package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
  131. package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
  132. package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
  133. package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
  134. package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
  135. package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
  136. package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
  137. package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
  138. package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
  139. package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
  140. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
  141. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
  142. package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
  143. package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
  144. package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
  145. package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
  146. package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
  147. package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
  148. package/plugins/litclaude/skills/rules/SKILL.md +66 -0
  149. package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
  150. package/scripts/audit-plan-checkboxes.mjs +37 -0
  151. package/scripts/doctor.mjs +41 -0
  152. package/scripts/inspect-agent-tools.mjs +27 -0
  153. package/scripts/postinstall.mjs +50 -0
  154. package/scripts/qa-claude-plugin-smoke.sh +60 -0
  155. package/scripts/qa-portable-install.sh +136 -0
  156. package/scripts/validate-plugin.mjs +72 -0
@@ -0,0 +1,360 @@
1
+ # httpx2 — Production Defaults
2
+
3
+ > **Source**: [pydantic/httpx2](https://github.com/pydantic/httpx2) — next-generation HTTP client for Python 3, continuation of HTTPX under Pydantic stewardship.
4
+ >
5
+ > **Rule**: Every network request MUST use `httpx2`. **ALL optimizations below are ON by default** — HTTP/2, brotli+zstd, tuned connection pool, fine-grained timeouts, transport retries, TCP_NODELAY. This is the baseline, not a stretch goal. A bare `httpx2.AsyncClient()` is a bug.
6
+
7
+ ---
8
+
9
+ ## 1. Installation — all extras, always
10
+
11
+ ```toml
12
+ # pyproject.toml
13
+ dependencies = [
14
+ "httpx2[http2,brotli,zstd]",
15
+ ]
16
+ ```
17
+
18
+ | Extra | What it enables | Why it's mandatory |
19
+ |-------|----------------|--------------------|
20
+ | `http2` | HTTP/2 multiplexing via `h2` | Single TCP connection handles concurrent requests; eliminates head-of-line blocking |
21
+ | `brotli` | Brotli content decoding (`br`) | ~20% smaller payloads than gzip for text/JSON |
22
+ | `zstd` | Zstandard content decoding | Faster decompression than brotli at similar ratios; stdlib in Python ≥ 3.14 |
23
+ | `socks` | SOCKS5 proxy support via `socksio` | Install only if you route through SOCKS proxies |
24
+
25
+ All three core extras (`http2,brotli,zstd`) are non-negotiable. Omitting any is leaving performance on the table.
26
+
27
+ ---
28
+
29
+ ## 2. The canonical defaults — ALL ON
30
+
31
+ These are not "optimizations to consider". These are **the correct defaults** that every httpx2 client must use.
32
+
33
+ ```python
34
+ import socket
35
+ import httpx2
36
+
37
+ # ── These are the STANDARD values. Use them verbatim. ──
38
+
39
+ LIMITS = httpx2.Limits(
40
+ max_connections=200, # library default 100 is too conservative
41
+ max_keepalive_connections=40, # library default 20 wastes reconnects
42
+ keepalive_expiry=30.0, # library default 5s kills warm connections too fast
43
+ )
44
+
45
+ TIMEOUT = httpx2.Timeout(
46
+ connect=5.0, # TCP + TLS handshake budget
47
+ read=30.0, # time to receive a response chunk
48
+ write=10.0, # time to send a request chunk
49
+ pool=10.0, # time to acquire a connection from pool
50
+ )
51
+
52
+ SOCKET_OPTIONS: list[tuple[int, int, int]] = [
53
+ (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), # disable Nagle — no 40ms delay
54
+ ]
55
+ ```
56
+
57
+ ### Why each knob is set this way
58
+
59
+ | Setting | Library default | Our default | Why |
60
+ |---------|----------------|-------------|-----|
61
+ | `http2` | `False` | **`True`** | HTTP/2 multiplexing is strictly superior for any modern API |
62
+ | `max_connections` | `100` | `200` | Headroom for fan-out; prevents pool exhaustion under load |
63
+ | `max_keepalive_connections` | `20` | `40` | Keeps warm connections alive; fewer TLS handshakes |
64
+ | `keepalive_expiry` | `5.0s` | `30.0s` | 5s is too aggressive — kills connections between burst requests |
65
+ | `Timeout(5.0)` uniform | `5.0` all | Split | Uniform 5s is too tight for reads, too loose for connects |
66
+ | `read` timeout | `5.0` | `30.0` | Slow APIs and streaming need breathing room |
67
+ | `pool` timeout | `5.0` | `10.0` | Explicit — hitting this means `max_connections` needs raising |
68
+ | `TCP_NODELAY` | off | **on** | Eliminates Nagle's 40ms coalescing delay for small payloads |
69
+ | `retries` | `0` | `3` | Retries on `ConnectError`/`ConnectTimeout` only — safe and resilient |
70
+ | `follow_redirects` | `False` | **`True`** | Most APIs redirect; failing on 3xx is wrong default behavior |
71
+
72
+ ---
73
+
74
+ ## 3. Factory functions — the ONE correct way to create clients
75
+
76
+ Copy this into your project. This is the canonical pattern.
77
+
78
+ ```python
79
+ """httpx2 client factory. Always use create_client() / create_async_client()."""
80
+
81
+ from __future__ import annotations
82
+
83
+ import socket
84
+ import typing
85
+
86
+ import httpx2
87
+
88
+ _LIMITS = httpx2.Limits(
89
+ max_connections=200,
90
+ max_keepalive_connections=40,
91
+ keepalive_expiry=30.0,
92
+ )
93
+
94
+ _TIMEOUT = httpx2.Timeout(
95
+ connect=5.0,
96
+ read=30.0,
97
+ write=10.0,
98
+ pool=10.0,
99
+ )
100
+
101
+ _SOCKET_OPTIONS: list[tuple[int, int, int]] = [
102
+ (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),
103
+ ]
104
+
105
+
106
+ def create_async_client(
107
+ *,
108
+ base_url: str = "",
109
+ http2: bool = True,
110
+ retries: int = 3,
111
+ limits: httpx2.Limits = _LIMITS,
112
+ timeout: httpx2.Timeout = _TIMEOUT,
113
+ headers: dict[str, str] | None = None,
114
+ event_hooks: dict[str, list[typing.Callable[..., typing.Any]]] | None = None,
115
+ **kwargs: typing.Any,
116
+ ) -> httpx2.AsyncClient:
117
+ transport = httpx2.AsyncHTTPTransport(
118
+ http2=http2,
119
+ retries=retries,
120
+ limits=limits,
121
+ socket_options=_SOCKET_OPTIONS,
122
+ )
123
+ return httpx2.AsyncClient(
124
+ transport=transport,
125
+ timeout=timeout,
126
+ base_url=base_url,
127
+ headers=headers or {},
128
+ event_hooks=event_hooks or {},
129
+ follow_redirects=True,
130
+ **kwargs,
131
+ )
132
+
133
+
134
+ def create_client(
135
+ *,
136
+ base_url: str = "",
137
+ http2: bool = True,
138
+ retries: int = 3,
139
+ limits: httpx2.Limits = _LIMITS,
140
+ timeout: httpx2.Timeout = _TIMEOUT,
141
+ headers: dict[str, str] | None = None,
142
+ event_hooks: dict[str, list[typing.Callable[..., typing.Any]]] | None = None,
143
+ **kwargs: typing.Any,
144
+ ) -> httpx2.Client:
145
+ transport = httpx2.HTTPTransport(
146
+ http2=http2,
147
+ retries=retries,
148
+ limits=limits,
149
+ socket_options=_SOCKET_OPTIONS,
150
+ )
151
+ return httpx2.Client(
152
+ transport=transport,
153
+ timeout=timeout,
154
+ base_url=base_url,
155
+ headers=headers or {},
156
+ event_hooks=event_hooks or {},
157
+ follow_redirects=True,
158
+ **kwargs,
159
+ )
160
+ ```
161
+
162
+ Usage:
163
+
164
+ ```python
165
+ # Async — the common case
166
+ async with create_async_client(base_url="https://api.example.com") as client:
167
+ r = await client.get("/users")
168
+
169
+ # Sync
170
+ with create_client() as client:
171
+ r = client.get("https://api.example.com/health")
172
+ ```
173
+
174
+ **If you are NOT using this factory pattern, you are doing it wrong.** A bare `httpx2.AsyncClient()` leaves HTTP/2 off, retries off, TCP_NODELAY off, keepalive too short, and timeouts too uniform.
175
+
176
+ ---
177
+
178
+ ## 4. Special case overrides
179
+
180
+ The factory defaults cover 95% of use cases. Override only when you have a specific reason:
181
+
182
+ | Scenario | Override |
183
+ |----------|----------|
184
+ | LLM streaming endpoints | `timeout=httpx2.Timeout(connect=10.0, read=None, write=10.0, pool=10.0)` — no read timeout on streaming |
185
+ | Single-host API with low concurrency | `limits=httpx2.Limits(max_connections=50, max_keepalive_connections=20, keepalive_expiry=60.0)` |
186
+ | Ephemeral short-lived requests | `keepalive_expiry=5.0` — don't hold connections |
187
+ | Unix domain sockets | `httpx2.AsyncHTTPTransport(uds="/path/to/socket", ...)` |
188
+ | mTLS / client certs | Pass `verify=ssl_ctx` with `ctx.load_cert_chain(certfile=...)` |
189
+ | SOCKS proxy | `httpx2[socks]`, `proxy="socks5://..."` |
190
+
191
+ ---
192
+
193
+ ## 5. Event hooks — always wire observability
194
+
195
+ This is not optional. Every production client should log requests.
196
+
197
+ ```python
198
+ import time
199
+ import logging
200
+
201
+ logger = logging.getLogger(__name__)
202
+
203
+ async def log_request(request: httpx2.Request) -> None:
204
+ request.extensions["request_start"] = time.perf_counter()
205
+
206
+ async def log_response(response: httpx2.Response) -> None:
207
+ start = response.request.extensions.get("request_start", 0)
208
+ elapsed = time.perf_counter() - start
209
+ logger.info(
210
+ "HTTP %s %s → %d (%.3fs, %s)",
211
+ response.request.method,
212
+ response.request.url,
213
+ response.status_code,
214
+ elapsed,
215
+ response.http_version,
216
+ )
217
+
218
+ # Sync versions for Client
219
+ def log_request_sync(request: httpx2.Request) -> None:
220
+ request.extensions["request_start"] = time.perf_counter()
221
+
222
+ def log_response_sync(response: httpx2.Response) -> None:
223
+ start = response.request.extensions.get("request_start", 0)
224
+ elapsed = time.perf_counter() - start
225
+ logger.info(
226
+ "HTTP %s %s → %d (%.3fs, %s)",
227
+ response.request.method,
228
+ response.request.url,
229
+ response.status_code,
230
+ elapsed,
231
+ response.http_version,
232
+ )
233
+ ```
234
+
235
+ For auto `raise_for_status()`:
236
+
237
+ ```python
238
+ async def raise_on_error(response: httpx2.Response) -> None:
239
+ response.raise_for_status()
240
+ ```
241
+
242
+ ---
243
+
244
+ ## 6. Verification script — confirm your setup is fully optimized
245
+
246
+ Run this against your target endpoint to **verify** (not decide) that all optimizations are active:
247
+
248
+ ```python
249
+ """Verify httpx2 is fully optimized against a target endpoint."""
250
+
251
+ from __future__ import annotations
252
+
253
+ import socket
254
+ import time
255
+
256
+ import anyio
257
+ import httpx2
258
+
259
+
260
+ TARGET_URL = "https://api.example.com/health"
261
+ ITERATIONS = 30
262
+
263
+
264
+ async def bench(label: str, client: httpx2.AsyncClient, url: str, n: int) -> float:
265
+ for _ in range(3): # warmup
266
+ await client.get(url)
267
+ start = time.perf_counter()
268
+ for _ in range(n):
269
+ r = await client.get(url)
270
+ assert r.status_code == 200
271
+ elapsed = time.perf_counter() - start
272
+ avg_ms = (elapsed / n) * 1000
273
+ print(f" {label}: {avg_ms:.1f}ms avg ({n} reqs in {elapsed:.2f}s)")
274
+ return avg_ms
275
+
276
+
277
+ async def main() -> None:
278
+ results: dict[str, float] = {}
279
+
280
+ # BAD: bare defaults (this is what we're proving is worse)
281
+ async with httpx2.AsyncClient() as c:
282
+ results["BAD-bare-defaults"] = await bench("BAD-bare-defaults", c, TARGET_URL, ITERATIONS)
283
+
284
+ # GOOD: full production defaults (this is what we always use)
285
+ limits = httpx2.Limits(max_connections=200, max_keepalive_connections=40, keepalive_expiry=30.0)
286
+ timeout = httpx2.Timeout(connect=5.0, read=30.0, write=10.0, pool=10.0)
287
+ transport = httpx2.AsyncHTTPTransport(
288
+ http2=True, retries=3, limits=limits,
289
+ socket_options=[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)],
290
+ )
291
+ async with httpx2.AsyncClient(transport=transport, timeout=timeout, follow_redirects=True) as c:
292
+ results["GOOD-full-production"] = await bench("GOOD-full-production", c, TARGET_URL, ITERATIONS)
293
+
294
+ print("\n--- Proof ---")
295
+ baseline = results["BAD-bare-defaults"]
296
+ for label, avg in results.items():
297
+ delta = ((avg - baseline) / baseline) * 100
298
+ print(f" {label}: {avg:.1f}ms ({delta:+.1f}% vs bare)")
299
+
300
+
301
+ if __name__ == "__main__":
302
+ anyio.run(main)
303
+ ```
304
+
305
+ ---
306
+
307
+ ## 7. Quick reference — all knobs
308
+
309
+ ### `httpx2.AsyncClient` / `httpx2.Client`
310
+
311
+ | Parameter | Type | Library Default | **Our Default** |
312
+ |-----------|------|-----------------|-----------------|
313
+ | `http1` | `bool` | `True` | `True` |
314
+ | `http2` | `bool` | `False` | **`True`** |
315
+ | `verify` | `ssl.SSLContext \| str \| bool` | `True` | `True` |
316
+ | `cert` | `CertTypes \| None` | `None` | `None` |
317
+ | `proxy` | `str \| Proxy \| None` | `None` | `None` |
318
+ | `mounts` | `dict[str, Transport]` | `None` | `None` |
319
+ | `timeout` | `Timeout \| float \| None` | `Timeout(5.0)` | **Split: 5/30/10/10** |
320
+ | `limits` | `Limits` | `Limits(100, 20, 5.0)` | **`Limits(200, 40, 30.0)`** |
321
+ | `follow_redirects` | `bool` | `False` | **`True`** |
322
+ | `max_redirects` | `int` | `20` | `20` |
323
+ | `event_hooks` | `dict` | `{}` | **Wire logging** |
324
+ | `base_url` | `str` | `""` | Set for single-API clients |
325
+ | `trust_env` | `bool` | `True` | `True` |
326
+ | `default_encoding` | `str \| Callable` | `"utf-8"` | `"utf-8"` |
327
+
328
+ ### `httpx2.AsyncHTTPTransport` / `httpx2.HTTPTransport`
329
+
330
+ | Parameter | Type | Library Default | **Our Default** |
331
+ |-----------|------|-----------------|-----------------|
332
+ | `http1` | `bool` | `True` | `True` |
333
+ | `http2` | `bool` | `False` | **`True`** |
334
+ | `retries` | `int` | `0` | **`3`** |
335
+ | `limits` | `Limits` | `Limits(100, 20, 5.0)` | **`Limits(200, 40, 30.0)`** |
336
+ | `uds` | `str \| None` | `None` | `None` |
337
+ | `local_address` | `str \| None` | `None` | `None` |
338
+ | `socket_options` | `Iterable[SOCKET_OPTION]` | `None` | **`[TCP_NODELAY]`** |
339
+ | `proxy` | `str \| Proxy \| None` | `None` | `None` |
340
+
341
+ ### `httpx2.Timeout`
342
+
343
+ | Parameter | Library Default | **Our Default** |
344
+ |-----------|-----------------|-----------------|
345
+ | `connect` | `5.0` | `5.0` |
346
+ | `read` | `5.0` | **`30.0`** |
347
+ | `write` | `5.0` | **`10.0`** |
348
+ | `pool` | `5.0` | **`10.0`** |
349
+
350
+ ### `httpx2.Limits`
351
+
352
+ | Parameter | Library Default | **Our Default** |
353
+ |-----------|-----------------|-----------------|
354
+ | `max_connections` | `100` | **`200`** |
355
+ | `max_keepalive_connections` | `20` | **`40`** |
356
+ | `keepalive_expiry` | `5.0` | **`30.0`** |
357
+
358
+ ### Async backend (httpcore2)
359
+
360
+ httpcore2 uses `anyio` by default (works with both asyncio and trio). No extra config needed if you're already on the anyio stack. For trio, install `httpcore2[trio]`.
@@ -0,0 +1,307 @@
1
+ # Library Defaults — Decision Tree
2
+
3
+ For each domain, the canonical 2026 choice, why, and the canonical usage snippet. The skill enforces these unless the project's `pyproject.toml` explicitly says otherwise.
4
+
5
+ ## CLI — typer
6
+
7
+ `typer` builds a CLI from type-annotated function signatures. argparse needs 5x the code; click ignores type annotations; fire is magic that breaks at scale.
8
+
9
+ ```python
10
+ import typer
11
+ from rich import print as rprint
12
+
13
+ app = typer.Typer()
14
+
15
+ @app.command()
16
+ def greet(name: str, count: int = 1, shout: bool = False) -> None:
17
+ """Print a greeting `count` times."""
18
+ message = f"Hello, {name}!" if not shout else f"HELLO, {name.upper()}!"
19
+ for _ in range(count):
20
+ rprint(message)
21
+
22
+ if __name__ == "__main__":
23
+ app()
24
+ ```
25
+
26
+ For a single-function script, `typer.run(main)` skips the `Typer()` boilerplate. Subcommands use `@app.command()`.
27
+
28
+ ## Terminal output — rich
29
+
30
+ `rich` produces tables, progress bars, syntax highlighting, traceback rendering. Use it for any structured output. Plain `print` is acceptable for non-interactive log lines (and even those are usually better via `rich.console.Console(stderr=True).log(...)`).
31
+
32
+ ```python
33
+ from rich.console import Console
34
+ from rich.table import Table
35
+
36
+ console = Console()
37
+
38
+ table = Table(title="Users")
39
+ table.add_column("ID", style="cyan")
40
+ table.add_column("Name", style="magenta")
41
+ table.add_row("1", "Alice")
42
+ console.print(table)
43
+
44
+ # Rich tracebacks (call once at process start)
45
+ from rich.traceback import install
46
+ install(show_locals=True)
47
+ ```
48
+
49
+ ## HTTP client — [httpx2](https://github.com/pydantic/httpx2)
50
+
51
+ Next-generation HTTP client under Pydantic stewardship. Sync and async in one library, HTTP/2 native, brotli + zstd content decoding, real type stubs. Replaces `requests` (sync only), `aiohttp` (async only), and the original `httpx`.
52
+
53
+ **Install**: `httpx2[http2,brotli,zstd]` — always include all three extras, no exceptions.
54
+
55
+ **A bare `httpx2.AsyncClient()` / `httpx2.Client()` is a bug.** Always use the factory pattern from `references/httpx2-optimization.md` with ALL optimizations enabled by default:
56
+
57
+ ```python
58
+ import socket
59
+ import httpx2
60
+
61
+ # ── Production defaults — ALL ON, always. ──
62
+ _LIMITS = httpx2.Limits(max_connections=200, max_keepalive_connections=40, keepalive_expiry=30.0)
63
+ _TIMEOUT = httpx2.Timeout(connect=5.0, read=30.0, write=10.0, pool=10.0)
64
+ _SOCKET_OPTS: list[tuple[int, int, int]] = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]
65
+
66
+ # Async (the common case)
67
+ transport = httpx2.AsyncHTTPTransport(http2=True, retries=3, limits=_LIMITS, socket_options=_SOCKET_OPTS)
68
+ async with httpx2.AsyncClient(transport=transport, timeout=_TIMEOUT, follow_redirects=True) as client:
69
+ response = await client.get("https://api.example.com/users")
70
+ response.raise_for_status()
71
+ users = response.json()
72
+
73
+ # Sync
74
+ transport = httpx2.HTTPTransport(http2=True, retries=3, limits=_LIMITS, socket_options=_SOCKET_OPTS)
75
+ with httpx2.Client(transport=transport, timeout=_TIMEOUT, follow_redirects=True) as client:
76
+ response = client.get("https://api.example.com/users")
77
+ response.raise_for_status()
78
+ users = response.json()
79
+ ```
80
+
81
+ See `references/httpx2-optimization.md` for the full factory functions (`create_client()` / `create_async_client()`), event hooks, and the rationale behind every setting. **Load that reference whenever you write ANY network code.**
82
+
83
+ ## JSON — stdlib `json` (default) or `orjson` (hot paths)
84
+
85
+ Stdlib `json` is fine for cold paths and configs. **Reach for `orjson` when JSON is in the hot path** — cache layers, queue payloads, streaming responses, structured logs, FastAPI endpoints returning raw `dict` / `list`.
86
+
87
+ ```python
88
+ import orjson
89
+
90
+ # orjson.dumps returns bytes, not str
91
+ raw: bytes = orjson.dumps(
92
+ payload,
93
+ option=orjson.OPT_NAIVE_UTC | orjson.OPT_UTC_Z | orjson.OPT_SERIALIZE_DATACLASS,
94
+ )
95
+ ```
96
+
97
+ **Critical 2026 fact**: with Pydantic v2, `model.model_dump_json()` is backed by pydantic-core (Rust) and is faster than `orjson + default=` bridge for Pydantic-shaped responses. **Use `model_dump_json()` for Pydantic; orjson for everything else.**
98
+
99
+ For FastAPI: `app = FastAPI(default_response_class=ORJSONResponse)`. Pydantic-typed responses bypass it (and that's correct — Pydantic's path is faster). Raw `dict`/`list` returns go through orjson.
100
+
101
+ See `references/orjson-stack.md` for the full decision tree, option flag reference, FastAPI integration, Redis/queue/logging patterns, and the `model_dump_json()` vs orjson benchmark.
102
+
103
+ ## Validation — pydantic v2
104
+
105
+ Pydantic v2's core is in Rust (~10x faster than v1). It is the de-facto boundary validator. Use it for:
106
+
107
+ - HTTP request/response models (FastAPI uses pydantic natively)
108
+ - Config files (env vars via `pydantic-settings`)
109
+ - Anything entering the program from outside
110
+
111
+ ```python
112
+ from pydantic import BaseModel, Field, EmailStr, field_validator
113
+
114
+ class User(BaseModel):
115
+ id: int = Field(ge=1)
116
+ email: EmailStr
117
+ name: str = Field(min_length=1, max_length=100)
118
+ age: int | None = Field(default=None, ge=0, le=150)
119
+
120
+ @field_validator("name")
121
+ @classmethod
122
+ def name_no_digits(cls, v: str) -> str:
123
+ if any(c.isdigit() for c in v):
124
+ raise ValueError("name cannot contain digits")
125
+ return v
126
+
127
+ # Inside the program, use the validated instance with confidence
128
+ user = User.model_validate({"id": 1, "email": "a@b.com", "name": "Alice"})
129
+ print(user.model_dump_json(indent=2))
130
+ ```
131
+
132
+ `@dataclass` is fine for purely internal records (no validation needed). For anything crossing a process boundary, use Pydantic.
133
+
134
+ ## Async — anyio
135
+
136
+ Full reference: [async-anyio.md](async-anyio.md). The summary:
137
+
138
+ ```python
139
+ import anyio
140
+
141
+ async def fetch(url: str) -> str:
142
+ await anyio.sleep(0.1)
143
+ return url
144
+
145
+ async def main() -> None:
146
+ async with anyio.create_task_group() as tg:
147
+ for url in ["a", "b", "c"]:
148
+ tg.start_soon(fetch, url)
149
+
150
+ anyio.run(main)
151
+ ```
152
+
153
+ Never `import asyncio` directly. The third-party libraries you call are free to use asyncio internally.
154
+
155
+ ## Web framework — fastapi
156
+
157
+ Type-hint-driven HTTP framework. Pydantic models become OpenAPI schemas automatically.
158
+
159
+ ```python
160
+ from fastapi import FastAPI
161
+ from pydantic import BaseModel
162
+
163
+ app = FastAPI()
164
+
165
+ class CreateUser(BaseModel):
166
+ name: str
167
+ email: str
168
+
169
+ class User(BaseModel):
170
+ id: int
171
+ name: str
172
+ email: str
173
+
174
+ @app.post("/users", response_model=User)
175
+ async def create_user(payload: CreateUser) -> User:
176
+ return User(id=1, **payload.model_dump())
177
+ ```
178
+
179
+ Full stack with database: [fastapi-stack.md](fastapi-stack.md).
180
+
181
+ ## ORM — sqlalchemy 2.x async
182
+
183
+ SQLAlchemy 2.x finally has a real async API. Use the modern declarative `MappedAsDataclass` style with type annotations.
184
+
185
+ ```python
186
+ from sqlalchemy import String
187
+ from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
188
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, MappedAsDataclass
189
+
190
+ class Base(MappedAsDataclass, DeclarativeBase):
191
+ pass
192
+
193
+ class User(Base):
194
+ __tablename__ = "users"
195
+ id: Mapped[int] = mapped_column(primary_key=True, init=False)
196
+ name: Mapped[str] = mapped_column(String(100))
197
+ email: Mapped[str] = mapped_column(String(255), unique=True)
198
+
199
+ engine = create_async_engine("postgresql+asyncpg://localhost/myapp")
200
+ SessionFactory = async_sessionmaker(engine, expire_on_commit=False)
201
+ ```
202
+
203
+ Full pattern with FastAPI integration: [fastapi-stack.md](fastapi-stack.md).
204
+
205
+ ## Database — postgres + asyncpg
206
+
207
+ For new applications, default to Postgres. SQLite for tests is fine; SQLite for production is not.
208
+
209
+ asyncpg is the fastest Python Postgres driver, native to SQLAlchemy 2.x async, native to FastAPI's lifespan model. URL: `postgresql+asyncpg://user:pass@host:5432/db`.
210
+
211
+ For migrations, use Alembic with `[alembic.context]` configured to use the async engine. Single-step:
212
+
213
+ ```bash
214
+ uv add alembic
215
+ uv run alembic init -t async migrations
216
+ ```
217
+
218
+ ## TUI — textual
219
+
220
+ Textual builds rich, mouse-aware, mobile-style TUIs on the rich rendering engine. See [textual-tui.md](textual-tui.md).
221
+
222
+ ## AI agents — pydantic-ai
223
+
224
+ The agent framework from the Pydantic team. Type-strict, structured outputs are first-class, model-agnostic. See [pydantic-ai.md](pydantic-ai.md).
225
+
226
+ ## DataFrames — polars + numpy
227
+
228
+ Polars is 10-50x faster than pandas, has a real type system, and supports lazy evaluation. Numpy stays in the toolbox for arrays. See [data-processing.md](data-processing.md).
229
+
230
+ ## OLAP / SQL — duckdb
231
+
232
+ DuckDB is the SQL engine for analytical workloads. Query CSV/Parquet/JSON files directly without loading into memory; perform joins and aggregations 3-4x faster than Polars; zero-copy interchange with Polars via Arrow. See [data-processing.md](data-processing.md).
233
+
234
+ ## Tests — pytest
235
+
236
+ Plain `unittest` is fine for stdlib; everything else uses pytest. Conventions:
237
+
238
+ - File names `test_*.py`, function names `test_*`.
239
+ - Fixtures via `@pytest.fixture`. Async fixtures are anyio-aware (`@pytest.fixture` on an async function works under `pytest-anyio` which is bundled with anyio).
240
+ - Parametrise with `@pytest.mark.parametrize`.
241
+ - Mark async tests with `@pytest.mark.anyio` (provided by anyio's pytest plugin).
242
+
243
+ ```python
244
+ import pytest
245
+ import anyio
246
+
247
+ @pytest.fixture
248
+ def sample_user() -> dict[str, str]:
249
+ return {"name": "Alice", "email": "a@b.com"}
250
+
251
+ @pytest.mark.parametrize("count,expected", [(1, "Hello"), (2, "Hello, Hello")])
252
+ def test_greet(count: int, expected: str) -> None:
253
+ result = ", ".join(["Hello"] * count)
254
+ assert result == expected
255
+
256
+ @pytest.mark.anyio
257
+ async def test_async_fetch() -> None:
258
+ await anyio.sleep(0)
259
+ assert True
260
+ ```
261
+
262
+ `pyproject.toml`:
263
+
264
+ ```toml
265
+ [tool.pytest.ini_options]
266
+ minversion = "8.0"
267
+ testpaths = ["tests"]
268
+ addopts = ["-ra", "--strict-config", "--strict-markers"]
269
+ ```
270
+
271
+ ## Settings / config — pydantic-settings
272
+
273
+ Loads env vars and `.env` files into a Pydantic model. Replaces ad-hoc `os.environ.get(...)` everywhere.
274
+
275
+ ```python
276
+ from pydantic import Field
277
+ from pydantic_settings import BaseSettings, SettingsConfigDict
278
+
279
+ class Settings(BaseSettings):
280
+ model_config = SettingsConfigDict(env_file=".env", env_prefix="MYAPP_")
281
+
282
+ database_url: str
283
+ api_key: str = Field(min_length=1)
284
+ debug: bool = False
285
+
286
+ settings = Settings() # loads at import time; raises if any required var is missing
287
+ ```
288
+
289
+ ## Logging — stdlib logging + rich handler
290
+
291
+ Stdlib `logging` is fine; it gets a face-lift from `rich.logging.RichHandler`.
292
+
293
+ ```python
294
+ import logging
295
+ from rich.logging import RichHandler
296
+
297
+ logging.basicConfig(
298
+ level=logging.INFO,
299
+ format="%(message)s",
300
+ datefmt="[%X]",
301
+ handlers=[RichHandler(rich_tracebacks=True, show_path=False)],
302
+ )
303
+ log = logging.getLogger(__name__)
304
+ log.info("ready")
305
+ ```
306
+
307
+ For structured logging in production, swap to `structlog` (separate dep). Don't roll your own.