@cyanheads/mcp-ts-core 0.10.13 → 0.10.15

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 (71) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +1 -1
  3. package/README.md +10 -6
  4. package/biome.json +1 -1
  5. package/changelog/0.10.x/0.10.13.md +2 -2
  6. package/changelog/0.10.x/0.10.14.md +22 -0
  7. package/changelog/0.10.x/0.10.15.md +57 -0
  8. package/dist/linter/rules/definition-rules.d.ts +23 -0
  9. package/dist/linter/rules/definition-rules.d.ts.map +1 -0
  10. package/dist/linter/rules/definition-rules.js +35 -0
  11. package/dist/linter/rules/definition-rules.js.map +1 -0
  12. package/dist/linter/rules/enrichment-rules.d.ts +2 -2
  13. package/dist/linter/rules/enrichment-rules.d.ts.map +1 -1
  14. package/dist/linter/rules/enrichment-rules.js +5 -0
  15. package/dist/linter/rules/enrichment-rules.js.map +1 -1
  16. package/dist/linter/rules/handler-body-rules.d.ts +1 -1
  17. package/dist/linter/rules/handler-body-rules.d.ts.map +1 -1
  18. package/dist/linter/rules/handler-body-rules.js +3 -0
  19. package/dist/linter/rules/handler-body-rules.js.map +1 -1
  20. package/dist/linter/rules/prompt-rules.d.ts.map +1 -1
  21. package/dist/linter/rules/prompt-rules.js +3 -0
  22. package/dist/linter/rules/prompt-rules.js.map +1 -1
  23. package/dist/linter/rules/resource-rules.d.ts.map +1 -1
  24. package/dist/linter/rules/resource-rules.js +3 -0
  25. package/dist/linter/rules/resource-rules.js.map +1 -1
  26. package/dist/linter/rules/tool-rules.d.ts.map +1 -1
  27. package/dist/linter/rules/tool-rules.js +3 -0
  28. package/dist/linter/rules/tool-rules.js.map +1 -1
  29. package/dist/linter/validate.d.ts.map +1 -1
  30. package/dist/linter/validate.js +16 -1
  31. package/dist/linter/validate.js.map +1 -1
  32. package/dist/logs/combined.log +8 -0
  33. package/dist/logs/error.log +4 -0
  34. package/dist/logs/interactions.log +0 -0
  35. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
  36. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +17 -2
  37. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
  38. package/dist/utils/network/fetchWithTimeout.d.ts +14 -1
  39. package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
  40. package/dist/utils/network/fetchWithTimeout.js +28 -5
  41. package/dist/utils/network/fetchWithTimeout.js.map +1 -1
  42. package/dist/utils/network/httpError.d.ts +4 -1
  43. package/dist/utils/network/httpError.d.ts.map +1 -1
  44. package/dist/utils/network/httpError.js +6 -0
  45. package/dist/utils/network/httpError.js.map +1 -1
  46. package/dist/utils/network/retry.d.ts +12 -0
  47. package/dist/utils/network/retry.d.ts.map +1 -1
  48. package/dist/utils/network/retry.js +58 -3
  49. package/dist/utils/network/retry.js.map +1 -1
  50. package/dist/utils/overflow/outlineOnOverflow.d.ts +24 -11
  51. package/dist/utils/overflow/outlineOnOverflow.d.ts.map +1 -1
  52. package/dist/utils/overflow/outlineOnOverflow.js +24 -11
  53. package/dist/utils/overflow/outlineOnOverflow.js.map +1 -1
  54. package/package.json +25 -25
  55. package/skills/add-tool/SKILL.md +2 -2
  56. package/skills/api-canvas/SKILL.md +2 -2
  57. package/skills/api-linter/SKILL.md +14 -1
  58. package/skills/api-utils/SKILL.md +4 -4
  59. package/skills/design-mcp-server/SKILL.md +2 -2
  60. package/skills/field-test/SKILL.md +5 -4
  61. package/skills/git-wrapup/SKILL.md +12 -24
  62. package/skills/orchestrations/SKILL.md +2 -2
  63. package/skills/orchestrations/workflows/field-test-fix.md +1 -1
  64. package/skills/orchestrations/workflows/fix-wrapup-release.md +2 -2
  65. package/skills/orchestrations/workflows/maintenance-release.md +2 -2
  66. package/skills/release-and-publish/SKILL.md +2 -2
  67. package/skills/techniques/SKILL.md +2 -2
  68. package/skills/techniques/references/outline-on-overflow.md +15 -9
  69. package/skills/tool-defs-analysis/SKILL.md +63 -22
  70. package/templates/Dockerfile +1 -1
  71. package/templates/package.json +2 -2
@@ -4,7 +4,7 @@ description: >
4
4
  Pick and run a multi-phase workflow that chains foundational task skills (`git-wrapup`, `release-and-publish`, `maintenance`, `field-test`, `setup`, etc.) end-to-end. Routes user intent to a workflow file under `workflows/` — greenfield builds, maintenance + release, field-test + fix, or known-work + release. Single source for the universal rules (no commits without authorization, no destructive git, no marketing language), the orchestrator posture (own the goal, ground sub-agents in primary sources, verify against the goal), and the sub-agent strategy (orient block, parallel fanout, isolation, normalization) that apply across every workflow. Sub-agents are an optional capability — workflows run linearly when fanout isn't available.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.6"
7
+ version: "1.7"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -189,7 +189,7 @@ Sub-agent self-reports describe intent, not always reality. After every phase th
189
189
  - **GitHub** — `gh repo view --json visibility`, `gh release view v<VERSION>`, `gh issue list`, `gh issue view <N> --comments` to confirm the fix comment landed
190
190
  - **npm / registries** — `npm view <pkg>@<version>`, registry-specific checks
191
191
  - **Build state** — re-run `bun run devcheck` if the previous phase was supposed to land green
192
- - **Quality** — tag annotation reads as structured markdown (not flat string), subject omits the version number, no marketing adjectives, dep arrows present where applicable, issue backlinks where applicable
192
+ - **Quality** — tag annotation is a headline digest covering every change (flat bullets — notable ones named, minor ones in one grouped bullet; no changelog section headers, deps ≤1 line, no gates line), subject omits the version number, no marketing adjectives, issue backlinks where applicable, changelog link as final line
193
193
 
194
194
  If verification disagrees with the sub-agent's report, that's the signal to re-spawn with the actual state and the unmet goal in the prompt — not to trust the report. The goal hasn't changed; only the path needs to.
195
195
 
@@ -137,7 +137,7 @@ Each sub-agent reads both `skills/git-wrapup/SKILL.md` and `skills/release-and-p
137
137
  4. Release commit on top — version bump + changelog + regenerated artifacts as `chore(release): v<version>`
138
138
  5. Tag the release commit
139
139
 
140
- The tag annotation and changelog cover ALL fixesthe commit split is about git history, not release notes.
140
+ The changelog carries the depth; the tag annotation covers every change at headline granularity notable ones named, minor ones in one grouped bullet (per git-wrapup step 8). The commit split is about git history, not release notes.
141
141
 
142
142
  **Version bump.** Default **patch** for field-test fix releases. **Minor** when enhancements are bundled in.
143
143
 
@@ -123,7 +123,7 @@ Each sub-agent reads BOTH `skills/git-wrapup/SKILL.md` AND `skills/release-and-p
123
123
  4. Release commit on top: `chore(release): v<version>` — version bump + changelog + regenerated artifacts
124
124
  5. Tag the release commit
125
125
 
126
- The tag annotation and changelog cover ALL fixesthe commit split is about git history, not release notes.
126
+ The changelog carries the depth; the tag annotation covers every change at headline granularity notable ones named, minor ones in one grouped bullet (per git-wrapup step 8). The commit split is about git history, not release notes.
127
127
 
128
128
  **Version bump.** Default **patch** for bug-fix releases. **Minor** when enhancements are included.
129
129
 
@@ -134,7 +134,7 @@ The tag annotation and changelog cover ALL fixes — the commit split is about g
134
134
  | Private / in-development | Version bump → changelog → commit → tag → mcpb bundle → push → `gh release create`. Skip `bun publish`, Docker, MCP Registry. |
135
135
  | Public / launched | Full `release-and-publish`: push + `bun publish` + `publish-mcp` + bundle + GH release + Docker (if Dockerfile). |
136
136
 
137
- **Tag annotations** are for end users — internal dev cleanup (lockfile refreshes, linter fixes, build config) belongs in commit bodies, not the tag annotation.
137
+ **Tag annotations** are for end users — every changelog-worthy change stays visible in the tag, with minor/internal items (build config, repo hygiene, metadata) grouped into ONE compact bullet; only non-changelog churn (lockfile refreshes, lint fixes) stays in commit bodies alone.
138
138
 
139
139
  ### Phase 4: Issue cleanup
140
140
  Close issues that shipped — only those. Skipped issues stay open.
@@ -119,7 +119,7 @@ If a target's diff suggests minor-or-above, **pause that target and surface to t
119
119
  ### Phase 4: Wrap-up + release
120
120
  Each sub-agent reads BOTH `skills/git-wrapup/SKILL.md` AND `skills/release-and-publish/SKILL.md`. Runs wrap-up (version bump, changelog authoring, commit, annotated tag), then release (push, npm publish, MCP Registry, GH release, Docker).
121
121
 
122
- **Framework changelog reading.** When `mcp-ts-core` was updated, the sub-agent must read the framework's changelog files for the version delta (e.g. `node_modules/@cyanheads/mcp-ts-core/changelog/0.9.x/0.9.2.md` through `0.9.6.md`) and distill user-facing changes relevant to this server into the changelog entry and tag annotation. "Picks up upstream fixes" is not acceptable — name what changed.
122
+ **Framework changelog reading.** When `mcp-ts-core` was updated, the sub-agent must read the framework's changelog files for the version delta (e.g. `node_modules/@cyanheads/mcp-ts-core/changelog/0.9.x/0.9.2.md` through `0.9.6.md`) and distill user-facing changes relevant to this server into the changelog entry; the tag annotation carries at most a one-line framework mention with the version arrow. "Picks up upstream fixes" is not acceptable in the changelog — name what changed.
123
123
 
124
124
  **Wrap-up scope.** Determined by repo visibility (`gh repo view --json visibility`):
125
125
 
@@ -135,7 +135,7 @@ Each sub-agent reads BOTH `skills/git-wrapup/SKILL.md` AND `skills/release-and-p
135
135
 
136
136
  **Commit structure.** Group the work by concern, then land the release artifacts (version bumps + changelog + regenerated `docs/tree.md`/`server.json`/`manifest.json`) as a `chore(release): <version> — <theme>` commit on top — same model as `git-wrapup` Step 7. A single-concern pass (just dep updates, or one framework adoption) is one work commit plus the release commit; a pass spanning multiple distinct concerns splits into per-concern work commits with the release commit last. Regenerated meta-drift is release-artifact-shaped — it rides in the release commit, never carved out as its own.
137
137
 
138
- **Tag annotations are for end users.** Internal dev cleanup (lockfile refreshes, linter fixes, build config) belongs in the commit body, not the tag annotation.
138
+ **Tag annotations are for end users.** Every changelog-worthy change stays visible in the tag, with minor/internal items (build config, repo hygiene, metadata) grouped into ONE compact bullet; only non-changelog churn (lockfile refreshes, lint fixes) stays in commit bodies alone.
139
139
 
140
140
  **Tag-moving protocol.** If post-version doc changes land after the version commit, move the tag to HEAD: delete remote release, delete remote + local tag, recreate tag at new HEAD with same annotation, re-push, recreate release with `.mcpb`. Authorized within the workflow — same-day forward move.
141
141
 
@@ -4,7 +4,7 @@ description: >
4
4
  Ship a release end-to-end across every registry the project targets (npm, MCP Registry, GitHub Releases for `.mcpb` bundles, GHCR). Runs the final verification gate, pushes commits and tags, then publishes to each applicable destination. Assumes git wrapup (version bumps, changelog, commit, annotated tag) is already complete — this skill is the post-wrapup publish workflow. Retries transient network failures on publish steps; halts with a partial-state report when retries are exhausted or the failure is terminal.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.10"
7
+ version: "2.11"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -202,7 +202,7 @@ Confirm each published artifact is actually live — don't rely on a successful
202
202
  - **npm**: `npm view <package.json#name>@<version> version` — must return the version string
203
203
  - **MCP Registry**: `curl -s "https://registry.modelcontextprotocol.io/v0.1/servers/<mcpName>/versions/<version>"` — must return HTTP 200 with `server.version` matching `<version>` (`mcpName` is the `name` field from `server.json`; URL-encode `/` as `%2F`). The search endpoint (`/v0.1/servers?search=`) paginates and may not include the latest version for packages with many releases — always use the direct version lookup.
204
204
  - **GitHub Release**: `gh release view v<VERSION> -R <OWNER>/<REPO> --json assets --jq '.assets[].name'` — must list the `.mcpb` file
205
- - **GHCR**: fetch an anonymous bearer token, then `curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $TOKEN" "https://ghcr.io/v2/<OWNER>/<REPO>/manifests/<VERSION>"` must return HTTP 200
205
+ - **GHCR**: `docker manifest inspect ghcr.io/<OWNER>/<REPO>:<VERSION>` must exit 0 (resolves multi-arch OCI indexes directly with the correct media types; exits non-zero when the tag is genuinely absent)
206
206
 
207
207
  If any check fails, halt and report which destination is unreachable. A successful `docker push` or `bun publish` exit code does not guarantee the artifact is queryable — registry propagation delays, auth scoping, and partial failures all exist.
208
208
 
@@ -4,7 +4,7 @@ description: >
4
4
  Catalog of reusable response- and data-shaping techniques for MCP servers built on `@cyanheads/mcp-ts-core` — overflow handling, payload shaping, retrieval patterns. Use when a tool's payload is too large, awkwardly shaped, or expensive to retrieve and you want a proven pattern instead of inventing one. Each technique has a self-contained reference under `references/`.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "0.1"
7
+ version: "0.2"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -13,7 +13,7 @@ metadata:
13
13
 
14
14
  A directory of cross-cutting techniques for shaping what a handler returns and how a client retrieves it — patterns that don't belong to a single API surface. Each entry is a self-contained reference under `references/`: the problem it solves, when to reach for it (and when not to), and how to apply it with current framework primitives.
15
15
 
16
- These are **patterns, not new primitives** — they compose `tool()`, discriminated-union `output`, `ctx.state`, and the existing helpers. Where a technique has (or will have) a dedicated helper, its reference says so and links the tracking issue.
16
+ These are **patterns, not new primitives** — they compose `tool()`, `output`/`format()` shaping, `ctx.state`, and the existing helpers. Where a technique has (or will have) a dedicated helper, its reference says so and links the tracking issue.
17
17
 
18
18
  ## Techniques
19
19
 
@@ -20,9 +20,9 @@ This is distinct from the other two overflow shapes:
20
20
 
21
21
  **Never truncate to fit a budget.** When a payload is too big, return a complete, honest outline of what's available plus how to retrieve it — identically on `content[]` and `structuredContent`.
22
22
 
23
- ## The shape — a discriminated-union `output`
23
+ ## The shape — a flat `output` with a `kind` discriminator
24
24
 
25
- The outline is the payload the agent acts on, so it lands in the **main body** (`structuredContent` + `content[]`), as a variant of the tool's own `output`. Not the enrichment block — enrichment is *additive* (`output.extend(...)` merged after `output.parse(result)`), so it can add fields to the fat document but never replace it. Not a post-hoc framework swap either — that would emit a `structuredContent` shape the advertised `outputSchema` (`tools/list`) doesn't describe. A discriminated-union variant is the only placement that replaces the payload, is advertised honestly, and gets `format()`-parity for free.
25
+ The outline is the payload the agent acts on, so it lands in the **main body** (`structuredContent` + `content[]`), as a variant of the tool's own `output`. Not the enrichment block — enrichment is *additive* (`output.extend(...)` merged after `output.parse(result)`), so it can add fields to the fat document but never replace it. Not a post-hoc framework swap either — that would emit a `structuredContent` shape the advertised `outputSchema` (`tools/list`) doesn't describe. A single `output` object carrying both modes — a `kind` discriminator plus presence-based optional arms — is the placement that replaces the payload, is advertised honestly, and holds `format()`-parity. (`tool()` requires `output` to be a `ZodObject`: the `schema-is-object` lint rule and the enrichment `.extend()` are both object-only, so a `z.discriminatedUnion` output is rejected — model the two modes as optional arms of one object, not union branches.)
26
26
 
27
27
  ```ts
28
28
  import { tool, z } from '@cyanheads/mcp-ts-core';
@@ -44,11 +44,17 @@ export const getLabel = tool('get_label', {
44
44
  .optional()
45
45
  .describe('Sections to return. Omit for the full label (or an outline if it overflows).'),
46
46
  }),
47
- output: z.discriminatedUnion('kind', [
48
- FullLabel.extend({ kind: z.literal('full') }),
49
- OUTLINE_VARIANT,
50
- ]),
51
- format: (r) => (r.kind === 'outline' ? formatOutline(r) : renderLabel(r)),
47
+ output: z.object({
48
+ kind: z.enum(['full', 'outline']),
49
+ ...FullLabel.partial().shape, // full arm — every field optional
50
+ sections: OUTLINE_VARIANT.shape.sections.optional(), // outline arm
51
+ notice: OUTLINE_VARIANT.shape.notice.optional(),
52
+ }),
53
+ // Render each arm on field presence, independently — never branch on `kind` (see below).
54
+ format: (r) => [
55
+ ...(r.id ? renderLabel(r) : []), // full arm — key on a full-only field
56
+ ...(r.sections ? formatOutline({ kind: 'outline', sections: r.sections, notice: r.notice ?? '' }) : []),
57
+ ],
52
58
  async handler(input) {
53
59
  const doc = await fetchLabel(input.query); // deterministic from query
54
60
  if (input.sections?.length) {
@@ -60,7 +66,7 @@ export const getLabel = tool('get_label', {
60
66
  });
61
67
  ```
62
68
 
63
- `format()`-parity is enforced **per branch** the linter walks each discriminated-union arm separately, so both `full` and `outline` must render. `formatOutline` is the shipped renderer for the `outline` arm; you supply the `full` renderer. That keeps the two client surfaces in lockstep with no extra work.
69
+ `format()`-parity holds because every terminal field in `output` must appear in the rendered text. With a flat object the linter builds **one** synthetic sample with every optional field populated at once, so render each arm on field presence, independently — a mutually-exclusive `if (kind === 'outline') else …` renders only one arm against that all-fields sample and fails parity for the other. `formatOutline` is the shipped renderer for the `outline` arm; you supply the `full` renderer. That keeps the two client surfaces in lockstep.
64
70
 
65
71
  ## The helper
66
72
 
@@ -69,7 +75,7 @@ export const getLabel = tool('get_label', {
69
75
  | Export | Purpose |
70
76
  |:--|:--|
71
77
  | `outlineOnOverflow(doc, options?)` | Returns `{ kind: 'full', ...doc }` under budget (or with `< 2` sections), else `{ kind: 'outline', sections, notice }`. |
72
- | `OUTLINE_VARIANT` | The reusable `outline`-arm Zod schema for your discriminated-union `output`. |
78
+ | `OUTLINE_VARIANT` | The reusable `outline`-arm Zod schema; fold `.shape.sections` / `.shape.notice` into your flat `output` object as optional arms. |
73
79
  | `selectSections(doc, want, { alwaysKeep })` | Projects the document to requested keys plus always-kept metadata. The selection-path counterpart. |
74
80
  | `formatOutline(outline)` | Renders the outline to `content[]` for `format()`. |
75
81
  | `DEFAULT_OUTLINE_BUDGET_BYTES` | The default budget (`24_000`) when `options.budget` is omitted. |
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: tool-defs-analysis
3
3
  description: >
4
- Read-only audit of MCP definition language across an existing surface — tools, resources, prompts. Walks every definition file and checks 12 categories the LLM reads to decide whether and how to call: voice & tense, internal leaks, audience leaks, defaults, recovery hints, output descriptions, cross-references, sparsity, examples, structure, mutator observability, unit-bearing numeric names. Produces grouped findings with file:line citations and a numbered options list. Use during polish, after a refactor, or before a release. Complements `field-test` (behavior testing) and `security-pass` (security audit).
4
+ Read-only audit of MCP definition language across an existing surface — tools, resources, prompts, server instructions. Walks every definition file and checks 15 categories the LLM reads to decide whether and how to call: voice & tense, internal leaks, audience leaks, defaults, recovery hints, field descriptions, cross-references, sparsity, examples, structure, mutator observability, unit-bearing numeric names, validator-enforced constraints, annotations truthfulness, single-line strings — then a cross-surface pass: naming taxonomy, parameter vocabulary, tool overlap, instructions drift, length outliers. Produces grouped findings with file:line citations and a numbered options list. Use during polish, after a refactor, or before a release. Complements `field-test` (behavior testing) and `security-pass` (security audit).
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.3"
7
+ version: "1.4"
8
8
  audience: external
9
9
  type: audit
10
10
  ---
@@ -22,7 +22,7 @@ This skill is the **review-time pass** for that drift. Read each definition the
22
22
  | `security-pass` | Injection, scopes, input sinks |
23
23
  | `tool-defs-analysis` (this) | LLM-facing language across the existing surface |
24
24
 
25
- `field-test` already audits descriptions for implementation leaks, meta-coaching, and consumer-aware phrasing during its catalog step — that's a fast shallow pass alongside live tool calls. This skill is the deeper review: 12 categories, every field, every recovery hint, every default value, with file:line citations.
25
+ `field-test` already audits descriptions for implementation leaks, meta-coaching, and consumer-aware phrasing during its catalog step — that's a fast shallow pass alongside live tool calls. This skill is the deeper review: 15 categories, every field, every recovery hint, every default value, with file:line citations — plus a cross-surface pass for the drift no single file shows.
26
26
 
27
27
  **Read-only.** This skill produces a report; the maintainer applies fixes. While running it, do not run git, do not stage or commit, do not update the changelog, do not run `devcheck`, do not invoke wrapup or release workflows. Fixes flow through the normal authoring path (edit the definition, then re-run this skill if you want to verify).
28
28
 
@@ -53,11 +53,11 @@ find src/mcp-server/resources/definitions -type f -name "*resource.ts" 2>/dev/nu
53
53
  find src/mcp-server/prompts/definitions -type f -name "*.prompt.ts" 2>/dev/null | sort
54
54
  ```
55
55
 
56
- The `*tool.ts` / `*resource.ts` patterns also catch `*.app-tool.ts` / `*.app-resource.ts`. If the server's definitions live elsewhere (`examples/`, a packages workspace, …), audit those paths too.
56
+ The `*tool.ts` / `*resource.ts` patterns also catch `*.app-tool.ts` / `*.app-resource.ts`. If the server's definitions live elsewhere (`examples/`, a packages workspace, …), audit those paths too. Also locate the server-level `instructions` string if the server sets one (the `createApp` option — `grep -rn "instructions" src/ --include="*.ts"`); it's audited in the cross-surface pass.
57
57
 
58
58
  Use `TaskCreate` — one task per file. Mark each complete after its findings are captured.
59
59
 
60
- ### 2. Walk the 12 categories per file
60
+ ### 2. Walk the 15 categories per file
61
61
 
62
62
  Read each definition file in full. Apply every category — most files trip more than one. Capture each hit with `file:line`, the offending excerpt, and a one-line fix.
63
63
 
@@ -79,8 +79,6 @@ Read each definition file in full. Apply every category — most files trip more
79
79
 
80
80
  **Smell:** "/api/v2/by-state", "Adds a second API call", "API requires `two_year_period`", "(deprecated; use bar_v2)", "TODO: support batch mode", "Used internally by FooService".
81
81
 
82
- Prior art: #25.
83
-
84
82
  #### 3. Audience leaks
85
83
 
86
84
  **Look in:** every `description` and `.describe()`.
@@ -89,7 +87,7 @@ Prior art: #25.
89
87
 
90
88
  **Smell:** "suitable for LLM consumption", "Treat the returned ID as the canonical Y", "Agents should…", "Callers should…", "When you call this tool…", any reference to "LLM", "agent", "Claude", "the model".
91
89
 
92
- Prior art: #74. Field-test catches this in its leak audit; this skill is the more thorough pass.
90
+ Field-test catches this in its leak audit; this skill is the more thorough pass.
93
91
 
94
92
  #### 4. Defaults
95
93
 
@@ -107,19 +105,21 @@ Prior art: #74. Field-test catches this in its leak audit; this skill is the mor
107
105
 
108
106
  **Smell:** "Check the logs", "See documentation", "Contact admin", "Try again later" (with no condition), generic non-actionable text, hints that name internal classes or files.
109
107
 
110
- #### 6. Output descriptions
108
+ #### 6. Field descriptions
111
109
 
112
- **Look in:** every field `.describe()` inside `output: z.object({ ... })`.
110
+ **Look in:** every field in `input` and `output` schemas; resource URI template variables.
113
111
 
114
- **Check:** the description tells the agent what the *value* is — not just the field name restated, not silent on dynamic shapes.
112
+ **Check:** every field carries a `.describe()`, and it tells the agent what the *value* is — not just the field name restated, not silent on dynamic shapes. Enum variants — especially operation discriminators — are explained.
115
113
 
116
114
  **Smell:**
117
115
 
116
+ - An input field with no `.describe()` at all
118
117
  - `name: z.string().describe('Name')` — tautology
119
- - `description: z.string().describe('Description.')` tautology
118
+ - `operation: z.enum([...])` whose variants are never explained
120
119
  - `metadata: z.record(z.string(), z.unknown()).describe('Metadata')` — opaque dynamic shape with no hint about keys/values
121
120
  - Optional fields with no note on when they're absent
122
- - Enum fields with no `.describe()` on the variants
121
+ - Paging fields (`total`, `hasMore`, `nextCursor`) with semantics unstated — or a `limit` param that doesn't say whether it caps the page or the whole result
122
+ - A URI template variable (`{cid}`) never described anywhere
123
123
 
124
124
  #### 7. Cross-references
125
125
 
@@ -157,8 +157,6 @@ Prior art: #74. Field-test catches this in its leak audit; this skill is the mor
157
157
 
158
158
  **Smell:** blank lines (`\n\n`) inside a description string, `- bullet` lines, `## Header` lines, "Operations:\n- foo: …" duplicating an enum's `.describe()` text.
159
159
 
160
- Prior art: #33.
161
-
162
160
  #### 11. Mutator observability
163
161
 
164
162
  **Look in:** mutator tools — any tool that writes, updates, deletes, appends, or patches (i.e., definitions without `annotations.readOnlyHint: true`).
@@ -171,11 +169,51 @@ Prior art: #33.
171
169
 
172
170
  **Look in:** every `z.number()` field in `output` schemas.
173
171
 
174
- **Check:** the field name carries a unit when not pinned by context — `sizeInBytes`, `durationInMs`, `priceInCents`, `latencyInMs`. The `.describe()` drops in summarization or gets truncated; the field name persists into the JSON the agent reads.
172
+ **Check:** the field name carries a unit when not pinned by context — `sizeInBytes`, `durationInMs`, `priceInCents`, `latencyInMs`. The `.describe()` drops in summarization or gets truncated; the field name persists into the JSON the agent reads. Scores, ratios, and percentages carry their range the same way — in the name or as the first thing in the describe (`0–1`).
173
+
174
+ **Smell:** `size`, `duration`, `price`, `latency` — bare names that force the agent to guess units; `score`/`confidence` with no stated range (0.87 and 87 both pass the schema). Exempt: `index`, `position`, `page`, `offset`, `limit`, `totalCount`, `itemCount` (dimensionless).
175
+
176
+ #### 13. Constraints in validators
177
+
178
+ **Look in:** input schemas — every field whose `.describe()` states a format, range, length, or pattern.
179
+
180
+ **Check:** stated constraints are machine-enforced in the schema (`.regex()`, `.min()`/`.max()`, `.int()`, `.length()`, an enum) so they emit into the JSON Schema the client renders — a constraint living only in prose reaches a weaker model unreliably and burns retries on malformed input. Opaque-ID params also say how to *obtain* the value (which sibling tool returns it), not just its shape.
181
+
182
+ **Smell:** `.describe('Date in YYYY-MM-DD format')` on a bare `z.string()`; "max 100" in prose with no `.max(100)`; an ID param whose describe gives the format but never the tool that produces it.
183
+
184
+ #### 14. Annotations truthfulness
185
+
186
+ **Look in:** the `annotations` block on every tool.
187
+
188
+ **Check:** hints match what the handler actually does — clients gate confirmation prompts and retry policy on them. A purely-read tool carries `readOnlyHint: true`; deletes and overwrites aren't marked `destructiveHint: false`; retry-safe mutators carry `idempotentHint: true`; tools calling external services carry `openWorldHint: true`. If `annotations.title` is set, it still matches the tool's current name and behavior.
189
+
190
+ **Smell:** `readOnlyHint: true` on anything that writes; a read-only tool with no `readOnlyHint` (clients assume it can mutate); `destructiveHint: false` on a delete; a stale `title` surviving a rename.
175
191
 
176
- **Smell:** `size`, `duration`, `price`, `latency` — bare names that force the agent to guess units or rely on description text. Exempt: `index`, `position`, `totalCount`, `itemCount` (dimensionless).
192
+ #### 15. Single-line strings
177
193
 
178
- ### 3. Report
194
+ **Look in:** every `description`, `.describe()`, and error `recovery` / `when` string in a definition file.
195
+
196
+ **Check:** each is a single-line string literal. NEVER split one across lines with `+` concatenation (`'part one ' + 'part two'`), and never line-wrap a description into a `\n`-bearing template literal. The formatter does not break string literals, so a long single-line string passes formatting untouched — the line-width limit is not a reason to concatenate.
197
+
198
+ **Why it's not cosmetic:** `+`-concatenation forces every fragment to hand-carry its boundary whitespace, and a dropped trailing space silently fuses two words in the rendered schema the model reads (`'…table_name. ' + 'Columns…'` renders as `table_name.Columns`). Correct output is byte-identical to the single-line form, so the concatenation buys nothing and adds a class of silent contract corruption.
199
+
200
+ **Smell:** a string literal ending in `' +` or `" +` at end of line; a `description:` value spanning multiple quoted fragments; a multi-line template literal inside a description (also a Structure finding, #10).
201
+
202
+ **Fix:** collapse to one single-line string literal.
203
+
204
+ ### 3. Cross-surface pass
205
+
206
+ The per-file walk misses drift that only shows between files. After it, sweep the whole surface:
207
+
208
+ - **Naming taxonomy** — verb prefixes mean one thing each across the surface (`search_` / `find_` / `get_` / `list_` / `lookup_`); the same verb carrying different semantics on different tools is a finding.
209
+ - **Parameter vocabulary** — one name per concept everywhere: `query` vs `q`, `limit` vs `maxResults`, `nctId` vs `nct_id` on sibling tools is a finding.
210
+ - **Tool overlap** — for any pair with adjacent scope, the two descriptions alone must answer "when X vs Y." If an agent can't pick, that's material.
211
+ - **Instructions drift** — if the server sets `instructions`: every tool it names exists, workflow guidance reflects the current surface (new tools that belong in it, renamed or removed ones purged), and nothing contradicts a per-tool description.
212
+ - **Length outliers** — a description several times longer than its siblings (attention drag), or a one-liner that underspecifies (selection risk).
213
+
214
+ Cross-surface findings use the same finding format, cited at the file:line you'd change (the `instructions` string is a citable location).
215
+
216
+ ### 4. Report
179
217
 
180
218
  Three sections.
181
219
 
@@ -194,6 +232,8 @@ Issue: <one line: what's wrong>
194
232
  Fix: <one line: what to change to>
195
233
  ```
196
234
 
235
+ Excerpts are verbatim copy-paste from the file as read, line numbers from that read — re-verify any finding written from memory before it enters the report.
236
+
197
237
  Two-level severity:
198
238
 
199
239
  - **material** — affects agent decisions (will mis-select tool, mis-fill input, mis-handle output, swallow an irrecoverable error)
@@ -219,10 +259,11 @@ End with:
219
259
 
220
260
  - [ ] Scope confirmed (whole server / module / specific files)
221
261
  - [ ] Severity floor applied — nits suppressed if user requested
222
- - [ ] Inventory built — every `*.tool.ts`, `*.app-tool.ts`, `*.resource.ts`, `*.app-resource.ts`, `*.prompt.ts` listed
223
- - [ ] Each file walked through all 12 categories (per-file, not 12 separate passes)
262
+ - [ ] Inventory built — every `*.tool.ts`, `*.app-tool.ts`, `*.resource.ts`, `*.app-resource.ts`, `*.prompt.ts` listed; server `instructions` located if set
263
+ - [ ] Each file walked through all 15 categories (per-file, not 15 separate passes)
264
+ - [ ] Cross-surface pass run — naming taxonomy, parameter vocabulary, tool overlap, instructions drift, length outliers
224
265
  - [ ] **Read-only:** no git, no commits, no changelog edits, no `devcheck`, no wrapup invoked during the audit
225
- - [ ] Findings carry file:line citation, excerpt, issue, fix
266
+ - [ ] Findings carry file:line citation, excerpt, issue, fix — excerpts verbatim, line numbers verified
226
267
  - [ ] Report: summary → grouped-by-category findings → numbered options
227
- - [ ] Options section produced — numbered, one-per-file, severity tagged, cherry-pickable
268
+ - [ ] Options section produced — numbered, each single-file-scoped, severity tagged, cherry-pickable
228
269
  - [ ] If no findings: summary states "no findings"; Findings and Options sections omitted
@@ -14,7 +14,7 @@ COPY package.json bun.lock ./
14
14
  # Install all dependencies (including dev dependencies for building).
15
15
  # The BuildKit cache mount persists Bun's global package cache across builds.
16
16
  RUN --mount=type=cache,target=/root/.bun/install/cache \
17
- bun install --frozen-lockfile
17
+ bun install --frozen-lockfile --ignore-scripts
18
18
 
19
19
  # Copy the rest of the source code
20
20
  COPY . .
@@ -64,9 +64,9 @@
64
64
  "zod": "{{ZOD_VERSION}}"
65
65
  },
66
66
  "devDependencies": {
67
- "@biomejs/biome": "2.5.2",
67
+ "@biomejs/biome": "2.5.4",
68
68
  "@socketsecurity/bun-security-scanner": "^1.1.2",
69
- "@types/node": "26.1.0",
69
+ "@types/node": "26.1.1",
70
70
  "depcheck": "^1.4.7",
71
71
  "ignore": "^7.0.5",
72
72
  "tsc-alias": "^1.9.0",