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,199 @@
1
+ # Bootstrap — Runtime, Package Manager, Tooling
2
+
3
+ When starting a new TypeScript project (or scripting against the world), the choice of runtime, package manager, framework, and toolchain compounds. The wrong default at minute zero costs hours every week. The right defaults for 2026:
4
+
5
+ ## Runtime decision tree
6
+
7
+ ```
8
+ Is this a CLI / script / single-binary tool?
9
+ └─ Yes → Bun (single executable, hot reload, native TS)
10
+ Use `bun run script.ts` directly. No build step.
11
+
12
+ Is this a backend service?
13
+ ├─ Edge (Cloudflare Workers / Vercel / Deno Deploy) → match the platform
14
+ ├─ Bun-supported runtime → Bun + Hono
15
+ ├─ Need Node-only deps (sharp, native modules without Bun support) → Node + Hono
16
+ └─ Otherwise → Bun + Hono
17
+
18
+ Is this a frontend?
19
+ └─ Vite (regardless of framework). Bun for the package manager.
20
+
21
+ Is this a library to publish to npm?
22
+ └─ tsdown (or unbuild). Targets Node 20+. Use pnpm for monorepo workspaces.
23
+ ```
24
+
25
+ ## Bun is the default runtime
26
+
27
+ Use Bun for:
28
+ - Scripts and CLIs (`bun run` is faster than `tsx` and `ts-node`)
29
+ - New backends (Hono runs natively, hot reload via `bun --hot`)
30
+ - Test runner (`bun test` is built-in, faster than vitest for small suites)
31
+ - Package manager (`bun install` is faster than `pnpm` and far faster than `npm`)
32
+
33
+ Use Node when:
34
+ - A dependency uses native modules Bun can't load (rare in 2026; check the dep's release notes)
35
+ - Production target is a Node-specific platform (some serverless platforms don't run Bun yet)
36
+ - You're contributing to a Node-only project
37
+
38
+ `bunx` replaces `npx`. `bun create` scaffolds projects.
39
+
40
+ ## Package manager — pnpm > npm
41
+
42
+ If you must use Node, use pnpm. NEVER npm except in legacy projects you don't control.
43
+
44
+ Why pnpm:
45
+ - Content-addressable store: 10x less disk usage on a machine with many projects
46
+ - Strict node_modules layout: phantom dependencies fail at install time, not at runtime
47
+ - Workspaces are first-class
48
+ - Significantly faster than npm
49
+
50
+ Why not yarn:
51
+ - Yarn classic is unmaintained
52
+ - Yarn berry's "PnP" mode breaks with editor tooling more often than it should
53
+ - pnpm has caught up on every yarn berry feature people actually use
54
+
55
+ Why not npm:
56
+ - Slowest of the three
57
+ - No proper workspace story until very recently
58
+ - Phantom dependencies allowed by default
59
+
60
+ ```bash
61
+ # Convert npm/yarn → pnpm
62
+ pnpm import # reads package-lock.json or yarn.lock and produces pnpm-lock.yaml
63
+ rm -rf node_modules package-lock.json yarn.lock
64
+ pnpm install
65
+ ```
66
+
67
+ ## Backend framework — Hono
68
+
69
+ Use Hono for any new HTTP service. It is:
70
+ - Type-safe end-to-end (request/response types flow through middleware)
71
+ - Edge-compatible (runs on Bun, Node, Cloudflare Workers, Deno, AWS Lambda)
72
+ - Faster than Express, Fastify, and most of its peers in synthetic benchmarks
73
+ - Maintained, opinionated, and documented well
74
+
75
+ When Hono → ALWAYS pair with `hono-openapi` + `@scalar/hono-api-reference` + `@hono/swagger-ui`. Full setup with copy-pasteable `app.ts`: [backend-hono.md](backend-hono.md).
76
+
77
+ NEVER:
78
+ - Express for new services. Express is the COBOL of Node — works, but writes itself out of every benchmark.
79
+ - Fastify for new services. Hono ships with better TypeScript ergonomics.
80
+ - NestJS for new services. The Angular-flavoured DI/decorator stack is overkill for ~95% of services.
81
+ - Bare `Bun.serve` or `node:http` unless you have a specific reason. Lose middleware, routing, validation. Reinvent everything.
82
+
83
+ ## Frontend tooling — Vite
84
+
85
+ Vite for any frontend. Replaces webpack, parcel, rollup-as-app-bundler. Works with React, Vue, Svelte, Solid, Preact, vanilla.
86
+
87
+ ```bash
88
+ bun create vite my-app -- --template react-ts
89
+ cd my-app
90
+ bun install
91
+ bun run dev
92
+ ```
93
+
94
+ ## Lint + format — Biome
95
+
96
+ Biome replaces ESLint + Prettier with one tool, written in Rust, ~30x faster.
97
+
98
+ ```bash
99
+ bun add --dev @biomejs/biome
100
+ bun biome init
101
+ ```
102
+
103
+ `biome.json`:
104
+
105
+ ```json
106
+ {
107
+ "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
108
+ "organizeImports": { "enabled": true },
109
+ "linter": { "enabled": true, "rules": { "recommended": true } },
110
+ "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 }
111
+ }
112
+ ```
113
+
114
+ Use ESLint only when:
115
+ - You have an ESLint plugin Biome doesn't replicate (rare in 2026)
116
+ - You're contributing to an existing ESLint project
117
+
118
+ Never run both — pick one.
119
+
120
+ ## Test runner — bun test or vitest
121
+
122
+ | Runner | Use when |
123
+ |---|---|
124
+ | `bun test` | Bun project, simple unit tests, no TypeScript path aliases that need vite-style resolution |
125
+ | `vitest` | Vite-based frontend, complex test infrastructure (DOM testing, snapshot, in-browser tests), or you need vitest-specific features |
126
+
127
+ NEVER Jest for a new project. Jest's CommonJS-first design fights every modern Node/TS project.
128
+
129
+ ## TypeScript
130
+
131
+ `tsconfig.json` for a Bun + Hono backend:
132
+
133
+ ```json
134
+ {
135
+ "compilerOptions": {
136
+ "target": "ESNext",
137
+ "module": "ESNext",
138
+ "moduleResolution": "bundler",
139
+ "lib": ["ESNext"],
140
+ "types": ["bun-types"],
141
+ "strict": true,
142
+ "noUncheckedIndexedAccess": true,
143
+ "noUnusedLocals": true,
144
+ "noUnusedParameters": true,
145
+ "noImplicitReturns": true,
146
+ "noFallthroughCasesInSwitch": true,
147
+ "exactOptionalPropertyTypes": true,
148
+ "esModuleInterop": true,
149
+ "isolatedModules": true,
150
+ "resolveJsonModule": true,
151
+ "skipLibCheck": true,
152
+ "verbatimModuleSyntax": true,
153
+ "noEmit": true
154
+ },
155
+ "include": ["src/**/*", "tests/**/*"]
156
+ }
157
+ ```
158
+
159
+ `verbatimModuleSyntax: true` enforces explicit `import type { ... }` for type-only imports — pairs with the no-excuse rule on type-only imports.
160
+
161
+ `noEmit: true` because `bun run` and `bun build` handle compilation. The `tsc` command becomes a typechecker only.
162
+
163
+ ## Quick-start: Bun + Hono backend
164
+
165
+ ```bash
166
+ mkdir my-api && cd my-api
167
+ bun init -y
168
+ bun add hono hono-openapi @scalar/hono-api-reference @hono/swagger-ui zod
169
+ bun add --dev @biomejs/biome typescript
170
+ bun biome init
171
+ ```
172
+
173
+ `package.json` scripts:
174
+
175
+ ```json
176
+ {
177
+ "scripts": {
178
+ "dev": "bun run --hot src/index.ts",
179
+ "start": "bun run src/index.ts",
180
+ "build": "bun build src/index.ts --target bun --outdir dist",
181
+ "typecheck": "tsc --noEmit",
182
+ "lint": "biome check --write src tests",
183
+ "test": "bun test"
184
+ }
185
+ }
186
+ ```
187
+
188
+ Wire the `app.ts` from [backend-hono.md](backend-hono.md). You have a documented, validated, OpenAPI-spec-emitting service in ~15 minutes.
189
+
190
+ ## When NOT to bootstrap from scratch
191
+
192
+ | Situation | Use |
193
+ |---|---|
194
+ | Internal tool with auth/admin/dashboards | Next.js (full-stack) - lots of free wiring |
195
+ | Documentation site | Astro or VitePress |
196
+ | Real-time features (WebRTC, complex sockets) | Bun + Hono + a real-time library |
197
+ | Data-heavy SPA | Vite + React + TanStack Query + TanStack Router |
198
+
199
+ For greenfield backend services, Bun + Hono. Always.
@@ -0,0 +1,202 @@
1
+ # Data Modeling
2
+
3
+ Which construct to use, how to structure data, and why readonly is the default.
4
+
5
+ ---
6
+
7
+ ## Decision flowchart
8
+
9
+ ```
10
+ Is it a fixed set of named constants?
11
+ YES → as const object + literal union type
12
+ NO ↓
13
+ Is it just branding a primitive (string, number)?
14
+ YES → Branded type
15
+ NO ↓
16
+ Is it an interface / contract?
17
+ YES → interface (structural typing is the default in TS)
18
+ NO ↓
19
+ Does the data cross a trust boundary (user input, API, file)?
20
+ YES → Zod schema + z.infer<typeof schema>
21
+ NO ↓
22
+ Is it a union of possible outcomes?
23
+ YES → Discriminated union (kind/type field)
24
+ NO ↓
25
+ Is it structured data with named fields?
26
+ YES → type alias with readonly properties
27
+ NO → you probably don't need a new type
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Container reference
33
+
34
+ ### type alias — internal data
35
+
36
+ The default for structured data inside your codebase. Zero runtime cost.
37
+
38
+ ```typescript
39
+ type User = {
40
+ readonly id: UserId
41
+ readonly name: string
42
+ readonly email: string
43
+ }
44
+
45
+ type Point = {
46
+ readonly x: number
47
+ readonly y: number
48
+ }
49
+ ```
50
+
51
+ All properties `readonly`. Mutable only when mutation is the documented purpose.
52
+
53
+ ### interface — contracts and extension
54
+
55
+ Use when you need declaration merging or `extends`.
56
+
57
+ ```typescript
58
+ interface Repository<T> {
59
+ get(id: string): Promise<T | null>
60
+ save(entity: T): Promise<void>
61
+ }
62
+
63
+ interface UserRepository extends Repository<User> {
64
+ findByEmail(email: string): Promise<User | null>
65
+ }
66
+ ```
67
+
68
+ ### interface vs type — when to use which
69
+
70
+ | Use | When |
71
+ |---|---|
72
+ | `type` | Union types, intersections, mapped types, utility types, internal data shapes |
73
+ | `interface` | Contracts that will be `implements`ed or `extends`ed, declaration merging needed |
74
+ | **Default** | **`type` — unless you have a specific reason for `interface`** |
75
+
76
+ ### Zod schema — trust boundary guardian
77
+
78
+ Use when data enters your system. Validates at runtime, infers types at compile time.
79
+
80
+ ```typescript
81
+ import { z } from "zod"
82
+
83
+ const CreateUserSchema = z.object({
84
+ name: z.string().min(1),
85
+ email: z.string().email(),
86
+ age: z.number().int().min(0),
87
+ })
88
+ type CreateUser = z.infer<typeof CreateUserSchema>
89
+
90
+ const UserResponseSchema = z.object({
91
+ id: z.string().uuid(),
92
+ name: z.string(),
93
+ email: z.string(),
94
+ })
95
+ type UserResponse = z.infer<typeof UserResponseSchema>
96
+ ```
97
+
98
+ **The one rule**: data crosses a trust boundary → Zod. Everything else → plain type/interface.
99
+ Never use Zod for internal-only data. The runtime validation cost and Zod coupling are unnecessary.
100
+
101
+ ### as const — fixed constants
102
+
103
+ Replaces `enum` entirely. Type-safe, tree-shakeable, no runtime overhead.
104
+
105
+ ```typescript
106
+ const ROLES = ["admin", "user", "guest"] as const
107
+ type Role = (typeof ROLES)[number]
108
+
109
+ const STATUS = {
110
+ ACTIVE: "active",
111
+ INACTIVE: "inactive",
112
+ DELETED: "deleted",
113
+ } as const
114
+ type Status = (typeof STATUS)[keyof typeof STATUS]
115
+ ```
116
+
117
+ ### Discriminated union — multiple outcomes
118
+
119
+ ```typescript
120
+ type GetUserResult =
121
+ | { readonly kind: "found"; readonly user: User }
122
+ | { readonly kind: "not_found"; readonly id: UserId }
123
+ | { readonly kind: "forbidden"; readonly reason: string }
124
+ ```
125
+
126
+ Each variant has a `kind` discriminant. TypeScript narrows on `switch (result.kind)`.
127
+
128
+ ---
129
+
130
+ ## Quick lookup
131
+
132
+ | Situation | Use |
133
+ |---|---|
134
+ | User input, API request/response | Zod schema + `z.infer` |
135
+ | Internal value object | `type` with `readonly` properties |
136
+ | Function with multiple outcomes | Discriminated union |
137
+ | Contract for implementations | `interface` |
138
+ | Fixed constants | `as const` + literal union |
139
+ | Distinct primitive (UserId vs OrderId) | Branded type |
140
+ | Dict shape / key-value map | `Record<K, V>` or index signature |
141
+
142
+ ---
143
+
144
+ ## Readonly by default
145
+
146
+ Every property is `readonly` unless mutation is the documented purpose.
147
+
148
+ ```typescript
149
+ // DEFAULT — readonly
150
+ type Config = {
151
+ readonly apiUrl: string
152
+ readonly timeout: number
153
+ }
154
+
155
+ // Arrays too
156
+ function getUsers(): readonly User[] { ... }
157
+
158
+ // Utility for existing types
159
+ type ReadonlyUser = Readonly<User>
160
+ type DeepReadonlyConfig = Readonly<Config>
161
+ ```
162
+
163
+ For mutable state (rare), document why:
164
+
165
+ ```typescript
166
+ /** Counter state — mutation is the entire purpose. */
167
+ type CounterState = {
168
+ count: number // intentionally mutable
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Parse, don't validate
175
+
176
+ Validate at the boundary. Inside the boundary, types are proof of validity.
177
+
178
+ ```typescript
179
+ // BAD — validate then pass raw data
180
+ function processEmail(email: string): void {
181
+ if (!email.includes("@")) throw new Error("invalid")
182
+ // still a raw string downstream
183
+ }
184
+
185
+ // GOOD — parse into typed value at boundary
186
+ const EmailSchema = z.string().email().brand("Email")
187
+ type Email = z.infer<typeof EmailSchema>
188
+
189
+ function sendWelcome(email: Email): void { ... }
190
+
191
+ // Boundary code
192
+ const parsed = EmailSchema.parse(rawInput) // Email or throws
193
+ sendWelcome(parsed) // no re-validation needed
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Sources
199
+
200
+ - TypeScript Handbook: [Object Types](https://www.typescriptlang.org/docs/handbook/2/objects.html)
201
+ - Zod: [docs](https://zod.dev)
202
+ - Total TypeScript: [Type vs Interface](https://www.totaltypescript.com/type-vs-interface-which-should-you-use)
@@ -0,0 +1,169 @@
1
+ # Error Handling
2
+
3
+ Typed errors, exhaustive matching, Result pattern, and resource safety.
4
+
5
+ ---
6
+
7
+ ## Typed errors — no bare strings
8
+
9
+ Error classes carry structured data. Callers know exactly what can go wrong.
10
+
11
+ ```typescript
12
+ class UserNotFoundError extends Error {
13
+ readonly name = "UserNotFoundError"
14
+ constructor(readonly userId: UserId) {
15
+ super(`user ${userId} not found`)
16
+ }
17
+ }
18
+
19
+ class PermissionDeniedError extends Error {
20
+ readonly name = "PermissionDeniedError"
21
+ constructor(
22
+ readonly userId: UserId,
23
+ readonly requiredRole: string,
24
+ ) {
25
+ super(`user ${userId} needs role ${requiredRole}`)
26
+ }
27
+ }
28
+ ```
29
+
30
+ ```typescript
31
+ // BAD
32
+ throw new Error("user not found")
33
+ throw new Error("permission denied")
34
+
35
+ // GOOD
36
+ throw new UserNotFoundError(userId)
37
+ throw new PermissionDeniedError(userId, "admin")
38
+ ```
39
+
40
+ Always set `readonly name` explicitly — `instanceof` checks survive minification, but `error.name` is more reliable for logging and serialization.
41
+
42
+ ---
43
+
44
+ ## Result pattern — expected failures without exceptions
45
+
46
+ For failures that are **expected** (not found, validation), return a discriminated union instead of throwing.
47
+
48
+ ```typescript
49
+ type Result<T, E = Error> =
50
+ | { readonly ok: true; readonly value: T }
51
+ | { readonly ok: false; readonly error: E }
52
+
53
+ function ok<T>(value: T): Result<T, never> {
54
+ return { ok: true, value }
55
+ }
56
+
57
+ function err<E>(error: E): Result<never, E> {
58
+ return { ok: false, error }
59
+ }
60
+ ```
61
+
62
+ ### Usage
63
+
64
+ ```typescript
65
+ type UserError =
66
+ | { readonly kind: "not_found"; readonly id: UserId }
67
+ | { readonly kind: "forbidden"; readonly reason: string }
68
+
69
+ function getUser(id: UserId): Result<User, UserError> {
70
+ const user = db.find(id)
71
+ if (!user) return err({ kind: "not_found", id })
72
+ if (!user.active) return err({ kind: "forbidden", reason: "deactivated" })
73
+ return ok(user)
74
+ }
75
+
76
+ // Caller must handle both cases
77
+ const result = getUser(userId)
78
+ if (!result.ok) {
79
+ switch (result.error.kind) {
80
+ case "not_found":
81
+ log.warn(`missing: ${result.error.id}`)
82
+ break
83
+ case "forbidden":
84
+ log.error(`denied: ${result.error.reason}`)
85
+ break
86
+ default:
87
+ assertNever(result.error)
88
+ }
89
+ return
90
+ }
91
+ const user = result.value // narrowed to User
92
+ ```
93
+
94
+ ### When to use which
95
+
96
+ **The heuristic**: caller is 1-2 levels away and MUST handle it → Result. Error should propagate up many layers → throw.
97
+
98
+ | Scenario | Pattern | Why |
99
+ |---|---|---|
100
+ | Repository → service (caller handles it) | Result | Caller is right there, must handle both |
101
+ | Validation at boundary (parsing input) | throw (Zod throws) | Propagates up to HTTP handler |
102
+ | Infrastructure failure (network, OOM) | throw | Can't handle locally |
103
+ | Service → service (deep internal) | throw (typed Error subclass) | Result boilerplate across many layers is worse |
104
+ | HTTP handler → response | Catch errors, convert to response | Boundary code catches and translates |
105
+
106
+ **Practical tradeoff**: Result is safest (compiler forces handling) but creates boilerplate when every caller in a chain must check `.ok`. If the error would just propagate through 3+ layers unchanged, use a typed Error subclass instead.
107
+
108
+ ### Library or roll your own?
109
+
110
+ Roll your own with the `Result`, `ok`, `err` above. It's 10 lines. Libraries like `neverthrow` add chaining (`.map`, `.andThen`) — use them only if you actually chain results frequently.
111
+
112
+ ---
113
+
114
+ ## Error cause — chain context
115
+
116
+ Use the `cause` option to chain errors without losing the original stack.
117
+
118
+ ```typescript
119
+ try {
120
+ await db.query(sql)
121
+ } catch (error) {
122
+ throw new DatabaseError("query failed", { cause: error })
123
+ }
124
+ ```
125
+
126
+ The `cause` is available on `error.cause` and shows up in stack traces.
127
+
128
+ ---
129
+
130
+ ## Exhaustive error handling at boundaries
131
+
132
+ HTTP handlers catch and translate:
133
+
134
+ ```typescript
135
+ app.onError((error, c) => {
136
+ if (error instanceof UserNotFoundError) {
137
+ return c.json({ error: error.message }, 404)
138
+ }
139
+ if (error instanceof PermissionDeniedError) {
140
+ return c.json({ error: error.message }, 403)
141
+ }
142
+ console.error("unhandled:", error)
143
+ return c.json({ error: "internal server error" }, 500)
144
+ })
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Async error patterns
150
+
151
+ ```typescript
152
+ // Promise.allSettled — when partial failure is OK
153
+ const results = await Promise.allSettled(urls.map(fetch))
154
+ const successes = results
155
+ .filter((r): r is PromiseFulfilledResult<Response> => r.status === "fulfilled")
156
+ .map((r) => r.value)
157
+
158
+ // AbortSignal — cancellation
159
+ async function fetchWithTimeout(url: string, ms: number): Promise<Response> {
160
+ return fetch(url, { signal: AbortSignal.timeout(ms) })
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Sources
167
+
168
+ - MDN: [Error cause](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause)
169
+ - MDN: [Promise.allSettled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)