ts-procedures 6.0.0 → 6.0.1

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 (30) hide show
  1. package/agent_config/claude-code/agents/ts-procedures-architect.md +1 -1
  2. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +1 -1
  3. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +2 -2
  4. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +15 -27
  5. package/agent_config/claude-code/skills/ts-procedures/patterns.md +11 -4
  6. package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +2 -2
  7. package/agent_config/copilot/copilot-instructions.md +3 -2
  8. package/agent_config/cursor/cursorrules +3 -2
  9. package/build/implementations/http/doc-registry.d.ts +14 -19
  10. package/build/implementations/http/doc-registry.js +41 -46
  11. package/build/implementations/http/doc-registry.js.map +1 -1
  12. package/build/implementations/http/doc-registry.test.js +141 -10
  13. package/build/implementations/http/doc-registry.test.js.map +1 -1
  14. package/build/implementations/http/error-taxonomy.d.ts +11 -2
  15. package/build/implementations/http/error-taxonomy.js +24 -2
  16. package/build/implementations/http/error-taxonomy.js.map +1 -1
  17. package/build/implementations/http/route-errors.test.js +5 -6
  18. package/build/implementations/http/route-errors.test.js.map +1 -1
  19. package/build/implementations/types.d.ts +13 -1
  20. package/docs/http-integrations.md +7 -5
  21. package/docs/superpowers/plans/2026-04-24-doc-registry-simplification.md +886 -0
  22. package/package.json +1 -1
  23. package/src/implementations/http/README.md +2 -3
  24. package/src/implementations/http/doc-registry.test.ts +154 -10
  25. package/src/implementations/http/doc-registry.ts +46 -53
  26. package/src/implementations/http/error-taxonomy.ts +26 -2
  27. package/src/implementations/http/express-rpc/README.md +2 -2
  28. package/src/implementations/http/hono-rpc/README.md +2 -2
  29. package/src/implementations/http/route-errors.test.ts +5 -6
  30. package/src/implementations/types.ts +13 -1
@@ -0,0 +1,886 @@
1
+ # DocRegistry Simplification Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Collapse `DocRegistry.fromTaxonomy` into a single smart constructor that accepts `errors: ErrorTaxonomy` directly, add a fluent `.documentError(...docs)` method for rare extension cases, and make `taxonomyToErrorDocs` an internal helper — so developers interact with one concept ("errors") and never see implementation-level words like "taxonomy" in the DocRegistry API.
6
+
7
+ **Architecture:** The constructor's `errors` field becomes the single entry point. It accepts an `ErrorTaxonomy` (the thing developers already have from `defineErrorTaxonomy`), auto-converts to `ErrorDoc[]` internally, auto-merges framework defaults (opt-out via `includeDefaults: false`), and auto-dedupes. The fluent `.documentError(...docs: ErrorDoc[])` method handles rare cases where extra errors (middleware-level, infrastructure, doc-only) need documentation without a taxonomy entry. `fromTaxonomy` is deleted. `taxonomyToErrorDocs` remains in the codebase (still used internally by `DocRegistry`) but is removed from the `./http-errors` public export so it stops leaking.
8
+
9
+ **Tech Stack:** TypeScript, Vitest, existing ts-procedures v6 taxonomy infrastructure.
10
+
11
+ **Breaking changes (v6 was released today in commit `c667cca`, so blast radius is near-zero):**
12
+ 1. `DocRegistry.fromTaxonomy(taxonomy, config)` → removed. Use `new DocRegistry({ errors: taxonomy })`.
13
+ 2. `DocRegistryConfig.errors: ErrorDoc[]` → `DocRegistryConfig.errors: ErrorTaxonomy | ErrorDoc[]` (polymorphic; existing `ErrorDoc[]` call sites still work).
14
+ 3. `DocRegistryConfig` gains `includeDefaults?: boolean` (default `true`). Previously, framework defaults were **not** auto-included when using the plain constructor — this is now a behavioral change: `new DocRegistry({ errors: [...] })` will merge in framework defaults unless `includeDefaults: false` is passed. Any test/consumer that constructed `new DocRegistry({ errors: [myCustom] })` and expected exactly `[myCustom]` needs either `includeDefaults: false` or to account for the defaults.
15
+ 4. `taxonomyToErrorDocs` → removed from the `./http-errors` public export. Still exists internally in `error-taxonomy.ts` but no longer in the published type surface.
16
+
17
+ ---
18
+
19
+ ## File Structure
20
+
21
+ **Files to modify:**
22
+
23
+ | File | Responsibility |
24
+ |---|---|
25
+ | `src/implementations/types.ts` | `DocRegistryConfig` — widen `errors` field, add `includeDefaults` |
26
+ | `src/implementations/http/doc-registry.ts` | Rewrite constructor (detect taxonomy vs ErrorDoc[], auto-merge defaults, dedupe); add `.documentError()`; delete `fromTaxonomy` |
27
+ | `src/implementations/http/error-taxonomy.ts` | Remove `export` from `taxonomyToErrorDocs` (or keep export but drop from the subpath entry — see Task 1) |
28
+ | `src/implementations/http/doc-registry.test.ts` | Add tests for new polymorphic `errors`, auto-dedupe, auto-defaults, `.documentError()`; adjust `defaultErrors()` tests; migrate any existing construction that relied on no-auto-defaults |
29
+ | `src/implementations/http/route-errors.test.ts` | Replace 4 `DocRegistry.fromTaxonomy` call sites with `new DocRegistry({ errors: ... })`. Update `describe` block name. |
30
+ | `docs/http-integrations.md` | Replace `fromTaxonomy` example with unified constructor; update surrounding prose |
31
+ | `src/implementations/http/README.md` | Update example to show taxonomy in constructor instead of `defaultErrors()` spread |
32
+ | `CHANGELOG.md` | Add v6.0.1 (or v7 if breaking deemed major) section describing the collapse |
33
+ | `CLAUDE.md` | Update the DocRegistry paragraph to drop `fromTaxonomy` mention |
34
+ | `agent_config/claude-code/agents/ts-procedures-architect.md` | Replace `fromTaxonomy` reference |
35
+ | `agent_config/claude-code/skills/ts-procedures/SKILL.md` | Remove `fromTaxonomy` from doc table |
36
+ | `agent_config/claude-code/skills/ts-procedures/patterns.md` | Replace `fromTaxonomy` code example |
37
+ | `agent_config/claude-code/skills/ts-procedures/api-reference.md` | Remove `fromTaxonomy` from class signature; remove `taxonomyToErrorDocs` public-function section; update `DocRegistryConfig` type; document `.documentError()` |
38
+ | `agent_config/claude-code/skills/ts-procedures-review/checklist.md` | Replace 2 `fromTaxonomy` references |
39
+ | `agent_config/copilot/copilot-instructions.md` | Replace `fromTaxonomy` example |
40
+ | `agent_config/cursor/cursorrules` | Mirror copilot changes |
41
+
42
+ **Files NOT modified:**
43
+ - `src/implementations/http/error-taxonomy.test.ts` — doesn't reference `fromTaxonomy`
44
+ - HTTP builder source files — taxonomy runtime wiring is unchanged
45
+ - Client code (`src/client/**`, `src/codegen/**`) — the DocEnvelope shape is unchanged; this is purely an input-ergonomics change
46
+ - `agent_config/claude-code/skills/ts-procedures/anti-patterns.md` — verify no references, no changes expected
47
+
48
+ ---
49
+
50
+ ## Task 1: Verify current API surface and write failing tests first
51
+
52
+ **Files:**
53
+ - Modify: `src/implementations/http/doc-registry.test.ts`
54
+
55
+ This is a TDD-driven refactor. Before touching production code, encode the new behavior as failing tests so the refactor has a reliable target.
56
+
57
+ - [ ] **Step 1.1: Confirm `taxonomyToErrorDocs` is currently exported via `./http-errors` subpath**
58
+
59
+ Run: `grep -n "export" src/implementations/http/error-taxonomy.ts | grep -i "taxonomy\|errordoc"`
60
+ Expected: Confirms `export function taxonomyToErrorDocs` exists. The `./http-errors` subpath re-exports everything from this file, so it's part of the public API today.
61
+
62
+ - [ ] **Step 1.2: Read the current `doc-registry.test.ts` top-of-file imports and fixtures so new tests fit the existing shape**
63
+
64
+ Run: `head -60 src/implementations/http/doc-registry.test.ts`
65
+ Expected: See the imports (`DocRegistry`, test fixtures like `rpcDoc`, `apiDoc`). Note the describe-block structure.
66
+
67
+ - [ ] **Step 1.3: Append a new describe block to `doc-registry.test.ts` covering the new behaviors**
68
+
69
+ Add this describe block at the end of the outer `describe('DocRegistry', ...)` block (after the existing `defaultErrors()` block, before `kind discriminant`):
70
+
71
+ ```typescript
72
+ // --------------------------------------------------------------------------
73
+ // taxonomy input + auto-defaults + dedupe (v6.0.1 simplification)
74
+ // --------------------------------------------------------------------------
75
+ describe('errors: ErrorTaxonomy (polymorphic constructor input)', () => {
76
+ test('accepts an ErrorTaxonomy directly and converts to ErrorDoc[]', () => {
77
+ const taxonomy = defineErrorTaxonomy({
78
+ AuthError: {
79
+ class: class AuthError extends Error {},
80
+ statusCode: 401,
81
+ description: 'unauthenticated',
82
+ },
83
+ })
84
+ const envelope = new DocRegistry({ errors: taxonomy }).toJSON()
85
+ const auth = envelope.errors.find((e) => e.name === 'AuthError')
86
+ expect(auth).toBeDefined()
87
+ expect(auth?.statusCode).toBe(401)
88
+ expect(auth?.description).toBe('unauthenticated')
89
+ })
90
+
91
+ test('auto-includes framework defaults when errors is a taxonomy', () => {
92
+ const taxonomy = defineErrorTaxonomy({
93
+ AuthError: { class: class AuthError extends Error {}, statusCode: 401 },
94
+ })
95
+ const names = new DocRegistry({ errors: taxonomy }).toJSON().errors.map((e) => e.name)
96
+ expect(names).toContain('AuthError')
97
+ expect(names).toContain('ProcedureValidationError')
98
+ expect(names).toContain('ProcedureYieldValidationError')
99
+ expect(names).toContain('ProcedureError')
100
+ expect(names).toContain('ProcedureRegistrationError')
101
+ })
102
+
103
+ test('auto-includes framework defaults when errors is ErrorDoc[]', () => {
104
+ const custom: ErrorDoc = { name: 'CustomThing', statusCode: 418, description: 'teapot' }
105
+ const names = new DocRegistry({ errors: [custom] }).toJSON().errors.map((e) => e.name)
106
+ expect(names).toContain('CustomThing')
107
+ expect(names).toContain('ProcedureValidationError')
108
+ })
109
+
110
+ test('includeDefaults: false omits framework defaults', () => {
111
+ const taxonomy = defineErrorTaxonomy({
112
+ AuthError: { class: class AuthError extends Error {}, statusCode: 401 },
113
+ })
114
+ const names = new DocRegistry({ errors: taxonomy, includeDefaults: false })
115
+ .toJSON()
116
+ .errors.map((e) => e.name)
117
+ expect(names).toEqual(['AuthError'])
118
+ })
119
+
120
+ test('user taxonomy entry with same name as default overrides default (dedupe, user wins)', () => {
121
+ const taxonomy = defineErrorTaxonomy({
122
+ ProcedureError: {
123
+ class: Error,
124
+ statusCode: 418,
125
+ description: 'custom override',
126
+ },
127
+ })
128
+ const envelope = new DocRegistry({ errors: taxonomy }).toJSON()
129
+ const proc = envelope.errors.filter((e) => e.name === 'ProcedureError')
130
+ expect(proc).toHaveLength(1)
131
+ expect(proc[0].statusCode).toBe(418)
132
+ expect(proc[0].description).toBe('custom override')
133
+ })
134
+
135
+ test('user ErrorDoc with same name as default overrides default (dedupe, user wins)', () => {
136
+ const custom: ErrorDoc = {
137
+ name: 'ProcedureError',
138
+ statusCode: 418,
139
+ description: 'custom override',
140
+ }
141
+ const envelope = new DocRegistry({ errors: [custom] }).toJSON()
142
+ const proc = envelope.errors.filter((e) => e.name === 'ProcedureError')
143
+ expect(proc).toHaveLength(1)
144
+ expect(proc[0].statusCode).toBe(418)
145
+ })
146
+
147
+ test('empty errors config still returns framework defaults', () => {
148
+ const names = new DocRegistry().toJSON().errors.map((e) => e.name)
149
+ expect(names).toContain('ProcedureError')
150
+ expect(names).toContain('ProcedureValidationError')
151
+ expect(names).toContain('ProcedureYieldValidationError')
152
+ expect(names).toContain('ProcedureRegistrationError')
153
+ })
154
+
155
+ test('includeDefaults: false with no errors produces empty error list', () => {
156
+ const envelope = new DocRegistry({ includeDefaults: false }).toJSON()
157
+ expect(envelope.errors).toEqual([])
158
+ })
159
+ })
160
+
161
+ // --------------------------------------------------------------------------
162
+ // .documentError() fluent extension
163
+ // --------------------------------------------------------------------------
164
+ describe('.documentError()', () => {
165
+ test('adds a single ErrorDoc to the envelope', () => {
166
+ const registry = new DocRegistry({ includeDefaults: false }).documentError({
167
+ name: 'RateLimitExceeded',
168
+ statusCode: 429,
169
+ description: 'too many requests',
170
+ })
171
+ const names = registry.toJSON().errors.map((e) => e.name)
172
+ expect(names).toEqual(['RateLimitExceeded'])
173
+ })
174
+
175
+ test('accepts multiple docs via variadic args', () => {
176
+ const registry = new DocRegistry({ includeDefaults: false }).documentError(
177
+ { name: 'A', statusCode: 400, description: 'a' },
178
+ { name: 'B', statusCode: 500, description: 'b' }
179
+ )
180
+ const names = registry.toJSON().errors.map((e) => e.name)
181
+ expect(names).toEqual(['A', 'B'])
182
+ })
183
+
184
+ test('returns this for chaining', () => {
185
+ const registry = new DocRegistry()
186
+ const returned = registry.documentError({
187
+ name: 'Foo',
188
+ statusCode: 500,
189
+ description: 'x',
190
+ })
191
+ expect(returned).toBe(registry)
192
+ })
193
+
194
+ test('composes with taxonomy input — extends docs without replacing', () => {
195
+ const taxonomy = defineErrorTaxonomy({
196
+ AuthError: { class: class AuthError extends Error {}, statusCode: 401 },
197
+ })
198
+ const envelope = new DocRegistry({ errors: taxonomy })
199
+ .documentError({ name: 'RateLimitExceeded', statusCode: 429, description: 'x' })
200
+ .toJSON()
201
+ const names = envelope.errors.map((e) => e.name)
202
+ expect(names).toContain('AuthError')
203
+ expect(names).toContain('RateLimitExceeded')
204
+ expect(names).toContain('ProcedureValidationError')
205
+ })
206
+
207
+ test('dedupes against existing errors (last write wins)', () => {
208
+ const registry = new DocRegistry({ includeDefaults: false })
209
+ .documentError({ name: 'Foo', statusCode: 400, description: 'first' })
210
+ .documentError({ name: 'Foo', statusCode: 500, description: 'second' })
211
+ const foo = registry.toJSON().errors.filter((e) => e.name === 'Foo')
212
+ expect(foo).toHaveLength(1)
213
+ expect(foo[0].statusCode).toBe(500)
214
+ expect(foo[0].description).toBe('second')
215
+ })
216
+ })
217
+ ```
218
+
219
+ Make sure the imports at the top of `doc-registry.test.ts` include `defineErrorTaxonomy` from `./error-taxonomy.js` and the `ErrorDoc` type. If they're not already imported, add them.
220
+
221
+ - [ ] **Step 1.4: Run the new tests to confirm they fail**
222
+
223
+ Run: `npx vitest run src/implementations/http/doc-registry.test.ts`
224
+ Expected: All new tests in the two new describe blocks FAIL with type errors (`errors: taxonomy` not assignable) or behavioral mismatches (defaults not auto-included, `documentError` method not found). Existing tests still pass.
225
+
226
+ - [ ] **Step 1.5: Commit the failing tests**
227
+
228
+ ```bash
229
+ git add src/implementations/http/doc-registry.test.ts
230
+ git commit -m "test(doc-registry): add failing tests for unified constructor + documentError()"
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Task 2: Widen `DocRegistryConfig` and rewrite the `DocRegistry` class
236
+
237
+ **Files:**
238
+ - Modify: `src/implementations/types.ts:186-190`
239
+ - Modify: `src/implementations/http/doc-registry.ts` (full rewrite of class body)
240
+
241
+ - [ ] **Step 2.1: Widen `DocRegistryConfig.errors` and add `includeDefaults`**
242
+
243
+ In `src/implementations/types.ts`, replace the `DocRegistryConfig` interface (lines 186-190) with:
244
+
245
+ ```typescript
246
+ export interface DocRegistryConfig {
247
+ basePath?: string
248
+ headers?: HeaderDoc[]
249
+ /**
250
+ * Errors to document in the envelope. Accepts either your runtime
251
+ * {@link ErrorTaxonomy} (from `defineErrorTaxonomy`) — the common case — or
252
+ * a raw `ErrorDoc[]` for consumers who aren't using a taxonomy.
253
+ *
254
+ * Framework defaults (`ProcedureError`, `ProcedureValidationError`,
255
+ * `ProcedureYieldValidationError`, `ProcedureRegistrationError`) are merged
256
+ * in automatically and deduped. Opt out via `includeDefaults: false`.
257
+ */
258
+ errors?: ErrorTaxonomy | ErrorDoc[]
259
+ /** Whether to auto-merge framework error defaults. Defaults to `true`. */
260
+ includeDefaults?: boolean
261
+ }
262
+ ```
263
+
264
+ Add the `ErrorTaxonomy` import to the top of `types.ts`. Check where it currently lives — if there's a circular-import risk (the taxonomy file imports types from `types.ts`), use a `type`-only import:
265
+
266
+ ```typescript
267
+ import type { ErrorTaxonomy } from './http/error-taxonomy.js'
268
+ ```
269
+
270
+ - [ ] **Step 2.2: Verify no circular import**
271
+
272
+ Run: `npm run build`
273
+ Expected: Build succeeds. If it fails with a circular-import or ordering error, inline the `ErrorTaxonomy` type shape into `types.ts` instead (copy the minimal shape: `Record<string, { statusCode: number; description?: string; schema?: Record<string, unknown>; class?: unknown; match?: unknown; toResponse?: unknown; onCatch?: unknown }>`) — this is load-bearing structural compatibility, not identity. Prefer the import path; only fall back if proven circular.
274
+
275
+ - [ ] **Step 2.3: Rewrite `DocRegistry` class**
276
+
277
+ Overwrite `src/implementations/http/doc-registry.ts` with the following. This rewrite:
278
+ - Detects whether `config.errors` is a taxonomy or `ErrorDoc[]` via shape check
279
+ - Applies defaults when `includeDefaults !== false`
280
+ - Dedupes by `name` (user wins over defaults; last-write-wins within user entries)
281
+ - Adds `documentError(...docs: ErrorDoc[]): this`
282
+ - Deletes `fromTaxonomy`
283
+ - Keeps `defaultErrors()` public (still used by `api-reference.md` callers who build custom envelopes)
284
+
285
+ ```typescript
286
+ import type {
287
+ AnyHttpRouteDoc,
288
+ DocEnvelope,
289
+ DocRegistryConfig,
290
+ DocRegistryOutputOptions,
291
+ DocSource,
292
+ ErrorDoc,
293
+ HeaderDoc,
294
+ } from '../types.js'
295
+ import {
296
+ ErrorTaxonomy,
297
+ defaultErrorTaxonomy,
298
+ taxonomyToErrorDocs,
299
+ } from './error-taxonomy.js'
300
+
301
+ export type {
302
+ AnyHttpRouteDoc,
303
+ DocEnvelope,
304
+ DocRegistryConfig,
305
+ DocRegistryOutputOptions,
306
+ DocSource,
307
+ ErrorDoc,
308
+ HeaderDoc,
309
+ } from '../types.js'
310
+
311
+ /**
312
+ * `ProcedureRegistrationError` is thrown at procedure-definition time (never at
313
+ * request time), so it doesn't appear in the runtime taxonomy. It is documented
314
+ * here so consumers still see it in the error catalog.
315
+ */
316
+ const PROCEDURE_REGISTRATION_ERROR_DOC: ErrorDoc = {
317
+ name: 'ProcedureRegistrationError',
318
+ statusCode: 500,
319
+ description:
320
+ 'An invalid schema or configuration was detected at procedure registration time.',
321
+ schema: {
322
+ type: 'object',
323
+ properties: {
324
+ name: { type: 'string', const: 'ProcedureRegistrationError' },
325
+ procedureName: { type: 'string' },
326
+ message: { type: 'string' },
327
+ },
328
+ required: ['name', 'procedureName', 'message'],
329
+ },
330
+ }
331
+
332
+ /**
333
+ * Duck-type check: a taxonomy is `Record<string, ErrorTaxonomyEntry>` where
334
+ * each entry has a numeric `statusCode`. An `ErrorDoc[]` is an array.
335
+ * Arrays and taxonomies are trivially distinguishable via `Array.isArray`.
336
+ */
337
+ function isTaxonomy(input: ErrorTaxonomy | ErrorDoc[]): input is ErrorTaxonomy {
338
+ return !Array.isArray(input)
339
+ }
340
+
341
+ /**
342
+ * Dedupes ErrorDocs by `name`, last occurrence wins. Input order is preserved
343
+ * for survivors — meaning when two entries share a name, the later one's
344
+ * position in the output is where the LAST occurrence appeared.
345
+ */
346
+ function dedupeByName(docs: ErrorDoc[]): ErrorDoc[] {
347
+ const byName = new Map<string, ErrorDoc>()
348
+ for (const doc of docs) byName.set(doc.name, doc)
349
+ return Array.from(byName.values())
350
+ }
351
+
352
+ export class DocRegistry {
353
+ private readonly basePath: string
354
+ private readonly headers: HeaderDoc[]
355
+ private errors: ErrorDoc[]
356
+ private readonly sources: DocSource<AnyHttpRouteDoc>[] = []
357
+
358
+ constructor(config?: DocRegistryConfig) {
359
+ this.basePath = config?.basePath ?? ''
360
+ this.headers = config?.headers ?? []
361
+
362
+ const includeDefaults = config?.includeDefaults ?? true
363
+ const userErrors: ErrorDoc[] = config?.errors
364
+ ? isTaxonomy(config.errors)
365
+ ? taxonomyToErrorDocs(config.errors)
366
+ : config.errors
367
+ : []
368
+
369
+ // Precedence: defaults come first, then user errors override via dedupe
370
+ // (last-write-wins). Matches runtime resolution order — user taxonomy
371
+ // entries win over framework defaults when keys overlap.
372
+ const merged = includeDefaults
373
+ ? [...DocRegistry.defaultErrors(), ...userErrors]
374
+ : userErrors
375
+
376
+ this.errors = dedupeByName(merged)
377
+ }
378
+
379
+ from(source: DocSource<AnyHttpRouteDoc>): this {
380
+ this.sources.push(source)
381
+ return this
382
+ }
383
+
384
+ /**
385
+ * Adds one or more {@link ErrorDoc} entries to the envelope. Use for
386
+ * documenting errors that aren't in your runtime taxonomy — middleware-level
387
+ * errors, infrastructure errors (502/503/504), or doc-only meta errors.
388
+ *
389
+ * Deduped against existing errors by `name` — last write wins, so calling
390
+ * this with a name that already exists overrides the earlier entry.
391
+ */
392
+ documentError(...docs: ErrorDoc[]): this {
393
+ this.errors = dedupeByName([...this.errors, ...docs])
394
+ return this
395
+ }
396
+
397
+ toJSON<T = DocEnvelope>(options?: DocRegistryOutputOptions<T>): T {
398
+ let routes = this.sources.flatMap((source) => source.docs)
399
+
400
+ if (options?.filter) {
401
+ routes = routes.filter(options.filter)
402
+ }
403
+
404
+ const envelope: DocEnvelope = {
405
+ basePath: this.basePath,
406
+ headers: [...this.headers],
407
+ errors: [...this.errors],
408
+ routes,
409
+ }
410
+
411
+ if (options?.transform) {
412
+ return options.transform(envelope)
413
+ }
414
+
415
+ return envelope as T
416
+ }
417
+
418
+ /**
419
+ * Framework error defaults for the DocEnvelope — derived from
420
+ * {@link defaultErrorTaxonomy} so the documented shape cannot drift from what
421
+ * the HTTP builders actually emit at runtime. `ProcedureRegistrationError` is
422
+ * appended because it's thrown at registration time (never at request time)
423
+ * and therefore lives only in the catalog, not the runtime taxonomy.
424
+ *
425
+ * Most consumers do not need to call this directly — the `DocRegistry`
426
+ * constructor auto-includes these unless `includeDefaults: false` is passed.
427
+ */
428
+ static defaultErrors(): ErrorDoc[] {
429
+ return [
430
+ ...taxonomyToErrorDocs(defaultErrorTaxonomy),
431
+ PROCEDURE_REGISTRATION_ERROR_DOC,
432
+ ]
433
+ }
434
+ }
435
+ ```
436
+
437
+ Notable details:
438
+ - `errors` field is no longer `readonly` because `documentError` mutates it.
439
+ - `isTaxonomy` uses `!Array.isArray(input)` — simple and correct since `ErrorTaxonomy` is `Record<string, ...>`.
440
+ - Dedup precedence is **defaults-first, user-last** so user entries override defaults, AND within user entries, the last write wins. This matches the old `fromTaxonomy` behavior (user entries override defaults) and aligns with runtime resolution.
441
+
442
+ - [ ] **Step 2.4: Run the new failing tests — they should now pass**
443
+
444
+ Run: `npx vitest run src/implementations/http/doc-registry.test.ts`
445
+ Expected: All tests pass, including the new ones from Task 1.
446
+
447
+ - [ ] **Step 2.5: Run the full test suite to check for regressions**
448
+
449
+ Run: `npm run test`
450
+ Expected: Most pass, but expect failures in:
451
+ - `src/implementations/http/doc-registry.test.ts` tests that expected `new DocRegistry()` to produce exactly `[]` errors (they will now produce the 4 defaults).
452
+ - `src/implementations/http/route-errors.test.ts` — still uses `DocRegistry.fromTaxonomy`, which no longer exists. TypeScript should catch this at compile-time via `tsc`-in-vitest, or the runtime will throw "not a function". These are fixed in Task 3.
453
+
454
+ Note the specific failing tests and their assertion messages — Task 3 fixes them.
455
+
456
+ - [ ] **Step 2.6: Fix doc-registry.test.ts regressions**
457
+
458
+ Find any existing test in `doc-registry.test.ts` that asserts `errors: []` or similar expected-empty behavior when constructing `new DocRegistry()` without `errors:`. These tests previously passed because the old constructor did NOT auto-merge defaults. Two options:
459
+
460
+ (a) If the test is specifically validating "no defaults by default," update the assertion to reflect the new behavior (defaults included), OR
461
+ (b) If the test just wanted a clean slate for some other assertion (e.g., checking route behavior), pass `{ includeDefaults: false }` to restore the old behavior.
462
+
463
+ Read each failing test, pick (a) or (b) based on intent, and fix accordingly.
464
+
465
+ Scan specifically for: `const registry = new DocRegistry()` (no args) at lines 64, 95, 101, 106, 111, 117, 123, 129, 137, 160, 171, 192, 202, 214, 234, 242 — most are for route-plumbing tests and can take `{ includeDefaults: false }`. Lines around 178 use `{ headers, errors }` — verify the assertions still hold with defaults merged, or add `includeDefaults: false`.
466
+
467
+ - [ ] **Step 2.7: Re-run doc-registry.test.ts**
468
+
469
+ Run: `npx vitest run src/implementations/http/doc-registry.test.ts`
470
+ Expected: All tests pass.
471
+
472
+ - [ ] **Step 2.8: Commit**
473
+
474
+ ```bash
475
+ git add src/implementations/types.ts src/implementations/http/doc-registry.ts src/implementations/http/doc-registry.test.ts
476
+ git commit -m "refactor(doc-registry): unify constructor to accept ErrorTaxonomy; add .documentError()"
477
+ ```
478
+
479
+ ---
480
+
481
+ ## Task 3: Migrate `route-errors.test.ts` off `fromTaxonomy`
482
+
483
+ **Files:**
484
+ - Modify: `src/implementations/http/route-errors.test.ts:120-177`
485
+
486
+ - [ ] **Step 3.1: Rewrite the `fromTaxonomy` describe block**
487
+
488
+ Replace lines 120-177 of `src/implementations/http/route-errors.test.ts` with:
489
+
490
+ ```typescript
491
+ describe('DocRegistry with taxonomy input', () => {
492
+ test('seeds envelope errors from the taxonomy + framework defaults', () => {
493
+ const registry = new DocRegistry({ errors: appErrors, basePath: '/api' })
494
+ const envelope = registry.toJSON()
495
+ const names = envelope.errors.map((e) => e.name)
496
+ expect(names).toContain('UseCaseError')
497
+ expect(names).toContain('AuthError')
498
+ expect(names).toContain('ProcedureValidationError')
499
+ expect(names).toContain('ProcedureRegistrationError')
500
+ expect(envelope.basePath).toBe('/api')
501
+ })
502
+
503
+ test('includeDefaults: false omits framework entries', () => {
504
+ const registry = new DocRegistry({ errors: appErrors, includeDefaults: false })
505
+ const names = registry.toJSON().errors.map((e) => e.name)
506
+ expect(names).toEqual(['UseCaseError', 'AuthError'])
507
+ })
508
+
509
+ test('user entry with same key as default takes precedence (deduped)', () => {
510
+ const overridden = defineErrorTaxonomy({
511
+ ProcedureError: {
512
+ class: Error,
513
+ statusCode: 418,
514
+ description: 'custom override',
515
+ },
516
+ })
517
+ const envelope = new DocRegistry({ errors: overridden }).toJSON()
518
+ const proc = envelope.errors.find((e) => e.name === 'ProcedureError')
519
+ expect(proc?.statusCode).toBe(418)
520
+ expect(proc?.description).toBe('custom override')
521
+ const count = envelope.errors.filter((e) => e.name === 'ProcedureError').length
522
+ expect(count).toBe(1)
523
+ })
524
+
525
+ test('registered route errors survive DocRegistry composition', () => {
526
+ const API = Procedures<{}, APIConfig<keyof typeof appErrors & string>>()
527
+ API.Create(
528
+ 'GetUser',
529
+ {
530
+ path: '/users/:id',
531
+ method: 'get',
532
+ errors: ['UseCaseError'],
533
+ schema: {
534
+ input: { pathParams: Type.Object({ id: Type.String() }) },
535
+ returnType: Type.Object({}),
536
+ },
537
+ },
538
+ async () => ({})
539
+ )
540
+ const app = new HonoAPIAppBuilder().register(API, () => ({}))
541
+ app.build()
542
+
543
+ const envelope = new DocRegistry({ errors: appErrors }).from(app).toJSON()
544
+ const route = envelope.routes.find((r) => r.kind === 'api' && r.name === 'GetUser')
545
+ expect(route?.errors).toEqual(['UseCaseError'])
546
+ })
547
+ })
548
+ ```
549
+
550
+ - [ ] **Step 3.2: Run the test file to confirm**
551
+
552
+ Run: `npx vitest run src/implementations/http/route-errors.test.ts`
553
+ Expected: All tests pass.
554
+
555
+ - [ ] **Step 3.3: Run full test suite**
556
+
557
+ Run: `npm run test`
558
+ Expected: All tests pass.
559
+
560
+ - [ ] **Step 3.4: Commit**
561
+
562
+ ```bash
563
+ git add src/implementations/http/route-errors.test.ts
564
+ git commit -m "test(route-errors): migrate fromTaxonomy call sites to unified constructor"
565
+ ```
566
+
567
+ ---
568
+
569
+ ## Task 4: Demote `taxonomyToErrorDocs` from the public `./http-errors` surface
570
+
571
+ **Files:**
572
+ - Modify: `src/implementations/http/error-taxonomy.ts` (JSDoc + @internal tag)
573
+
574
+ The function still needs to be importable from `doc-registry.ts` (same module directory), but we want it out of the `./http-errors` public docs so consumers don't see it as an API entry point.
575
+
576
+ The cleanest way without breaking the cross-module import is to keep the `export` (needed because `doc-registry.ts` imports it) but mark it `@internal` via JSDoc. TypeScript's `--stripInternal` or explicit documentation tooling will honor this. Our `tsconfig` may not strip it, but the signal to consumers (and to our agent_config docs) is clear.
577
+
578
+ - [ ] **Step 4.1: Add `@internal` JSDoc tag to `taxonomyToErrorDocs`**
579
+
580
+ In `src/implementations/http/error-taxonomy.ts`, update the JSDoc block at line ~185 from:
581
+
582
+ ```typescript
583
+ /**
584
+ * Converts a taxonomy into {@link ErrorDoc} objects suitable for a DocEnvelope.
585
+ * Single source of truth so the runtime mapping and the documented shape
586
+ * cannot drift apart.
587
+ */
588
+ export function taxonomyToErrorDocs(taxonomy: ErrorTaxonomy): ErrorDoc[] {
589
+ ```
590
+
591
+ to:
592
+
593
+ ```typescript
594
+ /**
595
+ * Converts a taxonomy into {@link ErrorDoc} objects suitable for a DocEnvelope.
596
+ *
597
+ * @internal Used by `DocRegistry` to merge taxonomy entries into the envelope.
598
+ * Consumers should pass their taxonomy directly to `new DocRegistry({ errors: taxonomy })`
599
+ * rather than calling this helper — the constructor handles the conversion.
600
+ */
601
+ export function taxonomyToErrorDocs(taxonomy: ErrorTaxonomy): ErrorDoc[] {
602
+ ```
603
+
604
+ - [ ] **Step 4.2: Verify build still passes**
605
+
606
+ Run: `npm run build`
607
+ Expected: Success.
608
+
609
+ - [ ] **Step 4.3: Commit**
610
+
611
+ ```bash
612
+ git add src/implementations/http/error-taxonomy.ts
613
+ git commit -m "docs(error-taxonomy): mark taxonomyToErrorDocs @internal — consumers should use DocRegistry directly"
614
+ ```
615
+
616
+ ---
617
+
618
+ ## Task 5: Update project documentation
619
+
620
+ **Files:**
621
+ - Modify: `docs/http-integrations.md:278-302`
622
+ - Modify: `src/implementations/http/README.md:256-262`
623
+ - Modify: `CHANGELOG.md:17,22,26-27`
624
+ - Modify: `CLAUDE.md:100`
625
+
626
+ - [ ] **Step 5.1: Rewrite the DocRegistry section in `docs/http-integrations.md`**
627
+
628
+ Read the current section:
629
+
630
+ ```bash
631
+ sed -n '274,303p' docs/http-integrations.md
632
+ ```
633
+
634
+ Replace lines 274-303 with:
635
+
636
+ ```markdown
637
+ ## DocRegistry — Composing Docs from Multiple Builders
638
+
639
+ Use `DocRegistry` to compose route documentation from any combination of HTTP builders into a typed envelope:
640
+
641
+ ```typescript
642
+ import { DocRegistry } from 'ts-procedures/http-docs'
643
+
644
+ const docs = new DocRegistry({
645
+ basePath: '/api',
646
+ headers: [{ name: 'Authorization', description: 'Bearer token', required: false }],
647
+ errors: appErrors, // your ErrorTaxonomy — auto-converted to ErrorDocs, framework defaults merged
648
+ })
649
+ .from(rpcBuilder)
650
+ .from(apiBuilder)
651
+ .from(streamBuilder)
652
+
653
+ app.get('/docs', (c) => c.json(docs.toJSON()))
654
+ ```
655
+
656
+ `errors` accepts either your runtime `ErrorTaxonomy` (the common case) or a raw `ErrorDoc[]`. Framework defaults (`ProcedureError`, `ProcedureValidationError`, `ProcedureYieldValidationError`, `ProcedureRegistrationError`) are merged in automatically and deduped — user entries win when keys overlap. Pass `includeDefaults: false` to opt out.
657
+
658
+ For errors that aren't in your taxonomy (middleware-level, infrastructure, doc-only meta errors), use the fluent `.documentError()` method:
659
+
660
+ ```typescript
661
+ const docs = new DocRegistry({ errors: appErrors, basePath: '/api' })
662
+ .from(rpcBuilder)
663
+ .documentError({ name: 'RateLimitExceeded', statusCode: 429, description: 'too many requests' })
664
+ ```
665
+
666
+ `from()` stores a reference — routes are read lazily at `toJSON()` time, so builders can be registered before or after `.build()`. Supports optional `filter` and `transform` options for customizing output.
667
+
668
+ The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md).
669
+ ```
670
+
671
+ - [ ] **Step 5.2: Update `src/implementations/http/README.md`**
672
+
673
+ Replace lines 256-262 (the `new DocRegistry({ errors: DocRegistry.defaultErrors() })` example) to match the new constructor idiom:
674
+
675
+ ```bash
676
+ sed -n '250,270p' src/implementations/http/README.md
677
+ ```
678
+
679
+ Replace the example with:
680
+
681
+ ```typescript
682
+ const docs = new DocRegistry({
683
+ basePath: '/api',
684
+ errors: appErrors, // framework defaults auto-merged and deduped
685
+ })
686
+ ```
687
+
688
+ (Keep surrounding prose coherent.)
689
+
690
+ - [ ] **Step 5.3: Update `CHANGELOG.md`**
691
+
692
+ At the top of the file, add a new unreleased/patch section (use v6.0.1 or whatever the next version is):
693
+
694
+ ```markdown
695
+ ## v6.0.1 — DocRegistry constructor simplification
696
+
697
+ ### Changed
698
+
699
+ - `DocRegistry` constructor now accepts `errors: ErrorTaxonomy | ErrorDoc[]` (polymorphic). Pass your taxonomy directly instead of converting it beforehand.
700
+ - Framework error defaults are now auto-merged into every `DocRegistry` envelope (user entries override defaults via dedupe). Opt out via `includeDefaults: false`.
701
+ - New fluent method `DocRegistry.prototype.documentError(...docs: ErrorDoc[])` for documenting errors not in the taxonomy.
702
+
703
+ ### Removed
704
+
705
+ - `DocRegistry.fromTaxonomy(taxonomy, config?)` — superseded by `new DocRegistry({ errors: taxonomy, ...config })`. The new form is strictly more capable (dedupe applies to plain-constructor call sites too).
706
+ - `taxonomyToErrorDocs` — still exists as an internal helper but marked `@internal` and no longer a recommended entry point. Pass your taxonomy to `DocRegistry` directly.
707
+
708
+ ### Migration
709
+
710
+ ```typescript
711
+ // before (v6.0.0)
712
+ DocRegistry.fromTaxonomy(appErrors, { basePath: '/api' })
713
+
714
+ // after (v6.0.1)
715
+ new DocRegistry({ errors: appErrors, basePath: '/api' })
716
+ ```
717
+
718
+ If you were constructing `new DocRegistry({ errors: [myDoc] })` in v6.0.0 and relied on framework defaults NOT being auto-included, add `includeDefaults: false` to preserve v6.0.0 behavior.
719
+ ```
720
+
721
+ Also scan the existing CHANGELOG for stale references to `fromTaxonomy` in the v6.0.0 entry (line 22, 26-27). Leave the v6.0.0 entry historically accurate — don't rewrite history. The v6.0.1 section above notes the deprecation.
722
+
723
+ - [ ] **Step 5.4: Update `CLAUDE.md:100`**
724
+
725
+ The relevant paragraph currently says:
726
+ > `DocRegistry.defaultErrors()` derives from `defaultErrorTaxonomy` via `taxonomyToErrorDocs` — single source of truth for the documented and runtime-emitted shapes. `DocRegistry.fromTaxonomy(taxonomy, config?)` seeds envelope errors directly from a taxonomy (user entries + framework defaults, deduped).
727
+
728
+ Replace with:
729
+ > `DocRegistry.defaultErrors()` derives from `defaultErrorTaxonomy` — single source of truth for the documented and runtime-emitted shapes. The `DocRegistry` constructor accepts `errors: ErrorTaxonomy | ErrorDoc[]` directly (common case: pass your taxonomy) and auto-merges framework defaults unless `includeDefaults: false`. For errors outside your taxonomy (middleware, infrastructure, doc-only), chain `.documentError(...docs)`.
730
+
731
+ - [ ] **Step 5.5: Verify docs-consistency script still passes**
732
+
733
+ Run: `npm run check-docs`
734
+ Expected: All invariants pass. If any invariant trips (e.g., an MD file now lacks a taxonomy reference that the script requires), fix the file.
735
+
736
+ - [ ] **Step 5.6: Commit**
737
+
738
+ ```bash
739
+ git add docs/http-integrations.md src/implementations/http/README.md CHANGELOG.md CLAUDE.md
740
+ git commit -m "docs: update DocRegistry examples + changelog for v6.0.1 simplification"
741
+ ```
742
+
743
+ ---
744
+
745
+ ## Task 6: Update agent_config skill content
746
+
747
+ **Files:**
748
+ - Modify: `agent_config/claude-code/agents/ts-procedures-architect.md:40`
749
+ - Modify: `agent_config/claude-code/skills/ts-procedures/SKILL.md:174`
750
+ - Modify: `agent_config/claude-code/skills/ts-procedures/patterns.md:189`
751
+ - Modify: `agent_config/claude-code/skills/ts-procedures/api-reference.md:280,298-304,788-821,878`
752
+ - Modify: `agent_config/claude-code/skills/ts-procedures-review/checklist.md:77,118`
753
+ - Modify: `agent_config/copilot/copilot-instructions.md:257`
754
+ - Modify: `agent_config/cursor/cursorrules` (if it contains the same text)
755
+
756
+ - [ ] **Step 6.1: Update `ts-procedures-architect.md`**
757
+
758
+ Line 40 currently says:
759
+ > Use `DocRegistry` to compose route docs from multiple builders — never manually wire `/docs` endpoints. Use `DocRegistry.fromTaxonomy(appErrors)` to seed envelope errors from your taxonomy + framework defaults in one call.
760
+
761
+ Replace with:
762
+ > Use `DocRegistry` to compose route docs from multiple builders — never manually wire `/docs` endpoints. Pass your taxonomy directly: `new DocRegistry({ errors: appErrors })` — framework defaults are auto-merged and deduped. For errors outside your taxonomy, chain `.documentError(...docs)`.
763
+
764
+ - [ ] **Step 6.2: Update `skills/ts-procedures/SKILL.md:174`**
765
+
766
+ The doc table mentions `DocRegistry + fromTaxonomy` — replace with `DocRegistry constructor + documentError()`.
767
+
768
+ Run: `grep -n "DocRegistry\|fromTaxonomy" agent_config/claude-code/skills/ts-procedures/SKILL.md`
769
+ Expected: Find the table row. Replace `DocRegistry + fromTaxonomy` with `DocRegistry (unified constructor, .documentError())`.
770
+
771
+ - [ ] **Step 6.3: Update `patterns.md:189`**
772
+
773
+ Read current example:
774
+
775
+ ```bash
776
+ sed -n '185,200p' agent_config/claude-code/skills/ts-procedures/patterns.md
777
+ ```
778
+
779
+ Replace the `DocRegistry.fromTaxonomy(appErrors, { basePath: '/api' })` call with `new DocRegistry({ errors: appErrors, basePath: '/api' })`. Add a follow-up example showing `.documentError()` for non-taxonomy errors.
780
+
781
+ - [ ] **Step 6.4: Update `api-reference.md`**
782
+
783
+ This file has several touch points:
784
+ - Line 280: description of `description?` on taxonomy entry mentions "consumed by DocRegistry" — keep unchanged.
785
+ - Lines 298-304: the `### taxonomyToErrorDocs(taxonomy)` section. Delete the entire section — it's no longer part of the public API. Add a note in its place:
786
+ > Taxonomy-to-doc conversion is handled automatically by `DocRegistry` when you pass your taxonomy to the constructor. There is no public helper.
787
+ - Lines 788-821: the `class DocRegistry { ... }` signature. Update it:
788
+ - Remove the `static fromTaxonomy(...)` method.
789
+ - Add the `documentError(...docs: ErrorDoc[]): this` method.
790
+ - Update the constructor signature to show `errors: ErrorTaxonomy | ErrorDoc[]` and the new `includeDefaults` field.
791
+ - Line 878: the bullet about `defaultErrors()` — keep but add a sentence noting consumers rarely need to call it directly since the constructor auto-merges.
792
+
793
+ - [ ] **Step 6.5: Update `ts-procedures-review/checklist.md`**
794
+
795
+ Two lines to update (77, 118). Replace both `DocRegistry.fromTaxonomy(appErrors)` references with `new DocRegistry({ errors: appErrors })`.
796
+
797
+ - [ ] **Step 6.6: Update `copilot-instructions.md:257` and `cursorrules`**
798
+
799
+ Read:
800
+
801
+ ```bash
802
+ sed -n '253,260p' agent_config/copilot/copilot-instructions.md
803
+ grep -n "fromTaxonomy\|DocRegistry" agent_config/cursor/cursorrules
804
+ ```
805
+
806
+ Replace `DocRegistry.fromTaxonomy(appErrors, { basePath: '/api' }).from(apiApp).toJSON()` with `new DocRegistry({ errors: appErrors, basePath: '/api' }).from(apiApp).toJSON()` in both files.
807
+
808
+ - [ ] **Step 6.7: Run docs consistency check**
809
+
810
+ Run: `npm run check-docs`
811
+ Expected: All invariants still pass (we haven't removed taxonomy references; we've just simplified the API they describe).
812
+
813
+ - [ ] **Step 6.8: Commit**
814
+
815
+ ```bash
816
+ git add agent_config/
817
+ git commit -m "docs(agent_config): update all skill content for DocRegistry v6.0.1 simplification"
818
+ ```
819
+
820
+ ---
821
+
822
+ ## Task 7: Final verification
823
+
824
+ **Files:** (none modified)
825
+
826
+ - [ ] **Step 7.1: Run the full test suite**
827
+
828
+ Run: `npm run test`
829
+ Expected: All tests pass, zero failures.
830
+
831
+ - [ ] **Step 7.2: Run the linter**
832
+
833
+ Run: `npm run lint`
834
+ Expected: Zero errors.
835
+
836
+ - [ ] **Step 7.3: Run the build**
837
+
838
+ Run: `npm run build`
839
+ Expected: TypeScript compilation succeeds, `build/` directory populated, no type errors.
840
+
841
+ - [ ] **Step 7.4: Run the docs-consistency script**
842
+
843
+ Run: `npm run check-docs`
844
+ Expected: All invariants pass.
845
+
846
+ - [ ] **Step 7.5: Grep the repo for any remaining `fromTaxonomy` references**
847
+
848
+ Run: `grep -rn "fromTaxonomy" --include="*.ts" --include="*.md" --include="*.json" .`
849
+ Expected: Zero hits outside of `CHANGELOG.md` (where the v6.0.0 historical entry and the v6.0.1 migration guide legitimately mention it).
850
+
851
+ - [ ] **Step 7.6: Grep for remaining `taxonomyToErrorDocs` references outside intended locations**
852
+
853
+ Run: `grep -rn "taxonomyToErrorDocs" --include="*.ts" --include="*.md" .`
854
+ Expected: Only in `src/implementations/http/error-taxonomy.ts` (declaration + internal JSDoc), `src/implementations/http/doc-registry.ts` (internal import), and `CHANGELOG.md` (historical mentions). Zero hits in `agent_config/` or `docs/`.
855
+
856
+ - [ ] **Step 7.7: Verify the CHANGELOG for v6.0.0 still accurately describes what shipped in v6.0.0**
857
+
858
+ Don't rewrite history — v6.0.0 did ship `fromTaxonomy`, and the v6.0.0 entry should say so. The v6.0.1 entry handles the removal.
859
+
860
+ - [ ] **Step 7.8: Bump `package.json` version**
861
+
862
+ Edit `package.json` and bump `version` from `6.0.0` to `6.0.1`. (Or 7.0.0 if the user considers the removal of a public API major — confirm before bumping.)
863
+
864
+ - [ ] **Step 7.9: Final commit**
865
+
866
+ ```bash
867
+ git add package.json
868
+ git commit -m "chore: bump version to 6.0.1"
869
+ ```
870
+
871
+ - [ ] **Step 7.10: Run `npm run prepublishOnly` (if configured) as a dry-run**
872
+
873
+ Run: `npm pack --dry-run`
874
+ Expected: Package contents look correct; no stale `fromTaxonomy` mentions in included files beyond the changelog.
875
+
876
+ ---
877
+
878
+ ## Summary
879
+
880
+ After this plan:
881
+ - **Single way to construct** `DocRegistry` — the plain constructor.
882
+ - **One polymorphic `errors` field** accepts the user's runtime taxonomy directly, with auto-convert/auto-defaults/auto-dedupe all for free.
883
+ - **Fluent `.documentError()` method** covers the rare extension case cleanly.
884
+ - **`fromTaxonomy` is gone**; `taxonomyToErrorDocs` is internal.
885
+ - **"Taxonomy" vocabulary** lives only where it originates (`defineErrorTaxonomy`) — it vanishes from downstream DocRegistry-facing APIs and docs.
886
+ - **Backward-compat bridge:** existing `new DocRegistry({ errors: [ErrorDoc, ...] })` call sites still work and now benefit from auto-dedupe + auto-defaults (one behavioral change noted in the changelog).