carrick 0.3.53

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 (114) hide show
  1. package/LICENSE.md +99 -0
  2. package/README.md +79 -0
  3. package/bin/carrick.mjs +168 -0
  4. package/dist/channel.d.ts +16 -0
  5. package/dist/channel.js +37 -0
  6. package/dist/channel.js.map +1 -0
  7. package/dist/cli.d.ts +29 -0
  8. package/dist/cli.js +60 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/contract.d.ts +154 -0
  11. package/dist/contract.js +141 -0
  12. package/dist/contract.js.map +1 -0
  13. package/dist/diagnostics.d.ts +54 -0
  14. package/dist/diagnostics.js +199 -0
  15. package/dist/diagnostics.js.map +1 -0
  16. package/dist/hook/post-edit.d.ts +13 -0
  17. package/dist/hook/post-edit.js +76 -0
  18. package/dist/hook/post-edit.js.map +1 -0
  19. package/dist/hook/session-start.d.ts +2 -0
  20. package/dist/hook/session-start.js +44 -0
  21. package/dist/hook/session-start.js.map +1 -0
  22. package/dist/init/identity.d.ts +20 -0
  23. package/dist/init/identity.js +60 -0
  24. package/dist/init/identity.js.map +1 -0
  25. package/dist/init/repos.d.ts +19 -0
  26. package/dist/init/repos.js +46 -0
  27. package/dist/init/repos.js.map +1 -0
  28. package/dist/init/run.d.ts +11 -0
  29. package/dist/init/run.js +228 -0
  30. package/dist/init/run.js.map +1 -0
  31. package/dist/init/settings.d.ts +50 -0
  32. package/dist/init/settings.js +138 -0
  33. package/dist/init/settings.js.map +1 -0
  34. package/dist/log.d.ts +2 -0
  35. package/dist/log.js +35 -0
  36. package/dist/log.js.map +1 -0
  37. package/dist/native.d.ts +48 -0
  38. package/dist/native.js +127 -0
  39. package/dist/native.js.map +1 -0
  40. package/dist/render.d.ts +65 -0
  41. package/dist/render.js +334 -0
  42. package/dist/render.js.map +1 -0
  43. package/dist/root.d.ts +30 -0
  44. package/dist/root.js +82 -0
  45. package/dist/root.js.map +1 -0
  46. package/dist/server.d.ts +2 -0
  47. package/dist/server.js +255 -0
  48. package/dist/server.js.map +1 -0
  49. package/dist/templates.d.ts +18 -0
  50. package/dist/templates.js +66 -0
  51. package/dist/templates.js.map +1 -0
  52. package/package.json +72 -0
  53. package/plugin/.claude-plugin/plugin.json +6 -0
  54. package/plugin/.lsp.json +14 -0
  55. package/plugin/hooks/hooks.json +27 -0
  56. package/sidecar/dist/src/bundler.d.ts +141 -0
  57. package/sidecar/dist/src/bundler.js +680 -0
  58. package/sidecar/dist/src/capture/anchors.d.ts +61 -0
  59. package/sidecar/dist/src/capture/anchors.js +1132 -0
  60. package/sidecar/dist/src/capture/api.d.ts +378 -0
  61. package/sidecar/dist/src/capture/api.js +10 -0
  62. package/sidecar/dist/src/capture/augmentations.d.ts +20 -0
  63. package/sidecar/dist/src/capture/augmentations.js +60 -0
  64. package/sidecar/dist/src/capture/check-classify.d.ts +58 -0
  65. package/sidecar/dist/src/capture/check-classify.js +189 -0
  66. package/sidecar/dist/src/capture/check-deep.d.ts +32 -0
  67. package/sidecar/dist/src/capture/check-deep.js +91 -0
  68. package/sidecar/dist/src/capture/check-poison.d.ts +40 -0
  69. package/sidecar/dist/src/capture/check-poison.js +155 -0
  70. package/sidecar/dist/src/capture/check-probe.d.ts +70 -0
  71. package/sidecar/dist/src/capture/check-probe.js +147 -0
  72. package/sidecar/dist/src/capture/check-scrub.d.ts +31 -0
  73. package/sidecar/dist/src/capture/check-scrub.js +71 -0
  74. package/sidecar/dist/src/capture/check-workspace.d.ts +52 -0
  75. package/sidecar/dist/src/capture/check-workspace.js +218 -0
  76. package/sidecar/dist/src/capture/check.d.ts +39 -0
  77. package/sidecar/dist/src/capture/check.js +450 -0
  78. package/sidecar/dist/src/capture/deep-walk.d.ts +62 -0
  79. package/sidecar/dist/src/capture/deep-walk.js +243 -0
  80. package/sidecar/dist/src/capture/index.d.ts +36 -0
  81. package/sidecar/dist/src/capture/index.js +477 -0
  82. package/sidecar/dist/src/capture/lockfile.d.ts +48 -0
  83. package/sidecar/dist/src/capture/lockfile.js +490 -0
  84. package/sidecar/dist/src/capture/machinery.d.ts +59 -0
  85. package/sidecar/dist/src/capture/machinery.js +160 -0
  86. package/sidecar/dist/src/capture/node-builder.d.ts +37 -0
  87. package/sidecar/dist/src/capture/node-builder.js +123 -0
  88. package/sidecar/dist/src/capture/paths-rewrite.d.ts +34 -0
  89. package/sidecar/dist/src/capture/paths-rewrite.js +100 -0
  90. package/sidecar/dist/src/capture/self-check.d.ts +38 -0
  91. package/sidecar/dist/src/capture/self-check.js +317 -0
  92. package/sidecar/dist/src/capture/specifiers.d.ts +39 -0
  93. package/sidecar/dist/src/capture/specifiers.js +56 -0
  94. package/sidecar/dist/src/definition-resolver.d.ts +57 -0
  95. package/sidecar/dist/src/definition-resolver.js +153 -0
  96. package/sidecar/dist/src/index.d.ts +14 -0
  97. package/sidecar/dist/src/index.js +564 -0
  98. package/sidecar/dist/src/monorepo-builder.d.ts +129 -0
  99. package/sidecar/dist/src/monorepo-builder.js +584 -0
  100. package/sidecar/dist/src/project-loader.d.ts +130 -0
  101. package/sidecar/dist/src/project-loader.js +399 -0
  102. package/sidecar/dist/src/type-inferrer.d.ts +940 -0
  103. package/sidecar/dist/src/type-inferrer.js +3540 -0
  104. package/sidecar/dist/src/type-structural-expander.d.ts +61 -0
  105. package/sidecar/dist/src/type-structural-expander.js +283 -0
  106. package/sidecar/dist/src/type-text-canonicalizer.d.ts +40 -0
  107. package/sidecar/dist/src/type-text-canonicalizer.js +297 -0
  108. package/sidecar/dist/src/types.d.ts +669 -0
  109. package/sidecar/dist/src/types.js +5 -0
  110. package/sidecar/dist/src/validators.d.ts +2214 -0
  111. package/sidecar/dist/src/validators.js +336 -0
  112. package/sidecar/package.json +6 -0
  113. package/templates/carrick.json +7 -0
  114. package/templates/carrick.yml +45 -0
@@ -0,0 +1,940 @@
1
+ /**
2
+ * Type Inferrer - Scope-based type inference with payload unwrapping
3
+ *
4
+ * This module extracts types even when developers don't write explicit
5
+ * annotations. It uses span-based node lookup (no line windows) to target
6
+ * precise expressions provided by the Rust/LLM pipeline.
7
+ *
8
+ * Key feature: Agent-informed payload unwrapping
9
+ * - Extracts payload types from machinery wrappers (Response<T>, AxiosResponse<T>, etc.)
10
+ * - Supports union/intersection composition
11
+ * - Recursive unwrapping with depth limits
12
+ *
13
+ * Framework agnosticism: The LLM emits the payload subexpression directly (e.g.,
14
+ * the `users` in `res.json(users)`, `ctx.body = users`, `h.response(users)`, or
15
+ * `return users`). The sidecar resolves that node and reads its type — no
16
+ * framework-specific method-name lists live here. For payload-less handlers
17
+ * (redirects, 204s), the LLM emits null and we fall back to the containing
18
+ * function's return type.
19
+ */
20
+ import { Project } from 'ts-morph';
21
+ import type { InferRequestItem, InferResult, ExtractionConfig } from './types.js';
22
+ /**
23
+ * Strongly-discriminating member names of HTTP transport machinery — the
24
+ * fetch/DOM `Response` & `Request`, a Node `http.ServerResponse`, a framework
25
+ * reply object. A type declared in a lib or `node_modules` origin that carries
26
+ * a subset of these is framework machinery, never a user contract: the
27
+ * PRODUCER-side structural mirror of the consumer `machineryIndicators`
28
+ * (ExtractionConfig), used to reject a wrapper envelope whose response field is
29
+ * a raw `Response` (carrick#371) instead of emitting it as a comparable type.
30
+ *
31
+ * Framework-agnostic by construction: no framework NAME appears here, only the
32
+ * shared HTTP-message surface. The names are deliberately the ones no
33
+ * JSON payload ever carries (`ok`, `redirected`, `bodyUsed`, `arrayBuffer`,
34
+ * `writeHead`, ...), so the origin gate + `MACHINERY_INDICATOR_THRESHOLD` never
35
+ * fire on real data. Kept in lockstep with the capture-seam mirror
36
+ * `capture/machinery.ts` (the seam forbids sharing a module across it, same as
37
+ * `BUILTIN_ANCHOR_SYMBOLS` mirrors `socket_io.rs`). Exported so a drift-guard
38
+ * test (`machinery-indicator-mirror.test.ts`) asserts the two sets stay equal.
39
+ */
40
+ export declare const MACHINERY_MEMBER_INDICATORS: Set<string>;
41
+ /**
42
+ * Options for TypeInferrer construction
43
+ */
44
+ export interface TypeInferrerOptions {
45
+ /** The ts-morph Project instance */
46
+ project: Project;
47
+ }
48
+ /**
49
+ * TypeInferrer - Extracts types from source code, both explicit and inferred
50
+ *
51
+ * Usage:
52
+ * const inferrer = new TypeInferrer({ project });
53
+ * const result = inferrer.infer(requests, extractionConfig);
54
+ */
55
+ export declare class TypeInferrer {
56
+ private readonly project;
57
+ constructor(options: TypeInferrerOptions);
58
+ /**
59
+ * Infer types for the given requests
60
+ *
61
+ * @param requests - Array of inference requests
62
+ * @param extractionConfig - Agent-generated extraction config for payload unwrapping
63
+ * @returns InferResult with inferred types or errors
64
+ */
65
+ infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig): InferResult;
66
+ /**
67
+ * Infer a single type from a request
68
+ */
69
+ private inferSingle;
70
+ /**
71
+ * Get or add a source file to the project
72
+ */
73
+ private getSourceFile;
74
+ private inferFunctionReturn;
75
+ /**
76
+ * Build a `function_return` inferred type from an already-resolved function
77
+ * node: unwrap `Promise<…>` / async-iterator transport, apply the extraction
78
+ * config to the awaited type, and expand a bare named return structurally.
79
+ * Extracted from `inferFunctionReturn` so the route-registration path
80
+ * (`inferResponseBody` following a handler) can reuse the exact same logic
81
+ * against the handler function instead of the containing function.
82
+ */
83
+ private buildFunctionReturnInferredType;
84
+ /**
85
+ * Infer a function's return type for the signature hint. Unlike
86
+ * `inferFunctionReturn`, this does NOT unwrap Promise or apply wrapper
87
+ * rules — a function that returns `Promise<AuthResult>` should show exactly
88
+ * that in its signature. Used by the function-signature collection pass.
89
+ */
90
+ private inferSignatureReturn;
91
+ /**
92
+ * Infer the type of a single named parameter. `is_explicit` reflects whether
93
+ * the parameter carries a source annotation; the type string is the
94
+ * compiler's view either way (so contextually-typed callback params resolve
95
+ * even without an annotation). Uses ts-morph's default `getText()` form,
96
+ * which keeps named types as names and bounds depth via the compiler's own
97
+ * truncation.
98
+ */
99
+ private inferFunctionParam;
100
+ /**
101
+ * Resolve a `function_param` locator against a function's parameter list.
102
+ * Three shapes, tried in order:
103
+ *
104
+ * 1. A parameter whose name matches exactly (`(payload) => …` ← "payload").
105
+ * 2. A parameter whose DESTRUCTURED BINDING PATTERN matches the locator
106
+ * text under whitespace normalization (`({ time, run }) => …` ←
107
+ * "{ time, run }") — the handler destructures the payload itself, so the
108
+ * pattern's own type IS the payload type.
109
+ * 3. A named BINDING ELEMENT inside a destructured parameter
110
+ * (`({ payload }) => …` ← "payload") — the payload is one property of an
111
+ * envelope param, and the checker projects the element's type
112
+ * (catalog-worker handlers: `params: { id, payload: Infer<…> }`).
113
+ *
114
+ * All three read the type off a node the checker has already instantiated,
115
+ * so generic wrappers (topic-map emitters, schema catalogs, channel handles)
116
+ * resolve without any named payload symbol existing anywhere.
117
+ */
118
+ private resolveParamTarget;
119
+ private inferResponseBody;
120
+ private inferCallResult;
121
+ private inferVariable;
122
+ private inferExpression;
123
+ private inferRequestBody;
124
+ private resolveCallResultTerminalNode;
125
+ private extractBindingFromCall;
126
+ private extractBindingNames;
127
+ private getPrimaryBindingNode;
128
+ private collectDefUseNodes;
129
+ private expressionUsesNames;
130
+ private isIdentifierUsage;
131
+ private isInFunctionScope;
132
+ /**
133
+ * Resolve the awaited type: `Promise<X>` / `PromiseLike<X>` / `Awaited<X>` →
134
+ * `X` at the type level (recursively, so `Awaited<Promise<X>>` → `X`); any
135
+ * non-thenable type passes through UNCHANGED.
136
+ *
137
+ * ts-morph 25.0.1 does not expose the compiler's `getAwaitedType` on its
138
+ * `Type` wrapper (only `AwaitableNode.isAwaited()` on AST nodes, which is
139
+ * unrelated), so we resolve structurally on the symbol/alias name instead of
140
+ * gating on the literal `'Promise'` symbol. This generalizes past `Promise<T>`
141
+ * to `PromiseLike<T>` and the `Awaited<T>` utility type without over-unwrapping:
142
+ * a non-thenable like `AsyncGenerator<T>` is NOT awaitable and is returned
143
+ * unchanged, so the `unwrapAsyncIterableType` step that runs right after still
144
+ * sees (and peels) the iterator wrapper as before.
145
+ */
146
+ private unwrapPromiseType;
147
+ /**
148
+ * `AsyncGenerator<T, …>` / `AsyncIterableIterator<T>` / `AsyncIterator<T>` /
149
+ * `Generator<T, …>` / `IterableIterator<T>` → `T` (the yield type) at the type
150
+ * level; any other type passes through. A GraphQL subscription resolver written
151
+ * as `async function* x(): AsyncGenerator<Order>` carries its contract in the
152
+ * yield position, so the iterator wrapper must be peeled the same way Promise is
153
+ * before structural expansion — otherwise the response contract resolves to the
154
+ * library `AsyncGenerator<…>` machinery instead of the bare `Order`.
155
+ */
156
+ private unwrapAsyncIterableType;
157
+ /** The "leave the type as it is" result every bail-out path shares. */
158
+ private noUnwrap;
159
+ /**
160
+ * Unwrap a type using the agent-generated ExtractionConfig.
161
+ */
162
+ private unwrapTypeWithConfig;
163
+ /**
164
+ * Core unwrapping implementation with ExtractionConfig rules.
165
+ *
166
+ * Requirements:
167
+ * 1. Exact wrapperSymbols match extracts (gated on originModuleGlobs when
168
+ * the rule carries them — names like `Response` are shared by the DOM,
169
+ * frameworks, and HTTP clients)
170
+ * 2. machineryIndicators only trigger unwrap if originModuleGlobs also match
171
+ * 3. Handle unions and intersections
172
+ * 4. Support recursive unwrapping with depth limits
173
+ * 5. A rule that matches but extracts nothing never blocks later rules;
174
+ * only after every rule has run does an origin-verified match with no
175
+ * recoverable payload collapse to `unknown`
176
+ */
177
+ private unwrapType;
178
+ /**
179
+ * Try to unwrap a type using a single ExtractionRule.
180
+ */
181
+ private tryUnwrapWithRule;
182
+ /**
183
+ * Extract the payload type from a matched wrapper. Returns null when the
184
+ * rule matched the wrapper but no payload is recoverable from generics or
185
+ * property paths — the caller decides what a payload-less match means
186
+ * (verified machinery collapses to `unknown` after every rule has run;
187
+ * a name-only match leaves the type untouched).
188
+ */
189
+ private extractPayloadFromWrapper;
190
+ /**
191
+ * True when a producer RESPONSE type IS or CONTAINS framework transport
192
+ * machinery (a fetch/DOM `Response`, a Node `ServerResponse`, a reply object)
193
+ * — the artifact behind carrick#371, where a wrapped handler's literal return
194
+ * envelope `{ response: Response; error?: undefined } | { ...; error: Error }`
195
+ * was captured as the response contract. Machinery is never a comparable
196
+ * contract, so a response path that resolves here abstains (honest `unknown`)
197
+ * rather than emit a concrete-but-false type.
198
+ *
199
+ * DETECTS, exactly (see `typeIsOrContainsMachinery`):
200
+ * 1. the type itself is machinery (`typeIsFrameworkMachinery`);
201
+ * 2. a union/intersection member is machinery (the envelope union);
202
+ * 3. a DIRECT property's type is machinery, ONE level of descent only
203
+ * (`{ response: Response; error }`).
204
+ *
205
+ * DELIBERATELY NOT DETECTED — listed so this comment never overstates the
206
+ * guarantee (an overstated safety comment is what bit sibling PR #442). Each is
207
+ * a non-regression: it leaves the pre-existing verdict unchanged and never
208
+ * manufactures a new wrong one, tracked as a follow-up:
209
+ * - machinery nested deeper than one property level (a property whose type
210
+ * is itself a nested object wrapping the machinery);
211
+ * - a property typed `Promise<Response>` (the property type is NOT awaited /
212
+ * Promise-unwrapped before the machinery check);
213
+ * - an array element type: `Response[]` is not descended to its element;
214
+ * - `interface X extends Response` declared in USER source — the origin gate
215
+ * is lib/`node_modules` only, so a user-declared subtype reads as a real
216
+ * contract, not machinery;
217
+ * - a function / call-signature return type: the response paths that call
218
+ * this resolve a handler's RETURN (an envelope/object), never a function
219
+ * value, so no call-signature descent happens here. The capture-seam
220
+ * mirror `capture/machinery.ts` DOES descend call signatures — that is
221
+ * where the wrapper-FUNCTION type the Infer fallback resolves is caught.
222
+ *
223
+ * The origin gate in `typeIsFrameworkMachinery` keeps a user object whose
224
+ * fields merely share a member name from tripping.
225
+ */
226
+ private typeIsOrContainsResponseMachinery;
227
+ private typeIsOrContainsMachinery;
228
+ /**
229
+ * True when `type` itself is an HTTP-machinery type: it structurally carries
230
+ * at least `MACHINERY_INDICATOR_THRESHOLD` of the strongly-discriminating
231
+ * `MACHINERY_MEMBER_INDICATORS`, AND its symbol is declared in a lib
232
+ * (`lib.dom.d.ts`, ...) or `node_modules` origin. Both gates are required —
233
+ * the indicator subset alone essentially never matches a JSON payload, and
234
+ * the origin gate makes certain a user's own local type sharing those member
235
+ * names is never mistaken for framework machinery (the advisor's guard).
236
+ */
237
+ private typeIsFrameworkMachinery;
238
+ /**
239
+ * True once the type carries at least `MACHINERY_INDICATOR_THRESHOLD` DISTINCT
240
+ * `MACHINERY_MEMBER_INDICATORS` (own + apparent). Deduplicates by name (own and
241
+ * apparent property lists overlap) and early-returns the moment the threshold
242
+ * is reached — the callers only need the boolean, never the full count.
243
+ */
244
+ private hasMachineryIndicatorThreshold;
245
+ /**
246
+ * True when the symbol is declared in a TypeScript lib file (`lib.*.d.ts`) or
247
+ * under `node_modules` — i.e. framework/runtime machinery, not user source.
248
+ * Works on a bare checkout: the DOM `Response`/`Request` resolve from the
249
+ * bundled `lib.dom.d.ts` even with no installed dependencies.
250
+ */
251
+ private symbolIsLibOrExternalOrigin;
252
+ /**
253
+ * Check if a type has machinery indicator properties/methods.
254
+ */
255
+ private typeHasMachineryIndicators;
256
+ /**
257
+ * Check if a symbol's declarations originate from modules matching the globs.
258
+ */
259
+ private symbolOriginatesFromModules;
260
+ /**
261
+ * Simple glob matching for module paths.
262
+ * Supports: exact match, a trailing "*" wildcard, and "package/*" patterns.
263
+ *
264
+ * Matches are segment-bounded: the glob names a package (or package
265
+ * subpath) under node_modules, and the match must end at a path-segment
266
+ * boundary — `got` matches `node_modules/got/...` but never
267
+ * `node_modules/got-scraping/...`. This matters because the exact-symbol
268
+ * origin gate routes shared names like `Response` through here.
269
+ */
270
+ private filePathMatchesModuleGlob;
271
+ /**
272
+ * Check if a type string is "useless" for payload purposes.
273
+ */
274
+ private isUselessType;
275
+ private unwrapExpressionNode;
276
+ /**
277
+ * If `node` is a `JSON.stringify(arg)` call, return the (expression-unwrapped)
278
+ * first argument so its type is read instead of the call's `string` result.
279
+ * Otherwise return `node` unchanged. Any non-`JSON.stringify` call, or a
280
+ * `JSON.stringify()` with no argument, is left alone.
281
+ */
282
+ private unwrapJsonStringifyArg;
283
+ private extractExplicitTypeFromAncestor;
284
+ /**
285
+ * Render an explicit annotation (`as T`, `<T>`, or a typed binding) as
286
+ * fully-structural text.
287
+ *
288
+ * `typeNode.getText()` keeps a named type as its bare identifier
289
+ * (`OrderView`, `Promise<Payment>`). A bare name is fine inside the source
290
+ * project but becomes a dangling reference in the cross-repo `.d.ts` bundle,
291
+ * which carries only alias lines and no source declarations — it resolves to
292
+ * `any` and the comparison reads `unverifiable`. Resolving the annotation to
293
+ * its `Type`, stripping `Promise<…>` at the type level, and expanding the
294
+ * object structurally (shared with `definition-resolver.ts`) lands the real
295
+ * shape (`{ id: string; currency: string }`) in the bundle so the consumer
296
+ * can actually be compared.
297
+ *
298
+ * Falls back to the bare annotation text when the resolved type can't be
299
+ * expanded to a structural form (primitives, library types, unresolvable
300
+ * references), so a non-object annotation behaves exactly as before.
301
+ */
302
+ private expandAnnotationTypeNode;
303
+ /**
304
+ * Producer-side analogue of `expandAnnotationTypeNode` that works from a
305
+ * resolved `Type` rather than a syntactic annotation node.
306
+ *
307
+ * `inferResponseBody`/`inferFunctionReturn` resolve a payload to a named
308
+ * object type (e.g. `Payment`), then render it with `typeText`, which keeps
309
+ * the bare name. In the source-less cross-repo `.d.ts` bundle that name is a
310
+ * dangling `export type <alias> = Payment;` → resolves to `any` →
311
+ * `unverifiable` → `compat = None`. Expanding the resolved object structurally
312
+ * lands the real members (`{ id: string; … }`) in the bundle so the producer
313
+ * can be compared. Mirror of #257's consumer-side fix; keeps the bare text for
314
+ * primitives, library types, and anything the expander leaves by name.
315
+ *
316
+ * `fallback` is the already-computed type text (post Promise/wrapper unwrap),
317
+ * preserved verbatim when expansion does not inline an object.
318
+ */
319
+ private expandResolvedTypeStructural;
320
+ /**
321
+ * Recover a response contract from a handler's own `return` statements when
322
+ * the resolved return type carries none.
323
+ *
324
+ * A file-based route `loader`/`action` almost never annotates its return: the
325
+ * payload goes through a helper — `json(payload)`, `reply(payload, opts)`,
326
+ * `wrap(request, json(payload))` — and the helper's result is transport, not
327
+ * contract. Two ways that collapses today:
328
+ *
329
+ * * the helper is typed and returns machinery (`Promise<Response>`), so the
330
+ * carrick#371 guard abstains and the manifest reads `unknown`;
331
+ * * the helper comes from a package with no installed declaration (the
332
+ * scanner reads a bare checkout), so it resolves to `any` and the whole
333
+ * response reads `any`.
334
+ *
335
+ * In both, the contract is the helper's ARGUMENT — every framework that ships
336
+ * such a helper is generic over it. This walks the handler's returned
337
+ * expressions, descends through wrapper calls to the first argument that
338
+ * carries a payload, and unions the surviving branches. It is structural
339
+ * throughout: no helper, framework or package name is matched anywhere, and
340
+ * the recovery only runs where the existing path already had nothing.
341
+ *
342
+ * `statedOnly` is the guard for the unresolvable-callee case. A return type
343
+ * of `any` does not say the callee was a response helper, only that it could
344
+ * not be resolved, and on a bare checkout that is true of most imported
345
+ * callees: `return db.findMany({ where })` would otherwise report the query
346
+ * object as the endpoint's contract, which is a false contract and worse
347
+ * than `unknown`. With `statedOnly` set, only an argument the source
348
+ * annotates (`satisfies` / `as` / `<T>`) counts, because there the developer
349
+ * stated the contract. The machinery case leaves it clear: the callee is
350
+ * known transport, so its argument is the payload whatever its type.
351
+ *
352
+ * Returns `null` (a logged limitation, never a guess) when no returned
353
+ * expression yields an argument type this may read.
354
+ */
355
+ private recoverPayloadFromReturnStatements;
356
+ /**
357
+ * The expressions a function returns, excluding returns belonging to nested
358
+ * functions (a `.map(x => …)` callback returns a row, not the response).
359
+ * A concise arrow body is itself the returned expression.
360
+ */
361
+ private returnedExpressions;
362
+ /**
363
+ * The expressions that can carry this handler's response: its own returned
364
+ * expressions, plus the returned expressions of any function the handler
365
+ * hands DIRECTLY to a returned call (carrick#707).
366
+ *
367
+ * `return settle(...).then(ok => serialise(body), err => serialise(problem))`
368
+ * returns a call whose result is whatever the callbacks produce, so the
369
+ * callbacks' returns are this handler's returns as surely as its own. That
370
+ * shape is how a result-type API, a promise combinator or a matcher is
371
+ * written, and on a bare checkout the callee resolves to nothing, so the
372
+ * outer call's type says nothing at all.
373
+ *
374
+ * Only ONE level, and only from the returned call's own arguments. A
375
+ * callback nested deeper is inside an expression the handler computes, not
376
+ * an expression it returns — `return rows.map(r => …)` returns a row list,
377
+ * and reading the callback there would report a row as the response. Each
378
+ * flattened expression still has to pass the payload rule below, which no
379
+ * bare object literal does.
380
+ */
381
+ private responseReturnedExpressions;
382
+ /**
383
+ * The callees these returned expressions PROVE are response serialisers.
384
+ *
385
+ * A return type of `any` says the callee could not be resolved, not that it
386
+ * was a serialiser — the reason the unresolvable-callee case is otherwise
387
+ * restricted to arguments the source annotates. But a handler that writes
388
+ *
389
+ * return f(problem, { status: 401 });
390
+ * return f(payload);
391
+ *
392
+ * has stated what `f` is in its own source: a call that takes a body and,
393
+ * beside it, an HTTP status is a response serialiser, and nothing else is
394
+ * written that way. That is evidence the compiler does not need to resolve
395
+ * anything to see, so it survives the bare checkout CI scans, and it makes
396
+ * the second call's unannotated argument a payload rather than a guess.
397
+ *
398
+ * Three deliberate limits, each a negative test:
399
+ * - only RETURNED expressions count. A handler may well call a logger with
400
+ * `{ status: 500 }`; what it returns is what it serialises.
401
+ * - the status-stating argument must sit at index >= 1. A status in FIRST
402
+ * position is a field of the body (`{ status: 503, note }`), and reading
403
+ * it as evidence would promote every callee that takes a status field.
404
+ * - the evidence is per HANDLER, never cached across files. It says what
405
+ * this handler does, and that is all it is used for.
406
+ *
407
+ * Callees are keyed by resolved symbol where one exists — an import alias
408
+ * resolves locally even when its module does not — and by callee text
409
+ * otherwise.
410
+ */
411
+ private calleesProvenSerialiser;
412
+ /**
413
+ * Stable identity for a call's callee within one file: its resolved symbol
414
+ * when the compiler has one, else the callee's source text.
415
+ */
416
+ private calleeIdentity;
417
+ /**
418
+ * The payload argument of a returned response-helper call, or `undefined`.
419
+ *
420
+ * Walks the call's arguments in source order: the first whose type reads as a
421
+ * contract wins; an argument that is itself a call is descended into, which is
422
+ * what recovers `wrap(request, json(payload))` — the transport wrapper's own
423
+ * first argument is the request, which never reads as a contract.
424
+ *
425
+ * A call whose sibling options object states a >= 400 status is an error
426
+ * branch and contributes nothing: the response contract of an endpoint is the
427
+ * shape it returns when it succeeds.
428
+ *
429
+ * `statedOnly` narrows what counts as a payload to an argument the source
430
+ * annotates; see `recoverPayloadFromReturnStatements`. It is lifted for a
431
+ * callee the handler's own returned calls PROVE is a serialiser
432
+ * (`calleesProvenSerialiser`): there the source has stated what the callee
433
+ * is, so its argument is the payload even unannotated.
434
+ */
435
+ private responseHelperPayloadNode;
436
+ /**
437
+ * True when a node's type reads as a response payload.
438
+ *
439
+ * Object-shaped only (an object literal, a named interface, an array of
440
+ * either, or a union containing one). A bare primitive argument is a
441
+ * redirect location, a status code or a body string — `redirectTo("/next")`
442
+ * must never report `"/next"` as the endpoint's contract. A stated
443
+ * `satisfies`/`as` annotation counts even when its declaration is missing:
444
+ * the source says what the contract is.
445
+ *
446
+ * Under `statedOnly` the annotation is the ONLY thing that counts, so an
447
+ * unresolvable callee's arguments never become a contract by accident.
448
+ */
449
+ private nodeCarriesPayloadContract;
450
+ /** Object, array-of-object, or a union/intersection containing one. */
451
+ private typeIsObjectShaped;
452
+ /**
453
+ * The `satisfies X` / `as X` / `<X>` annotation node on an expression, when
454
+ * the source states its type. `satisfies` is the shape a route uses to claim
455
+ * a shared response contract without widening the literal.
456
+ */
457
+ private statedTypeNodeOf;
458
+ /**
459
+ * True when an argument states a >= 400 status: an options object carrying
460
+ * `status`/`statusCode`, or a bare status code (`send(body, 404)`).
461
+ *
462
+ * Read from the AST first: `{ status: 400 }` in an argument position widens
463
+ * to `{ status: number }`, so the literal only survives syntactically. The
464
+ * type check behind it catches `as const` and hoisted option objects.
465
+ */
466
+ private statesErrorStatus;
467
+ /**
468
+ * The HTTP status an argument states, or `undefined`.
469
+ *
470
+ * Read from the AST first: `{ status: 400 }` in an argument position widens
471
+ * to `{ status: number }`, so the literal only survives syntactically. The
472
+ * type check behind it catches `as const` and hoisted option objects. Only
473
+ * values in the HTTP range count — an arbitrary number named `status` on a
474
+ * domain object (`{ status: 2 }`) states nothing about transport.
475
+ */
476
+ private statedStatusCode;
477
+ /**
478
+ * Peel the wrappers that do not change an expression's payload: parentheses
479
+ * and `await`. Unlike `unwrapExpressionNode` this KEEPS `as`/`satisfies`,
480
+ * because the annotation is exactly what the recovery wants to read.
481
+ */
482
+ private peelTransparentExpression;
483
+ /**
484
+ * The deterministic source symbol of a resolved type (`Payment` for a payload
485
+ * typed `Payment`), or `undefined` when there is no single user-defined
486
+ * symbol to anchor on. This is the same `getSymbol() || getAliasSymbol()` name
487
+ * the socket anchor already derives (`socket_io.rs`), filtered through
488
+ * `BUILTIN_ANCHOR_SYMBOLS` so TS/lib globals (`Promise`, `Array`, `Date`,
489
+ * primitives, …) never become an anchor. Used to populate
490
+ * `primary_type_symbol` so the manifest anchor no longer depends on the LLM.
491
+ */
492
+ private primaryTypeSymbol;
493
+ /**
494
+ * Declaration file (absolute path) of the anchor symbol
495
+ * `primaryTypeSymbol` reports for this type, or `undefined` when the type
496
+ * has no user-facing anchor or no source declaration. The scanner's
497
+ * pub/sub two-anchor arbitration (carrick#413) uses this to re-aim a
498
+ * demoted explicit bundle request: the bundler resolves a `SymbolRequest`
499
+ * only against declarations IN its `source_file`, so the request must
500
+ * point at the file that actually declares the tsc-witnessed payload type.
501
+ *
502
+ * Only declaration kinds the bundler's `validateSymbols` can resolve
503
+ * (interface, type alias, class, enum, function, variable) count. A
504
+ * symbol's declaration list can also contain re-export machinery — a
505
+ * barrel's `ExportSpecifier` (`export { Foo } from './foo'`) points at a
506
+ * file that does not DECLARE the type, and a request re-aimed there would
507
+ * fail validation, turning a resolvable explicit type into `unknown`.
508
+ * With no declaring node, no source is reported and the arbitration
509
+ * fails closed to the explicit anchor.
510
+ */
511
+ private primaryTypeSymbolSource;
512
+ /**
513
+ * The anchor a `satisfies X` / `as X` / `<X>` annotation states IN SOURCE:
514
+ * the name written at the annotation, where its type is declared, and the
515
+ * array levels wrapped around it (`satisfies Order[]` → `Order`, depth 1).
516
+ *
517
+ * carrick#768: reading the anchor off the RESOLVED type works for an
518
+ * interface, whose type carries its own symbol, and fails for
519
+ *
520
+ * export type OrderBody = Inferred<typeof OrderSchema>;
521
+ *
522
+ * because the alias resolves to an instantiated type TypeScript keeps no
523
+ * alias symbol on, so `getSymbol()` answers the synthetic `__type`. The
524
+ * route then published a correct shape with no name, and
525
+ * `primary_type_symbol` is precisely how a reader gets from a route to the
526
+ * type its consumer imports. The annotation names it either way, so read
527
+ * the name from the AST and confirm it against the declaration it resolves
528
+ * to. This is the fallback for a resolved type that anchored nothing, never
529
+ * an override of one that did.
530
+ *
531
+ * Four things are rejected, so a name only anchors when it is genuinely the
532
+ * contract a consumer would import:
533
+ * - a generic instantiation (`satisfies Envelope<Order>`) names the
534
+ * WRAPPER, and a bare `Envelope` is not the payload;
535
+ * - a name resolving to no type declaration — an import of a value, a
536
+ * re-export the checker cannot follow — is not importable as a type;
537
+ * - a declaration in the TypeScript default library, and every
538
+ * `BUILTIN_ANCHOR_SYMBOLS` name, which describe machinery not contracts;
539
+ * - an inline annotation (`satisfies { id: string }`), which names nothing.
540
+ */
541
+ private writtenAnchorOf;
542
+ /**
543
+ * Peel array levels off a resolved type: `TimelineEvent[]` → element
544
+ * `TimelineEvent`, depth 1. An array type's own symbol is `Array` (builtin,
545
+ * filtered), so without this a `T[]` payload has NO anchor and — worse — an
546
+ * explicit anchor bundled for the same alias silently drops the array-ness
547
+ * (#306: array-vs-scalar scored compatible). The element drives the anchor
548
+ * symbol; the depth is reported on the `InferredType` so the bundler's
549
+ * existing `array_depth` wrap (#248) can restore the `[]` levels on the
550
+ * explicit bundle. Depth is capped at the bundler's sane ceiling; a deeper
551
+ * type is treated as depth 0 rather than a runaway loop.
552
+ */
553
+ private unwrapArrayLevels;
554
+ /**
555
+ * Resolve the target node using the best available locator:
556
+ * 1. If span_start + span_end present → findNodeAtSpan (SWC byte offsets)
557
+ * 2. If expression_text present → findNodeByText (Gemini text + line)
558
+ * 3. Otherwise → undefined
559
+ */
560
+ /**
561
+ * carrick#695: the type of the RECEIVER of a member call, plus the two facts
562
+ * a role decision needs about it — which package declares that type, and what
563
+ * the invoked member returns.
564
+ *
565
+ * Nothing here classifies. A member call with a route-shaped literal
566
+ * (`x.verb("/lit", arg)`) is a route registration or a request depending on
567
+ * what `x` IS, and that is the only question this answers. The Rust driver
568
+ * maps the declaring package onto the detected framework / data-fetcher
569
+ * lists; the compiler layer names no library and applies no shape rule.
570
+ *
571
+ * A receiver that does not resolve reports its top type (`any`) with no
572
+ * package, so the caller can tell "unresolved" from "declared here": on a
573
+ * checkout with no installed dependencies EVERY dependency-typed receiver is
574
+ * `any`, and treating that as workspace-owned would invent a role.
575
+ */
576
+ private inferReceiverType;
577
+ /**
578
+ * The npm package name that declares a type, read off its declaration's file
579
+ * path. `undefined` when the type has no declaration to read (a top type, a
580
+ * primitive, an anonymous object literal) or when its declaration is not
581
+ * under a `node_modules` tree — a type the workspace itself declares.
582
+ *
583
+ * The LAST `node_modules` segment wins, which is what a nested or
584
+ * content-addressed store (`node_modules/.store/pkg@1.0.0/node_modules/pkg`)
585
+ * requires. Scoped names keep both segments.
586
+ */
587
+ private declaringPackageOf;
588
+ /**
589
+ * The awaited return type of the member invoked on the receiver. Reported as
590
+ * a fact next to the receiver; a caller that classified on it alone would be
591
+ * back to a shape rule (any async helper taking a path would match).
592
+ */
593
+ private memberReturnTypeOf;
594
+ /**
595
+ * A type's own name when it has one, else its printed form.
596
+ *
597
+ * The printed form of a type whose declaring module the use site does not
598
+ * import is `import("/abs/path/to/module").Name` — an absolute path that
599
+ * would be persisted and rendered. The name alone is both stable and the
600
+ * part a reader (or a package-list lookup) uses; anonymous types, top types
601
+ * and primitives have no name and keep their printed text.
602
+ */
603
+ private namedTypeLabel;
604
+ /**
605
+ * A type's own name, or `an unnamed branch` when it has none.
606
+ *
607
+ * Unlike `namedTypeLabel` this never falls back to the printed form, because
608
+ * its output reaches a reader as a provenance `detail`, and the printed form
609
+ * of a type whose declaring module the use site does not import is
610
+ * `import("/abs/path").Name`. The bar for a detail is "one scrubbed sentence,
611
+ * never an absolute path", so an anonymous branch is described rather than
612
+ * printed.
613
+ */
614
+ private pathFreeTypeLabel;
615
+ private resolveTargetNode;
616
+ /**
617
+ * Resolve to a CallExpression using the best available locator.
618
+ */
619
+ private resolveTargetCallExpression;
620
+ /**
621
+ * Resolve to a containing function using the best available locator.
622
+ */
623
+ private resolveContainingFunction;
624
+ /**
625
+ * Find the function whose declaration starts at (or within a couple of lines
626
+ * of) the given line. Used for signature inference, where the only locator is
627
+ * the function's start line as recorded by the scanner. Ties break toward the
628
+ * innermost (smallest) function. Returns undefined if nothing is close enough,
629
+ * to avoid binding to an unrelated function.
630
+ *
631
+ * The tolerance is one-directional in effect (carrick#766). Looking BACK is
632
+ * free: a function starting before the anchor is one the anchor sits inside
633
+ * or just after, which is what an anchor recorded on a signature or body line
634
+ * means. Looking FORWARD is how the anchor reaches a handler that starts a
635
+ * line or two into the registration or binding it names — and it is also how,
636
+ * unguarded, an anchor landing on a statement that declares no function
637
+ * (`export { action, loader };`) was answered by the NEXT declaration in the
638
+ * file. That answer is silent: nothing about it says it came from a
639
+ * neighbouring helper.
640
+ *
641
+ * So a forward candidate must be one the anchor line's own statement leads
642
+ * to: rejected when a statement begins at or after the anchor line and does
643
+ * not contain it. Nothing but trivia may sit between the anchor and the
644
+ * function it is taken to name. With no candidate left the caller abstains,
645
+ * which is the honest answer and the one a consumer check can act on.
646
+ */
647
+ private findFunctionByLine;
648
+ /**
649
+ * Walk up from a node to find its innermost containing function.
650
+ */
651
+ private findContainingFunctionForNode;
652
+ /**
653
+ * Follow a LINE-ONLY route-registration anchor to its handler function. The
654
+ * scanner sends a bare line number (no span/text) for a named-handler
655
+ * registration whose handler is declared away from the registration line
656
+ * (`InferLocator::Line`). Find a registration node — a call or object literal
657
+ * — that STARTS on that line and whose handler resolves, and return it.
658
+ * Structural (call with a function-typed arg, or handler-shaped object-literal
659
+ * property), no framework name-lists. Used only when the primary locator did
660
+ * not resolve a node, so no existing path changes.
661
+ */
662
+ private handlerAtLine;
663
+ /**
664
+ * The route registration on `line` together with the handler it references.
665
+ *
666
+ * `handlerAtLine` returns only the handler, but the route's DECLARED contract
667
+ * (a validation-schema object passed alongside the handler) lives on the
668
+ * registration node itself, so the schema anchors need both halves.
669
+ */
670
+ private registrationAtLine;
671
+ /**
672
+ * Follow a route-registration locator ONE indirection to the handler function.
673
+ *
674
+ * The scanner points the infer request at the registration site, but the
675
+ * type-bearing expression (the handler's return, or its first request read)
676
+ * lives inside the handler function, which the registration only *references*.
677
+ * This resolves that reference to the handler's `FunctionLike` node, purely
678
+ * structurally, in three shapes:
679
+ *
680
+ * 1. INLINE — the node is (or is inside) a call whose arguments include an
681
+ * arrow/function expression: `app.post('/x', async (c) => { … })`. The
682
+ * inline function IS the handler.
683
+ * 2. OBJECT-LITERAL HANDLER PROPERTY — the node is (or is inside) an object
684
+ * literal with a `handler`-like property whose value is either an inline
685
+ * function or an identifier bound to a function:
686
+ * `{ method, path, handler: healthCheckHandler }`. Resolve that value.
687
+ * 3. IDENTIFIER ARGUMENT — the node is (or is inside) a call whose 2nd+
688
+ * argument is an identifier bound to a function:
689
+ * `app.get('/x', healthCheckHandler)`. Resolve that identifier.
690
+ *
691
+ * No framework name, method name, or property name beyond the generic
692
+ * `handler`-shaped key is hardcoded — the match is on node SHAPE (call with a
693
+ * function-typed arg / object literal with a function-valued property), which
694
+ * is what "route registration" structurally is across Express, Fastify, Hono,
695
+ * Koa, a hand-rolled registry array, etc. Returns undefined when the locator
696
+ * is not a registration shape, so every existing non-registration path is
697
+ * untouched.
698
+ */
699
+ private resolveRegisteredHandler;
700
+ /**
701
+ * Request-path gate for handler-following: return the handler function ONLY
702
+ * when the located node is ITSELF (after expression-unwrap) the registration
703
+ * — a call whose arguments include a handler function, or a route-registry
704
+ * object literal with a handler property. Unlike `resolveRegisteredHandler`,
705
+ * this does NOT walk up to an ancestor call, so a consumer's inner
706
+ * `JSON.stringify(payload)` (nested inside a `fetch(...)` that may carry other
707
+ * callbacks) is never mistaken for a registration and the existing consumer
708
+ * request paths stay exactly as they were.
709
+ */
710
+ private registrationHandlerAt;
711
+ /**
712
+ * The handler function referenced by a route-registration call: the first
713
+ * argument that is an inline function, or an identifier bound to a function
714
+ * (typically the 2nd+ arg — the path literal is not function-valued, so it is
715
+ * skipped naturally). No callee-name check: a call whose argument resolves to
716
+ * a function is structurally a registration regardless of the framework.
717
+ */
718
+ private handlerFromRegistrationCall;
719
+ /**
720
+ * The handler function carried by a route-registry object literal. Matches a
721
+ * property whose name is `handler`-shaped (case-insensitive `handler`) and
722
+ * whose value is an inline function or an identifier bound to a function.
723
+ */
724
+ private handlerFromObjectLiteral;
725
+ /**
726
+ * Resolve a node to a handler `FunctionLike`: an inline arrow/function
727
+ * expression is returned directly; an identifier is followed to its binding
728
+ * declaration (via the compiler's definition nodes) and returned when that
729
+ * declaration is — or initializes to — a function. Anything else (a path
730
+ * literal, an object, a non-function binding) yields undefined.
731
+ */
732
+ private asHandlerFunction;
733
+ /**
734
+ * Extract a `FunctionLike` from a declaration node the compiler resolved an
735
+ * identifier to: a function declaration is itself the function; a variable
736
+ * declaration / binding whose initializer is an inline function yields that
737
+ * function (`const h = async () => { … }`). Import/re-export shims are walked
738
+ * by ts-morph's `getDefinitionNodes`, so no manual import chasing is needed.
739
+ */
740
+ private functionFromDeclaration;
741
+ /**
742
+ * Scan a handler function body for the FIRST request-body read that carries a
743
+ * type, and return that type's structural text — or null when the body holds
744
+ * no typed request read.
745
+ *
746
+ * "Typed request read" is matched on SHAPE, never on a callee/method name:
747
+ *
748
+ * A. a CALL that carries an explicit type argument — `c.req.json<T>()`,
749
+ * `parseBody<T>(req)`; the first type argument is `T`; or
750
+ * B. an expression with an explicit type annotation or cast in its immediate
751
+ * binding context — `const b: T = …`, `req.body as T`.
752
+ *
753
+ * The body is walked in source order (`forEachDescendant` is a pre-order
754
+ * traversal), so the FIRST such read wins, mirroring "the request type lives
755
+ * at the first body read" without assuming which framework produced it.
756
+ * Returns null (not a spurious type) for a genuinely payload-less handler.
757
+ */
758
+ private inferRequestReadFromHandler;
759
+ /**
760
+ * Resolve a type-annotation/type-argument node to fully-structural text,
761
+ * dropping any `Promise<…>` wrapper (`c.req.json<T>()` returns `Promise<T>`,
762
+ * but the annotation node is `T` directly; the guard is harmless either way).
763
+ * Returns null when the node resolves to a useless/library type that carries
764
+ * no member shape, so the caller keeps scanning rather than locking onto
765
+ * `any`/`unknown`.
766
+ */
767
+ private structuralTextFromTypeNode;
768
+ /**
769
+ * Same reduction as `structuralTextFromTypeNode`, but starting from a resolved
770
+ * `Type` rather than a syntax node. `at` only supplies the scope the type is
771
+ * rendered in.
772
+ */
773
+ private structuralTextFromType;
774
+ /**
775
+ * The route registration a request's locator points AT, or undefined when it
776
+ * points at anything else.
777
+ *
778
+ * Deliberately strict: the located node must itself be the registration (a
779
+ * call registering a handler, or a route-registry object literal), never an
780
+ * ancestor of it, so an expression inside a handler is not mistaken for the
781
+ * route it belongs to. A locator that resolves nothing falls back to the
782
+ * registration on the request's line.
783
+ */
784
+ private routeRegistrationForRequest;
785
+ /**
786
+ * Anchor (b) on the response side: the success-status contract declared in the
787
+ * registration's schema, as an `InferredType` ready to return. Null when the
788
+ * registration declares no resolvable response schema, so the caller falls
789
+ * back to following the handler's return.
790
+ */
791
+ private declaredResponseInferredType;
792
+ /**
793
+ * The request contract of a route registration, in anchor order: the
794
+ * handler's parameter annotation (a), the registration's declared schema (b),
795
+ * then the first typed request read in the handler body (the pre-existing
796
+ * behaviour). The first anchor yielding a non-useless type wins; null means
797
+ * the route declares its request nowhere we can read.
798
+ */
799
+ private requestContractFromRegistration;
800
+ /**
801
+ * Anchor (a): the request contract declared on the handler's own signature.
802
+ *
803
+ * A typed request parameter (`request: CreateWidgetRequest` where that alias
804
+ * resolves to a request type parameterised with the body shape) exposes the
805
+ * contract as the `body` member of the parameter's type. Parameters are
806
+ * scanned in order and the first one carrying a non-useless `body` member
807
+ * wins; a reply/response parameter has no `body` member, and an unparameterised
808
+ * request type resolves `body` to `unknown`/`any`, which the useless-type
809
+ * guard rejects. So a handler that declares nothing yields null and the next
810
+ * anchor runs.
811
+ */
812
+ private requestBodyFromHandlerParams;
813
+ /**
814
+ * Anchor (b): the contract declared in the route's validation schema.
815
+ *
816
+ * `part` is `'body'` for the request contract and `'response'` for the
817
+ * response contract; a `response` entry keyed by status code resolves to its
818
+ * success entry. Returns null when the registration carries no schema, when
819
+ * the entry references something whose parsed output cannot be resolved, or
820
+ * when the schema is a plain JSON-schema literal (whose own object type is
821
+ * the JSON-Schema document, not the payload — emitting that would be worse
822
+ * than abstaining).
823
+ */
824
+ private routeSchemaContractText;
825
+ /**
826
+ * The `schema` object literal carried by a route registration: scan the
827
+ * registration call's arguments (or the registry object literal itself) for a
828
+ * `schema` property whose value is an object literal.
829
+ */
830
+ private routeSchemaObject;
831
+ /**
832
+ * The success entry of a status-keyed response map (`{ 200: …, 4xx: … }`):
833
+ * exact `200` when present, else the lowest 2xx key. Returns undefined when
834
+ * the value is not a status-keyed map, so the caller reads it as the schema
835
+ * itself.
836
+ */
837
+ private successStatusEntry;
838
+ /**
839
+ * The payload type a schema entry declares.
840
+ *
841
+ * The entry is either a REFERENCE call — `ref('CreateWidget')`, a
842
+ * name-to-schema indirection whose registry is the argument the ref function
843
+ * was built from — or the schema value itself. Either way the payload is the
844
+ * schema's parsed output: the return type of its `parse` method (the shape
845
+ * every schema value exposes as its public validate-and-return API), falling
846
+ * back to a declared `_output` member. A value with neither is not a schema
847
+ * and yields null.
848
+ */
849
+ private schemaOutputTypeText;
850
+ /**
851
+ * Follow a schema REFERENCE call (`ref('CreateWidget')`) to the schema value
852
+ * it names.
853
+ *
854
+ * The ref function is produced by a registry-building call — `const { $ref } =
855
+ * build({ CreateWidget, Widget })` — so the registry is that call's first
856
+ * argument, and the referenced key is a property of it. Resolve the callee to
857
+ * its declaration, walk to the variable declaration it is bound in, and read
858
+ * the key off the builder argument's type. Returns undefined for anything
859
+ * that is not this shape.
860
+ */
861
+ private registrySchemaType;
862
+ /**
863
+ * The parsed output type of a schema value: the return type of its `parse`
864
+ * method, else a declared `_output` member. Returns undefined when neither
865
+ * carries a usable type — including when the schema library's own types are
866
+ * unavailable (an uninstalled dependency resolves the schema to `any`), which
867
+ * must abstain rather than publish `any` as a contract.
868
+ */
869
+ private schemaOutputType;
870
+ /**
871
+ * Find a node by matching expression text near a target line.
872
+ *
873
+ * Strategy:
874
+ * 1. Get all descendant nodes within [lineNumber - searchRadius, lineNumber + searchRadius]
875
+ * 2. Normalize whitespace for comparison
876
+ * 3. Try exact match first (after normalization), prefer closest to target line
877
+ * 4. Fall back to substring match (LLM text in node text, or vice versa)
878
+ * 5. Return smallest matching node closest to target line
879
+ */
880
+ private findNodeByText;
881
+ /**
882
+ * Find a CallExpression by matching expression text near a target line.
883
+ */
884
+ private findCallExpressionByText;
885
+ /**
886
+ * Shared text-matching logic for node resolution.
887
+ * Normalizes whitespace once per candidate, then tries exact match,
888
+ * then substring match (preferring containing matches).
889
+ */
890
+ private matchByText;
891
+ /**
892
+ * Pick the best match from a set of candidate nodes:
893
+ * smallest range, then closest to target line.
894
+ */
895
+ private pickBestMatch;
896
+ /**
897
+ * Normalize whitespace for text comparison:
898
+ * collapse runs of whitespace into single spaces, trim.
899
+ *
900
+ * Also strips trailing commas before `}` `)` `]`: multi-line source
901
+ * literals carry them but the LLM's single-line locator print does not,
902
+ * and that one comma used to defeat exact AND containment matching for
903
+ * the payload and every enclosing node (#335). Applied symmetrically to
904
+ * node text and target, so both sides compare equal.
905
+ *
906
+ * Also strips the space left AFTER `(` `[` `{` by the collapse: a
907
+ * multi-line call whose arguments start on the next line normalizes to
908
+ * `f( x)` while the LLM's compact print is `f(x)`, and that one space
909
+ * defeated exact matching for the call and every enclosing node — the
910
+ * opening-delimiter mirror of the #335 trailing comma (#336). Symmetric
911
+ * for the same reason.
912
+ */
913
+ private normalizeWhitespace;
914
+ /**
915
+ * Format a human-readable location string for error messages.
916
+ */
917
+ private formatRequestLocation;
918
+ private findContainingFunctionBySpan;
919
+ private findNodeAtSpan;
920
+ private findCallExpressionAtSpan;
921
+ private unwrapPromise;
922
+ /**
923
+ * Unwrap a single `Promise<...>` / `PromiseLike<...>` type string, only when
924
+ * the inner text is bracket-balanced (so `Promise<A> | B` is left alone for
925
+ * the caller's union handling rather than mangled).
926
+ */
927
+ private unwrapPromiseText;
928
+ /**
929
+ * Split a type string on `|` at bracket depth 0. `=>` is not treated as a
930
+ * closing bracket.
931
+ */
932
+ private splitTopLevelUnion;
933
+ private isBracketBalanced;
934
+ private getNodeLocation;
935
+ private createInferredType;
936
+ private generateAlias;
937
+ private inferKindSuffix;
938
+ private log;
939
+ private logError;
940
+ }