@cyanheads/mcp-ts-core 0.13.4 → 0.13.6

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 (108) hide show
  1. package/AGENTS.md +5 -5
  2. package/CLAUDE.md +5 -5
  3. package/README.md +1 -1
  4. package/biome.json +1 -1
  5. package/changelog/0.13.x/0.13.5.md +41 -0
  6. package/changelog/0.13.x/0.13.6.md +49 -0
  7. package/dist/linter/rules/enrichment-rules.d.ts +5 -4
  8. package/dist/linter/rules/enrichment-rules.d.ts.map +1 -1
  9. package/dist/linter/rules/enrichment-rules.js +99 -22
  10. package/dist/linter/rules/enrichment-rules.js.map +1 -1
  11. package/dist/linter/rules/error-contract-rules.d.ts +101 -3
  12. package/dist/linter/rules/error-contract-rules.d.ts.map +1 -1
  13. package/dist/linter/rules/error-contract-rules.js +291 -3
  14. package/dist/linter/rules/error-contract-rules.js.map +1 -1
  15. package/dist/linter/rules/format-parity-rules.js +1 -1
  16. package/dist/linter/rules/format-parity-rules.js.map +1 -1
  17. package/dist/linter/rules/index.d.ts +1 -1
  18. package/dist/linter/rules/index.d.ts.map +1 -1
  19. package/dist/linter/rules/index.js +1 -1
  20. package/dist/linter/rules/index.js.map +1 -1
  21. package/dist/linter/rules/resource-rules.d.ts.map +1 -1
  22. package/dist/linter/rules/resource-rules.js +5 -2
  23. package/dist/linter/rules/resource-rules.js.map +1 -1
  24. package/dist/linter/rules/tool-rules.d.ts.map +1 -1
  25. package/dist/linter/rules/tool-rules.js +5 -2
  26. package/dist/linter/rules/tool-rules.js.map +1 -1
  27. package/dist/mcp-server/handlerContext.d.ts +5 -2
  28. package/dist/mcp-server/handlerContext.d.ts.map +1 -1
  29. package/dist/mcp-server/handlerContext.js +6 -4
  30. package/dist/mcp-server/handlerContext.js.map +1 -1
  31. package/dist/mcp-server/inputRequired.d.ts +35 -2
  32. package/dist/mcp-server/inputRequired.d.ts.map +1 -1
  33. package/dist/mcp-server/inputRequired.js +116 -2
  34. package/dist/mcp-server/inputRequired.js.map +1 -1
  35. package/dist/mcp-server/resources/resource-registration.d.ts +2 -1
  36. package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
  37. package/dist/mcp-server/resources/resource-registration.js +4 -4
  38. package/dist/mcp-server/resources/resource-registration.js.map +1 -1
  39. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +2 -1
  40. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
  41. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +5 -2
  42. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
  43. package/dist/mcp-server/server.d.ts.map +1 -1
  44. package/dist/mcp-server/server.js +11 -2
  45. package/dist/mcp-server/server.js.map +1 -1
  46. package/dist/mcp-server/tools/tool-registration.d.ts +2 -1
  47. package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
  48. package/dist/mcp-server/tools/tool-registration.js +4 -4
  49. package/dist/mcp-server/tools/tool-registration.js.map +1 -1
  50. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +14 -4
  51. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  52. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +83 -9
  53. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  54. package/dist/services/canvas/core/CanvasInstance.d.ts +4 -1
  55. package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -1
  56. package/dist/services/canvas/core/CanvasInstance.js +5 -0
  57. package/dist/services/canvas/core/CanvasInstance.js.map +1 -1
  58. package/dist/services/canvas/core/CanvasRegistry.d.ts +52 -1
  59. package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
  60. package/dist/services/canvas/core/CanvasRegistry.js +79 -3
  61. package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
  62. package/dist/services/canvas/index.d.ts +1 -1
  63. package/dist/services/canvas/index.d.ts.map +1 -1
  64. package/dist/services/canvas/index.js +1 -1
  65. package/dist/services/canvas/index.js.map +1 -1
  66. package/dist/types-global/errors.d.ts +48 -0
  67. package/dist/types-global/errors.d.ts.map +1 -1
  68. package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
  69. package/dist/utils/internal/error-handler/errorHandler.js +13 -3
  70. package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
  71. package/dist/utils/internal/error-handler/mappings.d.ts +14 -1
  72. package/dist/utils/internal/error-handler/mappings.d.ts.map +1 -1
  73. package/dist/utils/internal/error-handler/mappings.js +19 -1
  74. package/dist/utils/internal/error-handler/mappings.js.map +1 -1
  75. package/dist/utils/internal/error-handler/types.d.ts +12 -1
  76. package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
  77. package/dist/utils/internal/performance.d.ts.map +1 -1
  78. package/dist/utils/internal/performance.js +4 -1
  79. package/dist/utils/internal/performance.js.map +1 -1
  80. package/dist/utils/network/fetchWithTimeout.d.ts +24 -4
  81. package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
  82. package/dist/utils/network/fetchWithTimeout.js +10 -6
  83. package/dist/utils/network/fetchWithTimeout.js.map +1 -1
  84. package/dist/utils/network/httpError.d.ts +43 -4
  85. package/dist/utils/network/httpError.d.ts.map +1 -1
  86. package/dist/utils/network/httpError.js +53 -6
  87. package/dist/utils/network/httpError.js.map +1 -1
  88. package/dist/utils/telemetry/attributes.d.ts +8 -0
  89. package/dist/utils/telemetry/attributes.d.ts.map +1 -1
  90. package/dist/utils/telemetry/attributes.js +8 -0
  91. package/dist/utils/telemetry/attributes.js.map +1 -1
  92. package/framework-skills/add-service/SKILL.md +5 -2
  93. package/framework-skills/add-tool/SKILL.md +10 -5
  94. package/framework-skills/api-canvas/SKILL.md +31 -14
  95. package/framework-skills/api-context/SKILL.md +4 -2
  96. package/framework-skills/api-errors/SKILL.md +51 -7
  97. package/framework-skills/api-linter/SKILL.md +128 -13
  98. package/framework-skills/api-telemetry/SKILL.md +18 -3
  99. package/framework-skills/api-utils/SKILL.md +4 -4
  100. package/framework-skills/design-mcp-server/SKILL.md +4 -2
  101. package/framework-skills/field-test/SKILL.md +2 -2
  102. package/package.json +3 -3
  103. package/scripts/lint-mcp.ts +43 -4
  104. package/templates/AGENTS.md +1 -1
  105. package/templates/CLAUDE.md +1 -1
  106. package/templates/devcheck.config.json +1 -0
  107. package/templates/package.json +1 -1
  108. package/templates/src/mcp-server/tools/definitions/echo.tool.ts +7 -1
package/AGENTS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Package:** `@cyanheads/mcp-ts-core`
4
- **Version:** 0.13.4
4
+ **Version:** 0.13.6
5
5
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
6
6
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
7
7
  **Zod:** ^4.6.5
@@ -247,7 +247,7 @@ export const myTool = tool('my_tool', {
247
247
  **`format`**: Maps output to MCP `content[]`. Different clients forward different surfaces to the agent — some (Claude Code) read `structuredContent` from `output`, others (Claude Desktop) read `content[]` from `format()`. `format()` is the markdown twin of `structuredContent`, not a reduced summary.
248
248
 
249
249
  - **Parity is lint-enforced.** Every terminal field in `output` must appear in `format()`'s rendered text (via sentinel injection), or the `format-parity` rule fails `bun run lint:mcp` / `devcheck`.
250
- - **Primary fix:** render the missing field in `format()`. Use `z.discriminatedUnion` for list/detail variants — each branch is validated separately.
250
+ - **Primary fix:** render the missing field in `format()`. For list/detail variants, declare one flat `z.object` with a `kind` discriminator and presence-based optional arms, rendered by independent `if` blocks — `tool()` rejects a `z.discriminatedUnion` output root.
251
251
  - **Escape hatch:** if the schema was over-typed for a genuinely dynamic upstream API, relax it (`z.object({}).passthrough()`) — passthrough still flows data to `structuredContent`.
252
252
  - **Fallback:** omit `format` for JSON stringify. Additional formatters in `/utils`: `markdown()` (builder), `diffFormatter` (async), `tableFormatter`, `treeFormatter`.
253
253
 
@@ -378,7 +378,7 @@ See `api-context` skill for full details.
378
378
 
379
379
  ## Error Handling
380
380
 
381
- **Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the single source of truth for the wire hint. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (framework mirrors `data.recovery.hint` into `content[]` text); override with explicit `{ recovery: { hint: '...' } }` when runtime context matters.
381
+ **Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the single source of truth for the wire hint. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (framework mirrors `data.recovery.hint` into `content[]` text); override with explicit `{ recovery: { hint: '...' } }` when runtime context matters. Optional `severity` (`debug`/`info`/`notice`/`warning`) logs a modeled outcome below `error` — tools only, logging only: the wire envelope, the span status, and the `mcp.tool.*` metrics are untouched. Optional `thrownBy: 'service'` marks an entry the service layer produces, so `error-contract-unthrown` skips it while still checking the handler's own reasons — lint-only metadata, nothing at runtime reads it.
382
382
 
383
383
  ```ts
384
384
  errors: [
@@ -418,9 +418,9 @@ For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { se
418
418
 
419
419
  **Auto-classification.** Plain `Error`, `ZodError`, and any other thrown value are caught and classified automatically. Resolution order: request signal already aborted (→ `RequestCancelled`, outranking the thrown value's own code, `McpError` included) → `McpError` code (preserved as-is) → SDK `ConnectionClosed` (→ `RequestCancelled`) → JS constructor name (`TypeError` → `ValidationError`) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name (→ `Timeout`) → `InternalError` fallback.
420
420
 
421
- **Error-path parity.** Tool errors: `content[]` carries markdown with `data.recovery.hint`; `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran.
421
+ **Error-path parity.** Tool errors: `content[]` carries `Error: <message>`, then `Recovery: <hint>` when the hint says something the message does not already contain, then a closing `(reason … · not retryable)` for whichever of `data.reason` / `data.retryable` is present; the numeric code and `data.issues` stay JSON-only. `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran. `client_capability_missing` is the second framework-owned reason: a `ctx.requestInput` return a 2025-era connection cannot serve is refused before any wire traffic as `-32600` carrying that reason and a hint naming the capability.
422
422
 
423
- **Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`. See `api-linter` skill.
423
+ **Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`, `error-contract-unthrown` (a declared reason no literal `ctx.fail`/`ctx.recoveryFor` in the handler names, unless marked `thrownBy: 'service'`), `error-contract-recovery-unforwarded` (a `ctx.fail` site carrying neither `ctx.recoveryFor('<reason>')` nor its own `recovery` key, so the declared hint reaches neither client surface). See `api-linter` skill.
424
424
 
425
425
  See `api-errors` skill for the full pattern-matching table, error code reference, and detailed examples.
426
426
 
package/CLAUDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Package:** `@cyanheads/mcp-ts-core`
4
- **Version:** 0.13.4
4
+ **Version:** 0.13.6
5
5
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
6
6
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
7
7
  **Zod:** ^4.6.5
@@ -247,7 +247,7 @@ export const myTool = tool('my_tool', {
247
247
  **`format`**: Maps output to MCP `content[]`. Different clients forward different surfaces to the agent — some (Claude Code) read `structuredContent` from `output`, others (Claude Desktop) read `content[]` from `format()`. `format()` is the markdown twin of `structuredContent`, not a reduced summary.
248
248
 
249
249
  - **Parity is lint-enforced.** Every terminal field in `output` must appear in `format()`'s rendered text (via sentinel injection), or the `format-parity` rule fails `bun run lint:mcp` / `devcheck`.
250
- - **Primary fix:** render the missing field in `format()`. Use `z.discriminatedUnion` for list/detail variants — each branch is validated separately.
250
+ - **Primary fix:** render the missing field in `format()`. For list/detail variants, declare one flat `z.object` with a `kind` discriminator and presence-based optional arms, rendered by independent `if` blocks — `tool()` rejects a `z.discriminatedUnion` output root.
251
251
  - **Escape hatch:** if the schema was over-typed for a genuinely dynamic upstream API, relax it (`z.object({}).passthrough()`) — passthrough still flows data to `structuredContent`.
252
252
  - **Fallback:** omit `format` for JSON stringify. Additional formatters in `/utils`: `markdown()` (builder), `diffFormatter` (async), `tableFormatter`, `treeFormatter`.
253
253
 
@@ -378,7 +378,7 @@ See `api-context` skill for full details.
378
378
 
379
379
  ## Error Handling
380
380
 
381
- **Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the single source of truth for the wire hint. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (framework mirrors `data.recovery.hint` into `content[]` text); override with explicit `{ recovery: { hint: '...' } }` when runtime context matters.
381
+ **Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the single source of truth for the wire hint. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (framework mirrors `data.recovery.hint` into `content[]` text); override with explicit `{ recovery: { hint: '...' } }` when runtime context matters. Optional `severity` (`debug`/`info`/`notice`/`warning`) logs a modeled outcome below `error` — tools only, logging only: the wire envelope, the span status, and the `mcp.tool.*` metrics are untouched. Optional `thrownBy: 'service'` marks an entry the service layer produces, so `error-contract-unthrown` skips it while still checking the handler's own reasons — lint-only metadata, nothing at runtime reads it.
382
382
 
383
383
  ```ts
384
384
  errors: [
@@ -418,9 +418,9 @@ For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { se
418
418
 
419
419
  **Auto-classification.** Plain `Error`, `ZodError`, and any other thrown value are caught and classified automatically. Resolution order: request signal already aborted (→ `RequestCancelled`, outranking the thrown value's own code, `McpError` included) → `McpError` code (preserved as-is) → SDK `ConnectionClosed` (→ `RequestCancelled`) → JS constructor name (`TypeError` → `ValidationError`) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name (→ `Timeout`) → `InternalError` fallback.
420
420
 
421
- **Error-path parity.** Tool errors: `content[]` carries markdown with `data.recovery.hint`; `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran.
421
+ **Error-path parity.** Tool errors: `content[]` carries `Error: <message>`, then `Recovery: <hint>` when the hint says something the message does not already contain, then a closing `(reason … · not retryable)` for whichever of `data.reason` / `data.retryable` is present; the numeric code and `data.issues` stay JSON-only. `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran. `client_capability_missing` is the second framework-owned reason: a `ctx.requestInput` return a 2025-era connection cannot serve is refused before any wire traffic as `-32600` carrying that reason and a hint naming the capability.
422
422
 
423
- **Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`. See `api-linter` skill.
423
+ **Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`, `error-contract-unthrown` (a declared reason no literal `ctx.fail`/`ctx.recoveryFor` in the handler names, unless marked `thrownBy: 'service'`), `error-contract-recovery-unforwarded` (a `ctx.fail` site carrying neither `ctx.recoveryFor('<reason>')` nor its own `recovery` key, so the declared hint reaches neither client surface). See `api-linter` skill.
424
424
 
425
425
  See `api-errors` skill for the full pattern-matching table, error code reference, and detailed examples.
426
426
 
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <div align="center">
8
8
 
9
- [![Version](https://img.shields.io/badge/Version-0.13.4-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2026--07--28-8A2BE2.svg?style=flat-square)](https://modelcontextprotocol.io/specification/2026-07-28)
9
+ [![Version](https://img.shields.io/badge/Version-0.13.6-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2026--07--28-8A2BE2.svg?style=flat-square)](https://modelcontextprotocol.io/specification/2026-07-28)
10
10
 
11
11
  [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0%2B-blueviolet.svg?style=flat-square)](https://bun.sh/)
12
12
 
package/biome.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.5.13/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.5.14/schema.json",
3
3
  "vcs": {
4
4
  "enabled": true,
5
5
  "clientKind": "git",
@@ -0,0 +1,41 @@
1
+ ---
2
+ summary: "Tool error text closes with the reason and retryable terms a caller branches on, a ctx.requestInput no 2025-era client capability can serve is refused with a real error envelope, an errors[] entry can declare a log severity below error, and a malformed canvas_id is an input error rather than an expired canvas."
3
+ breaking: false
4
+ security: false
5
+ agent-notes: |
6
+ Three adoption steps for a consumer upgrading from 0.13.4.
7
+
8
+ 1. A test pinning `content[0].text` exactly moves for any tool error whose
9
+ `data` carries a `reason` or a boolean `retryable` — the text now closes
10
+ with `(reason <reason> · not retryable)`. Assert containment instead.
11
+ `toolContractSuite`'s `^Error:` assertion is unaffected.
12
+ 2. Declare a tool's `canvas_id` *input* field with `CanvasIdSchema` from
13
+ `@cyanheads/mcp-ts-core/canvas` (add your own `.describe()` naming the
14
+ producing tool). The *output* `canvas_id` stays a plain `z.string()`.
15
+ 3. `error-contract-unthrown` is new and fires on the next `devcheck`: a
16
+ declared `errors[]` reason no literal `ctx.fail('<reason>'` in the handler
17
+ names. Wire the throw, or drop the entry.
18
+ ---
19
+
20
+ # 0.13.5 — 2026-09-18
21
+
22
+ ## Added
23
+
24
+ - **`severity` on an `errors[]` entry** ([#380](https://github.com/cyanheads/mcp-ts-core/issues/380)) — `debug`, `info`, `notice`, or `warning`, resolved against the thrown error's `data.reason`. It moves that one log record's level and adds `mcp.error.severity` to `mcp.errors.classified`; the wire envelope, the span status, and the `mcp.tool.*` counters are untouched. Tools only.
25
+ - **`CanvasIdSchema`** from `/canvas` ([#327](https://github.com/cyanheads/mcp-ts-core/issues/327)) — the minted `^[A-Za-z0-9_-]{10}$` shape, for a tool's `canvas_id` *input* field so the constraint reaches `inputSchema` and an impossible value is rejected at argument validation.
26
+ - **`errorHeaders` on `fetchWithTimeout` and `httpErrorFromResponse`** ([#302](https://github.com/cyanheads/mcp-ts-core/issues/302)) — an allowlist of response headers copied onto `error.data.headers` under lowercase keys on a non-2xx. Every selected value reaches the client, so never name a credential-bearing header; `set-cookie` is never captured.
27
+ - **Lint rules `error-contract-unthrown` (warning) and `error-contract-severity-unknown` (error)** ([#290](https://github.com/cyanheads/mcp-ts-core/issues/290), [#380](https://github.com/cyanheads/mcp-ts-core/issues/380)) — a declared reason no literal `ctx.fail` / `ctx.recoveryFor` in the handler names, and a `severity` outside the four levels.
28
+
29
+ ## Changed
30
+
31
+ - **Tool error `content[]` closes with `(reason … · not retryable)`** ([#458](https://github.com/cyanheads/mcp-ts-core/issues/458)) whenever `data.reason` is a non-empty string or `data.retryable` a boolean. The numeric code and `data.issues` stay JSON-only, and `structuredContent` is byte-identical to before.
32
+ - **The `Recovery:` line is dropped when the trimmed message already contains the hint verbatim** ([#459](https://github.com/cyanheads/mcp-ts-core/issues/459)) — the case a constraint or refinement rejection produces, whose synthesized hint is the issue's own message. `structuredContent.error.data.recovery.hint` stays populated.
33
+ - **The canvas tenant-cap refusal keeps `RateLimited` and names itself** ([#275](https://github.com/cyanheads/mcp-ts-core/issues/275)) — `data.reason: 'canvas_capacity_exhausted'`, `retryable: true`, and a hint leading with reusing a held `canvas_id`. `getErrorCategory` reads the error data alongside the code, so `mcp.tool.error_category` files it as `server` rather than `upstream`.
34
+ - Skill versions: `add-tool` 2.27 → 2.28, `api-canvas` 2.2 → 2.3, `api-context` 2.4 → 2.5, `api-errors` 1.13 → 1.14, `api-linter` 1.15 → 1.16, `api-telemetry` 1.11 → 1.12, `api-utils` 2.10 → 2.11, `design-mcp-server` 2.27 → 2.28, `field-test` 2.15 → 2.16.
35
+
36
+ ## Fixed
37
+
38
+ - **A `ctx.requestInput` the connection cannot serve fails with an error envelope** ([#379](https://github.com/cyanheads/mcp-ts-core/issues/379)) — on a 2025-era connection declaring no matching capability, `ctx.requestInput` throws `InvalidRequest` (`-32600`) with `data.reason: 'client_capability_missing'` and a hint naming `elicitation.form`, `elicitation.url`, `sampling.tools`, or `roots`, in place of the bare `isError` text block. A resource read gets the same code, reason, and hint instead of `-32603`. Still refused before any wire traffic; the 2026-07-28 leg is unchanged.
39
+ - **The advertised `error.data.reason` description terminates each contract entry's `when`** ([#389](https://github.com/cyanheads/mcp-ts-core/issues/389)), so entries no longer run into each other or into the trailing sentence. Authored text is untouched — a `when` already ending in `.`, `?`, or `!` takes no second terminator.
40
+ - **A malformed `canvas_id` is a `ValidationError`, not an expired canvas** ([#327](https://github.com/cyanheads/mcp-ts-core/issues/327)) — `data.reason: 'canvas_id_malformed'` before any registry lookup, on `acquire`, `drop` (which previously reported a silent `false`), and `importFrom`'s source id. A well-formed id that is missing, expired, or another tenant's keeps `canvas_not_found`.
41
+ - **A 3xx maps to `InvalidRequest` rather than `InternalError`** ([#460](https://github.com/cyanheads/mcp-ts-core/issues/460)) — reachable under `redirect: 'manual'`, where `httpStatusToErrorCode` previously fell through to a code meaning *this* server failed. Outside `withRetry`'s transient set, since re-issuing returns the same redirect.
@@ -0,0 +1,49 @@
1
+ ---
2
+ summary: "A ctx.fail site that never forwards its declared recovery now warns, an errors[] entry can mark itself thrownBy: 'service' so the unthrown rule skips it, capped-list-no-truncation stops firing on value bounds like max_depth_km, and devcheck.config.json can carry the truncation allowlist."
3
+ breaking: false
4
+ security: false
5
+ agent-notes: |
6
+ Four adoption steps for a consumer upgrading from 0.13.5.
7
+
8
+ 1. `error-contract-recovery-unforwarded` is new and fires on the next
9
+ `devcheck` / `lint:mcp`: a literal `ctx.fail('<reason>', …)` site carrying
10
+ neither `ctx.recoveryFor('<reason>')` nor its own `recovery` key. Clear it
11
+ by spreading `...ctx.recoveryFor('<reason>')` into the data argument, by
12
+ passing the resolver as that argument, or with an explicit
13
+ `recovery: { hint }` when the hint needs runtime context.
14
+ 2. An `errors[]` reason the service layer produces takes `thrownBy: 'service'`,
15
+ which is what `error-contract-unthrown` reads to skip it. Prefer it over
16
+ moving the handler's literal `ctx.fail` out of scan range — that silences
17
+ the rule for the whole definition, handler-local reasons included.
18
+ 3. Declare `lint.truncationAllowlist` in `devcheck.config.json` in place of
19
+ `MCP_LINT_TRUNCATION_ALLOWLIST` on each script entry, and drop the env var
20
+ from the `devcheck` and `lint:mcp` scripts once the key is in place. A
21
+ scaffolded config predating this release has no `lint` key; add it.
22
+ 4. Drop any `truncationAllowlist` entry added only to silence
23
+ `capped-list-no-truncation` on a value-bound `max_*` input — those no
24
+ longer warn.
25
+ ---
26
+
27
+ # 0.13.6 — 2026-09-19
28
+
29
+ ## Added
30
+
31
+ - **Lint rule `error-contract-recovery-unforwarded`** (warning, [#255](https://github.com/cyanheads/mcp-ts-core/issues/255)) — one diagnostic per literal `ctx.fail('<reason>', …)` site carrying neither `ctx.recoveryFor('<reason>')` nor its own `recovery` key, so the declared hint reaches neither client surface. A site whose only resolver names a different reason warns too, naming both.
32
+ - **`thrownBy: 'service'` on an `errors[]` entry** ([#462](https://github.com/cyanheads/mcp-ts-core/issues/462)) — lint-only metadata marking a reason produced below the handler, so `error-contract-unthrown` skips it while the handler's own reasons keep being checked. The entry stays typed, advertised, and thrown exactly as an unmarked one.
33
+ - **`lint.truncationAllowlist` in `devcheck.config.json`** ([#388](https://github.com/cyanheads/mcp-ts-core/issues/388)) — read by `scripts/lint-mcp.ts` and forwarded as `LintInput.truncationAllowlist`, so one project-owned declaration covers every entrypoint that shells out to the linter. Takes an array of tool names or `false`; precedence is explicit `LintInput`, then the config file, then `MCP_LINT_TRUNCATION_ALLOWLIST`.
34
+
35
+ ## Changed
36
+
37
+ - **The scaffold's `echo` tool forwards its declared recovery** ([#255](https://github.com/cyanheads/mcp-ts-core/issues/255)) — `templates/src/mcp-server/tools/definitions/echo.tool.ts` spreads `ctx.recoveryFor('empty_message')`, so a new server starts from the shape the rule asks for.
38
+ - Skill versions: `add-service` 1.10 → 1.11, `add-tool` 2.28 → 2.29, `api-errors` 1.14 → 1.15, `api-linter` 1.16 → 1.17.
39
+
40
+ ## Fixed
41
+
42
+ - **`capped-list-no-truncation`'s `max_` arm requires the counted noun to name a returned list** ([#345](https://github.com/cyanheads/mcp-ts-core/issues/345)) — a depth-0 array in `output` (plural-insensitive, a trailing `_count` stripped first) or a generic result container (`results`, `records`, `items`, `rows`, `hits`, `entries`, `matches`, `count`, `page`, `docs`). Value bounds (`max_depth_km`, `maxLat`, `max_date`) and work budgets (`max_court_lookups`, `maxCharacters`) stop warning; `limit`, `<noun>_limit`, and the page-size idioms are unchanged.
43
+ - **`error-contract-unthrown` skips a definition whose `ctx.recoveryFor(` takes a non-literal first argument** ([#462](https://github.com/cyanheads/mcp-ts-core/issues/462)), as it already did for `ctx.fail(` — unknown reasons are in play either way. Its message now names all three fixes.
44
+ - **The `format-parity` diagnostic no longer points authors at a `z.discriminatedUnion` output** ([#268](https://github.com/cyanheads/mcp-ts-core/issues/268)), which `tool()` rejects before any lint rule runs. The "Primary fix" line names a flat `z.object` with a `kind` discriminator and presence-based optional arms, rendered by independent `if` blocks.
45
+ - **The `sanitizeHtml` event-handler property matches only inside a tag** ([#463](https://github.com/cyanheads/mcp-ts-core/issues/463)) — the whole-string `/\bon\w+\s*=/` also matched inert text such as `on0=`, failing roughly once in 300 runs. `on0=` and `<p>onclick=1</p>` are pinned as `examples`; the sanitizer itself is unchanged.
46
+
47
+ ## Dependencies
48
+
49
+ - `@biomejs/biome` 2.5.13 → 2.5.14, `fast-check` ^4.10.0 → ^4.10.1.
@@ -45,10 +45,11 @@ export interface TruncationOptions {
45
45
  }
46
46
  /**
47
47
  * Warns when a tool takes a cap-like input field (`limit`, `per_page`,
48
- * `maxRecords`, … — see `isCapFieldName`) and returns an array output, but
49
- * declares no truncation disclosure — neither a
50
- * `truncated` key in its declared `enrichment` shape, nor `totalCount` in enrichment,
51
- * nor `truncated` or `totalCount` as top-level `output` keys.
48
+ * `maxRecords`, … — see `isCapFieldName`) that plausibly caps the returned list
49
+ * (see `capsTheList`) and returns an array output, but declares no truncation
50
+ * disclosure — neither a `truncated` key in its declared `enrichment` shape, nor
51
+ * `totalCount` in enrichment, nor `truncated` or `totalCount` as top-level
52
+ * `output` keys.
52
53
  *
53
54
  * The established `enrich.total()` convention (`totalCount`) and bespoke output fields
54
55
  * count as honest disclosure — the rule fires only on a genuinely silent cap.
@@ -1 +1 @@
1
- {"version":3,"file":"enrichment-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/enrichment-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkFtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EAC/F,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CA0IlB;AAED,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,mBAAmB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;CACrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EACC;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3E,IAAI,GACJ,SAAS,EACb,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,cAAc,EAAE,CA4ClB"}
1
+ {"version":3,"file":"enrichment-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/enrichment-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqJtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EAC/F,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CA0IlB;AAED,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,mBAAmB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EACC;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3E,IAAI,GACJ,SAAS,EACb,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,cAAc,EAAE,CAgDlB"}
@@ -19,13 +19,60 @@ import { getCoreDefType, objectShape, objectShapeKeys, unwrapWrappers } from './
19
19
  /** Cap-like names that carry no `max`/`limit` morpheme and must be listed outright. */
20
20
  const CAP_FIELD_EXACT = new Set(['limit', 'per_page', 'page_size']);
21
21
  /**
22
- * True for a depth-0 input field name that caps how many items come back.
22
+ * Nouns that name a generic result container whatever the array is called, so a
23
+ * `max_<noun>` built on one of them caps the list even when nothing in `output`
24
+ * carries the same name — `maxRecords` against an `articles` array. Stored
25
+ * singular; the counted noun is singularized before the lookup, so the plural
26
+ * spellings (`results`, `records`, `items`, `rows`, `hits`, `entries`,
27
+ * `matches`, `docs`) resolve here too.
28
+ */
29
+ const RESULT_CONTAINER_NOUNS = new Set([
30
+ 'count',
31
+ 'doc',
32
+ 'entry',
33
+ 'hit',
34
+ 'item',
35
+ 'match',
36
+ 'page',
37
+ 'record',
38
+ 'result',
39
+ 'row',
40
+ ]);
41
+ /** camelCase → snake_case, lowercased, so `maxRecords` and `max_records` are one case. */
42
+ function snakeCase(key) {
43
+ return key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
44
+ }
45
+ /**
46
+ * Singularizes on a bounded suffix set — `articles` → `article`, `studies` →
47
+ * `study`, `matches` → `match`. Not a general English pluralizer: it only has to
48
+ * make an input's counted noun and an output array's name meet in the middle.
49
+ */
50
+ function singularize(noun) {
51
+ if (noun.endsWith('ies'))
52
+ return `${noun.slice(0, -3)}y`;
53
+ if (/(?:s|x|ch|sh)es$/.test(noun))
54
+ return noun.slice(0, -2);
55
+ if (noun.endsWith('s') && !noun.endsWith('ss'))
56
+ return noun.slice(0, -1);
57
+ return noun;
58
+ }
59
+ /**
60
+ * The noun a `max_*` field counts: the name minus the `max_` prefix, minus a
61
+ * trailing `_count` (`max_result_count` → `result`), reduced to its last
62
+ * underscore segment and singularized.
63
+ */
64
+ function countedNoun(key) {
65
+ const stem = snakeCase(key)
66
+ .replace(/^max_/, '')
67
+ .replace(/_count$/, '');
68
+ return singularize(stem.split('_').pop() ?? stem);
69
+ }
70
+ /**
71
+ * True for a depth-0 input field name that is cap-*shaped*.
23
72
  *
24
73
  * Matched by shape, not by an enumeration: an allowlist turns every new cap noun
25
74
  * (`maxRecords`, `maxRows`, `resultLimit`) into a silent gap where the rule never
26
- * runs at all, which is the same defect class the rule exists to catch. Both
27
- * naming conventions normalize to the same snake form first, so `maxRecords` and
28
- * `max_records` are one case.
75
+ * runs at all, which is the same defect class the rule exists to catch.
29
76
  *
30
77
  * - `limit`, and any `<noun>_limit` / `<noun>Limit`
31
78
  * - any `max_<noun>` / `max<Noun>`
@@ -34,17 +81,36 @@ const CAP_FIELD_EXACT = new Set(['limit', 'per_page', 'page_size']);
34
81
  * Deliberately NOT matched: bare `count`, `size`, `n`, `rows`, `records`, and
35
82
  * words that merely start with the letters (`maximum`).
36
83
  *
37
- * The shape cannot separate a cap on *how many* from an upper bound on a *value*,
38
- * so a range filter spelled `max_<noun>` (`max_magnitude`, `maxLat`, `max_date`)
39
- * matches as well. The rule still has to clear its other two conditions before it
40
- * says anything, and `truncationAllowlist` exempts a tool that trips it anyway.
84
+ * Shape alone cannot separate a cap on *how many* from an upper bound on a
85
+ * *value* or a budget on work done, so the `max_` arm is narrowed further by
86
+ * `capsTheList`.
41
87
  */
42
88
  function isCapFieldName(key) {
43
- const normalized = key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
89
+ const normalized = snakeCase(key);
44
90
  return (CAP_FIELD_EXACT.has(normalized) ||
45
91
  normalized.startsWith('max_') ||
46
92
  normalized.endsWith('_limit'));
47
93
  }
94
+ /**
95
+ * True when a cap-shaped input field plausibly caps the returned list.
96
+ *
97
+ * `limit` / `<noun>_limit` / the page-size idioms say what they bound in the
98
+ * name itself and always qualify. `max_<noun>` does not: the same spelling
99
+ * carries value bounds (`max_depth_km`, `maxLat`, `max_date`) and budgets on
100
+ * secondary work (`max_court_lookups`, `maxCharacters`), none of which slice the
101
+ * array. So the counted noun has to name something the tool returns — a depth-0
102
+ * array in `output`, or a generic result container.
103
+ *
104
+ * Accepted false negative: a domain cap naming neither, `max_studies` against a
105
+ * `documents` array, goes silent. Nothing in the declaration separates it from a
106
+ * value bound, and `truncationAllowlist` only suppresses — it cannot re-enable.
107
+ */
108
+ function capsTheList(key, arrayNouns) {
109
+ if (!snakeCase(key).startsWith('max_'))
110
+ return true;
111
+ const noun = countedNoun(key);
112
+ return RESULT_CONTAINER_NOUNS.has(noun) || arrayNouns.has(noun);
113
+ }
48
114
  /**
49
115
  * Output field names that almost always indicate agent-facing context rather
50
116
  * than domain payload. Kept deliberately tiny to avoid false positives —
@@ -228,10 +294,11 @@ export function lintEnrichmentContract(def, definitionType, definitionName) {
228
294
  }
229
295
  /**
230
296
  * Warns when a tool takes a cap-like input field (`limit`, `per_page`,
231
- * `maxRecords`, … — see `isCapFieldName`) and returns an array output, but
232
- * declares no truncation disclosure — neither a
233
- * `truncated` key in its declared `enrichment` shape, nor `totalCount` in enrichment,
234
- * nor `truncated` or `totalCount` as top-level `output` keys.
297
+ * `maxRecords`, … — see `isCapFieldName`) that plausibly caps the returned list
298
+ * (see `capsTheList`) and returns an array output, but declares no truncation
299
+ * disclosure — neither a `truncated` key in its declared `enrichment` shape, nor
300
+ * `totalCount` in enrichment, nor `truncated` or `totalCount` as top-level
301
+ * `output` keys.
235
302
  *
236
303
  * The established `enrich.total()` convention (`totalCount`) and bespoke output fields
237
304
  * count as honest disclosure — the rule fires only on a genuinely silent cap.
@@ -251,12 +318,16 @@ export function lintCappedListTruncation(def, truncationOptions) {
251
318
  // per variant, so a cap in any branch counts — the rule asks whether the tool
252
319
  // can be capped at all, not whether every branch caps.
253
320
  const inputKeys = inputVariants(def.input).flatMap((variant) => objectShapeKeys(variant));
254
- const capKeys = inputKeys.filter(isCapFieldName);
255
- if (capKeys.length === 0)
321
+ const capShapedKeys = inputKeys.filter(isCapFieldName);
322
+ if (capShapedKeys.length === 0)
256
323
  return [];
257
- // Check output for at least one array-typed field
258
- const hasArrayOutput = hasTopLevelArray(def.output);
259
- if (!hasArrayOutput)
324
+ // Check output for at least one array-typed field. The array names are also
325
+ // what a `max_<noun>` cap has to correlate with to count as a list cap.
326
+ const arrayNouns = topLevelArrayNouns(def.output);
327
+ if (arrayNouns.size === 0)
328
+ return [];
329
+ const capKeys = capShapedKeys.filter((key) => capsTheList(key, arrayNouns));
330
+ if (capKeys.length === 0)
260
331
  return [];
261
332
  // Check for truncation disclosure:
262
333
  // 1. Declared enrichment has `truncated` or `totalCount`
@@ -279,12 +350,18 @@ export function lintCappedListTruncation(def, truncationOptions) {
279
350
  },
280
351
  ];
281
352
  }
282
- /** True when the schema has at least one depth-0 field whose core Zod type is `array`. */
283
- function hasTopLevelArray(schema) {
353
+ /**
354
+ * The singularized names of every depth-0 field whose core Zod type is `array`.
355
+ * Empty means the tool returns no list at all, which is the rule's array
356
+ * precondition; non-empty is also what a `max_<noun>` cap correlates against.
357
+ */
358
+ function topLevelArrayNouns(schema) {
284
359
  const shape = objectShape(schema);
285
360
  if (!shape)
286
- return false;
287
- return Object.values(shape).some((field) => getCoreDefType(unwrapWrappers(field)) === 'array');
361
+ return new Set();
362
+ return new Set(Object.entries(shape)
363
+ .filter(([, field]) => getCoreDefType(unwrapWrappers(field)) === 'array')
364
+ .map(([key]) => singularize(snakeCase(key).split('_').pop() ?? key)));
288
365
  }
289
366
  /**
290
367
  * True when truncation is already disclosed — enrichment block has `truncated` or
@@ -1 +1 @@
1
- {"version":3,"file":"enrichment-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/enrichment-rules.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"enrichment-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/enrichment-rules.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * @fileoverview Lint rules for the declarative `errors[]` contract on tool and
3
- * resource definitions. Validates structure (codes, reasons), uniqueness,
4
- * and (when a contract is present) cross-checks handler bodies for thrown
5
- * codes that aren't declared.
3
+ * resource definitions. Validates structure (codes, reasons, severity),
4
+ * uniqueness, and — when a contract is present — cross-checks the handler body:
5
+ * codes thrown but not declared, reasons declared but never thrown, and throw
6
+ * sites that never put the declared `recovery` on the wire.
6
7
  * @module src/linter/rules/error-contract-rules
7
8
  */
8
9
  import type { LintDefinitionType, LintDiagnostic } from '../types.js';
@@ -16,6 +17,7 @@ import type { LintDefinitionType, LintDiagnostic } from '../types.js';
16
17
  * - `recovery` is non-empty and ≥ 5 words (forcing function for thoughtful
17
18
  * agent guidance — placeholders like "Try again." get flagged)
18
19
  * - `retryable` (when present) is a boolean
20
+ * - `severity` (when present) is one of the four levels below `error`
19
21
  */
20
22
  export declare function lintErrorContract(errors: unknown, definitionType: LintDefinitionType, definitionName: string): LintDiagnostic[];
21
23
  /**
@@ -46,4 +48,100 @@ export declare function lintErrorContractConformance(def: {
46
48
  handler?: unknown;
47
49
  errors?: unknown;
48
50
  }, definitionType: LintDefinitionType, definitionName: string): LintDiagnostic[];
51
+ /** One literal `ctx.fail('<reason>', …)` (or `ctx.recoveryFor`) call site. */
52
+ export interface ReasonCallSite {
53
+ /** Source offset just past the site's closing `)`. */
54
+ end: number;
55
+ /** The reason named by the site's first argument, read from the raw source. */
56
+ reason: string;
57
+ /** Source offset of the site's opening `(`. */
58
+ start: number;
59
+ }
60
+ /** What {@link scanReasonCalls} found for one callee in a handler's source. */
61
+ export interface ReasonCallScan {
62
+ /**
63
+ * True when some call site took a non-literal first argument — a variable, a
64
+ * template literal, a map lookup. The set of reasons in play is then unknown.
65
+ */
66
+ indeterminate: boolean;
67
+ /** Every literal call site found, in source order. */
68
+ sites: ReasonCallSite[];
69
+ }
70
+ /**
71
+ * Finds every `<callee>('<literal>', …)` site in a handler's source, one record
72
+ * per site.
73
+ *
74
+ * Matching runs over the comment- and string-stripped text, which already
75
+ * excludes a call written inside a comment or nested in another literal. That
76
+ * transform blanks literal *contents* and keeps the quotes, so the reason
77
+ * survives only in the raw source — and it is length-preserving, so the same
78
+ * offsets address both. `tests/unit/linter/source-text.test.ts` asserts that
79
+ * alignment directly, since this rule depends on it.
80
+ *
81
+ * The span is carried so a rule that needs a site's arguments — not just which
82
+ * reason it names — can read them without rescanning.
83
+ */
84
+ export declare function scanReasonCalls(source: string, callee: string): ReasonCallScan;
85
+ /**
86
+ * Flags a declared `errors[]` reason that no code path in the handler can
87
+ * produce — the inverse of {@link lintErrorContractConformance}, which only
88
+ * catches codes thrown but not declared.
89
+ *
90
+ * A dead entry compiles, lints clean, and stays in the contract indefinitely;
91
+ * the typed `ctx.fail` union accepts the reason, so nothing downstream objects.
92
+ * The cost lands on the client, which plans for a failure mode the tool cannot
93
+ * produce while the mode it does produce goes undocumented.
94
+ *
95
+ * **Trigger.** Only when the handler holds at least one literal `ctx.fail(`. A
96
+ * handler with none produces its reasons somewhere the scan cannot reach, and
97
+ * firing there would warn on every service-layer definition. A `ctx.fail(` or
98
+ * `ctx.recoveryFor(` whose first argument is not a string literal makes the
99
+ * named set unknowable, so the whole definition is skipped rather than guessed
100
+ * at.
101
+ *
102
+ * **Warning, never error.** A reason produced outside the handler closure is
103
+ * invisible to any `toString()` scan, so the rule can never prove absence. An
104
+ * entry the service layer produces says so with `thrownBy: 'service'` and is
105
+ * skipped while the handler's own reasons keep being checked. Still silent
106
+ * without a marker: a `createFail(errors)` resolver built outside the handler,
107
+ * and an aliased `const fail = ctx.fail`.
108
+ */
109
+ export declare function lintErrorContractUnthrown(def: {
110
+ handler?: unknown;
111
+ errors?: unknown;
112
+ }, definitionType: LintDefinitionType, definitionName: string): LintDiagnostic[];
113
+ /**
114
+ * Flags a literal `ctx.fail('<reason>', …)` site that does not put the
115
+ * contract's `recovery` on the wire.
116
+ *
117
+ * An `errors[]` entry must declare `recovery`, but reaching the client with it
118
+ * is opt-in: the throw site forwards `ctx.recoveryFor('<reason>')`, or passes
119
+ * its own `recovery` key. A site that does neither ships `reason` and
120
+ * `retryable` with no hint — and because the framework mirrors
121
+ * `data.recovery.hint` into the error `content[]`, both client surfaces lose it
122
+ * together. The declared guidance is right there in the contract and reaches
123
+ * nobody; an error-path test asserting `code` and `reason` passes either way.
124
+ *
125
+ * **Per site, not per reason.** A handler wiring one of six throws is covered
126
+ * at one of them, so each site is judged on its own argument list. Two sites
127
+ * naming one reason, one forwarding and one bare, produce exactly one
128
+ * diagnostic.
129
+ *
130
+ * **Accepted forms.** `{ ...ctx.recoveryFor('<reason>') }` spread into the data
131
+ * object, `ctx.recoveryFor('<reason>')` passed as the data argument, and an
132
+ * explicit `recovery` key carrying a runtime-interpolated hint.
133
+ *
134
+ * **Bails.** A non-literal first argument on either callee skips the whole
135
+ * definition — the reasons in play are unknown. A resolver sitting outside
136
+ * every fail span (a hoisted `const hint = ctx.recoveryFor('x')`) skips that
137
+ * reason, since the binding is assembled where the scan cannot follow it. A
138
+ * data argument the scan cannot read skips that one site.
139
+ *
140
+ * **Warning, never error.** A failure thrown below the handler is invisible to
141
+ * a `handler.toString()` scan, so the rule speaks only for the sites it sees.
142
+ */
143
+ export declare function lintErrorContractRecoveryUnforwarded(def: {
144
+ handler?: unknown;
145
+ errors?: unknown;
146
+ }, definitionType: LintDefinitionType, definitionName: string): LintDiagnostic[];
49
147
  //# sourceMappingURL=error-contract-rules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-contract-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/error-contract-rules.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAetE;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,EACf,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CAmLlB;AAyDD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC5C,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CAiGlB"}
1
+ {"version":3,"file":"error-contract-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/error-contract-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAuBtE;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,EACf,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CAkMlB;AAyDD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC5C,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CAiGlB;AAMD,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,sDAAsD;IACtD,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CA0B9E;AA6DD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC5C,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CA4BlB;AA4DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,oCAAoC,CAClD,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC5C,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CA4ClB"}