@suss/extractor 0.3.1 → 0.4.0

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.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,260 @@
1
- import { Effect, MessageBusSemantics, DeployableUnit, Predicate, TypeShape, RenderNode, CodeUnitKind, BoundaryBinding, BehavioralSummary, ConfidenceInfo, Transition, Gap, Input, Output } from '@suss/behavioral-ir';
1
+ import { Effect, MessageBusSemantics, DeployableUnit, Predicate, BehavioralSummary, TypeShape, RenderNode, CodeUnitKind, BoundaryBinding, GraphqlDeclaredContract, ConfidenceInfo, Transition, Gap, Input, Output } from '@suss/behavioral-ir';
2
+
3
+ /**
4
+ * The language-neutral form the path engine walks: statement kind, condition
5
+ * handle, children, and exit kind. Each language's adapter lowers its own AST
6
+ * into this, and the path enumeration never touches a language-specific node
7
+ * again.
8
+ *
9
+ * The `Cond` type parameter is the language's own handle for a condition
10
+ * expression (a ts-morph Expression for TypeScript, a tree-sitter node for a
11
+ * language added later). The engine passes it through untouched for a caller
12
+ * to parse afterwards, and never looks inside it.
13
+ */
14
+ /** Where a condition came from, in terms of what that branch's body does. */
15
+ type ConditionSource = "explicit" | "earlyReturn" | "earlyThrow" | "catchBlock";
16
+ /** Whether a statement's own subtree leaves the unit by returning or throwing. A throw anywhere beats a return anywhere. Never set for break or continue. */
17
+ type ExitKind = "return" | "throw" | null;
18
+ /**
19
+ * A test the engine passes through without looking inside: its display text
20
+ * plus the language's own expression handle. The handle is null for a
21
+ * synthetic condition, such as a loop's "some iteration of" marker or a
22
+ * switch group's disjunction text.
23
+ */
24
+ interface ConditionHandle<Cond> {
25
+ readonly sourceText: string;
26
+ readonly expression: Cond | null;
27
+ }
28
+ /** One condition attached to a caller-visible transition. */
29
+ interface ConditionInfo<Cond> {
30
+ readonly sourceText: string;
31
+ readonly polarity: "positive" | "negative";
32
+ readonly source: ConditionSource;
33
+ readonly expression: Cond | null;
34
+ }
35
+ /** A statement list: an if arm, a loop body, a try/catch/finally block. */
36
+ type StatementBlock<Cond> = readonly StructuredStatement<Cond>[];
37
+ /**
38
+ * One switch or match case group, already merged out of the language's own
39
+ * grammar (TypeScript stacks several empty-bodied labels on top of the
40
+ * clause that finally has a body, and the lowering step folds that
41
+ * into one group with a joined condition). `condition` is null for the
42
+ * default or wildcard group. `body` has any language-specific
43
+ * statement that ends a fallthrough (a trailing break) already stripped
44
+ * out, and `hasTrailingBreak` records whether one was there.
45
+ */
46
+ interface CaseGroup<Cond> {
47
+ readonly condition: ConditionHandle<Cond> | null;
48
+ readonly hasTrailingBreak: boolean;
49
+ readonly body: StatementBlock<Cond>;
50
+ }
51
+ /**
52
+ * One statement in the unit's control flow, already lowered out of the
53
+ * source language. Every variant has the same four things on it:
54
+ * `kind` says which construct it is, `condition` (where
55
+ * one applies) is the opaque test, the block and group fields are its
56
+ * children, and `exitKind` is worked out by the lowering step (a deep
57
+ * scan of the statement's own subtree for a return or throw, skipping
58
+ * nested function bodies, the same scan every language needs in order to
59
+ * tell an early return from an early throw).
60
+ */
61
+ type StructuredStatement<Cond = unknown> = {
62
+ readonly kind: "if";
63
+ readonly condition: ConditionHandle<Cond>;
64
+ readonly thenBody: StatementBlock<Cond>;
65
+ /** null when the source has no else/elif tail at all. */
66
+ readonly elseBody: StatementBlock<Cond> | null;
67
+ readonly exitKind: ExitKind;
68
+ } | {
69
+ readonly kind: "switch";
70
+ /** Source order; at most one group has a null (default) condition. */
71
+ readonly groups: readonly CaseGroup<Cond>[];
72
+ readonly exitKind: ExitKind;
73
+ } | {
74
+ readonly kind: "loop";
75
+ /** Display text for the loop header. The engine builds two synthetic conditions out of it: "some iteration of: ..." and "loop exited via ...: ...". */
76
+ readonly condition: ConditionHandle<Cond>;
77
+ readonly body: StatementBlock<Cond>;
78
+ readonly exitKind: ExitKind;
79
+ } | {
80
+ readonly kind: "try";
81
+ readonly tryBody: StatementBlock<Cond>;
82
+ readonly catchBody: StatementBlock<Cond> | null;
83
+ /** Here only so it can be validated. A finally that exits, or that contains a terminal the caller gave us, is not modeled, and its own conditions are never enumerated. */
84
+ readonly finallyBody: StatementBlock<Cond> | null;
85
+ readonly exitKind: ExitKind;
86
+ } | {
87
+ readonly kind: "exit";
88
+ readonly exit: "return" | "throw" | "break" | "continue";
89
+ readonly exitKind: ExitKind;
90
+ } | {
91
+ /** Anything else: expression statements, declarations, and any statement the enumeration does not branch on. */
92
+ readonly kind: "opaque";
93
+ readonly exitKind: ExitKind;
94
+ };
95
+
96
+ /**
97
+ * `Reading<T>`: what a reader found, in the four states a source can leave a
98
+ * value in.
99
+ *
100
+ * A reader that returns `T | null` makes null mean three things at once: the
101
+ * source left the value out, the source states it and the reader could not
102
+ * evaluate it, or several candidates matched and the reader picked none.
103
+ * Each needs something different said about it on the summary, and the type
104
+ * does not show which the reader meant, so every new reader decides it again
105
+ * and some of them decide wrong.
106
+ *
107
+ * The rule that turns a reading into a claim, a library default, or a gap
108
+ * lives in the summary builder in this package and is not exported.
109
+ */
110
+ /** Where in a file a value is written, as byte offsets into that file. */
111
+ interface SourceRange {
112
+ start: number;
113
+ end: number;
114
+ }
115
+ /**
116
+ * What a reader found where a value could be. `written` is the only state
117
+ * with a value a summary may claim, and the other three each say something
118
+ * different about why there is no claim to make.
119
+ */
120
+ type Reading<T> = {
121
+ kind: "written";
122
+ value: T;
123
+ range: SourceRange;
124
+ } | {
125
+ kind: "absent";
126
+ } | {
127
+ kind: "unreadable";
128
+ reason: string;
129
+ range: SourceRange;
130
+ } | {
131
+ kind: "ambiguous";
132
+ candidates: readonly T[];
133
+ reason: string;
134
+ range: SourceRange;
135
+ };
136
+ /** The source gives this value, here, and the reader read it. */
137
+ declare function writtenReading<T>(value: T, range: SourceRange): Reading<T>;
138
+ /**
139
+ * The source says nothing where this value could be. A library that defines
140
+ * a default for it may still supply one, and the summary builder applies
141
+ * that default only when a pack declares it as data.
142
+ */
143
+ declare const absentReading: Reading<never>;
144
+ /**
145
+ * The source gives the value and the reader could not evaluate it. The
146
+ * reason is the sentence somebody reading the summary will see, so write it
147
+ * about what could not be read rather than about the code.
148
+ */
149
+ declare function unreadableReading<T>(reason: string, range: SourceRange): Reading<T>;
150
+ /**
151
+ * Several values could be right and the reader picked none. Keeping the
152
+ * candidates leaves what was found available to whoever later teaches the
153
+ * reader how to choose, and every step afterwards keeps them too.
154
+ *
155
+ * The range is where somebody should look to see the ambiguity. An ambiguity
156
+ * often spans more than one place (two mounts of one router, in two files),
157
+ * so give the site the reading was taken at, the same one an `unreadable`
158
+ * reading here would have given.
159
+ */
160
+ declare function ambiguousReading<T>(candidates: readonly T[], reason: string, range: SourceRange): Reading<T>;
161
+ /**
162
+ * A reading paired with what the library does when the source says nothing.
163
+ * Only a pack may supply that default, so the value a summary claims for an
164
+ * unstated field is library-defined and lives in the pack alongside
165
+ * everything else the pack already declares.
166
+ */
167
+ interface DefaultedReading<T> {
168
+ /** What the source said. */
169
+ reading: Reading<T>;
170
+ /**
171
+ * The value the library applies when the source gives none, as the pack
172
+ * declared it. Leave it out when the library defines no default, and then
173
+ * a source that says nothing gets no claim.
174
+ */
175
+ libraryDefault?: T;
176
+ }
177
+ /**
178
+ * The same reading with its value in another form. A written value goes
179
+ * through `f` and keeps its range. An ambiguous reading's candidates go
180
+ * through `f` too, so the alternatives end up in the same form as the
181
+ * chosen value would. Absent and unreadable readings pass through
182
+ * unchanged, since neither one has a value to convert.
183
+ */
184
+ declare function mapReading<T, U>(reading: Reading<T>, f: (value: T) => U): Reading<U>;
185
+ /**
186
+ * Read further from what this reading found. `f` runs on a written
187
+ * value with the range it was written at, so a step that turns out to
188
+ * be unreadable can say so against the same syntax.
189
+ *
190
+ * `f` runs on each of an ambiguous reading's candidates too, and the ones
191
+ * that do read come back as the candidates of an ambiguous reading with the
192
+ * same reason. That way the alternatives stay next to the chosen value all
193
+ * the way to the summary, instead of being dropped at the first step
194
+ * that reads further.
195
+ */
196
+ declare function andThenReading<T, U>(reading: Reading<T>, f: (value: T, range: SourceRange) => Reading<U>): Reading<U>;
197
+ /**
198
+ * Which of several readings a claim comes from, for a value a library lets
199
+ * a source give in more than one place.
200
+ */
201
+ interface ChosenReading<T> {
202
+ /**
203
+ * The reading a claim comes from: the first that was written, or if none
204
+ * was, the first that failed to read, or absent when all of them were
205
+ * absent.
206
+ */
207
+ reading: Reading<T>;
208
+ /**
209
+ * The readings the choice passed over that nobody could resolve. A later
210
+ * reading supplying the value does not settle what an earlier one said
211
+ * and could not be read, so these still go to the
212
+ * builder and their reasons still reach the summary.
213
+ */
214
+ passedOver: readonly Reading<T>[];
215
+ }
216
+ /**
217
+ * Choose among readings of the same value, taking the first that was
218
+ * written. Nothing is thrown away: whatever the choice passed over and
219
+ * could not read comes back next to it in `passedOver`, so a
220
+ * `response_model` nobody could read still lands as a gap even when the
221
+ * return annotation after it supplies the type.
222
+ */
223
+ declare function firstWrittenReading<T>(readings: readonly Reading<T>[]): ChosenReading<T>;
224
+ /**
225
+ * What a written reading found, for a reader that has to read further from
226
+ * it before anything is claimed. A route's path template gives the
227
+ * parameters that decide what each of the handler's parameters is, and
228
+ * that has to be settled before there is a summary field to fill in.
229
+ *
230
+ * This applies no default and gives no reason, so most of what a summary
231
+ * says must not be written from it. Hand the reading to the
232
+ * builder instead, and the fixed rule gets applied to it once, somewhere
233
+ * review can see it.
234
+ *
235
+ * The identity fields of a boundary binding are the exception, and the path
236
+ * this reads is one of them. A binding either says where a unit is or says
237
+ * nothing and pairs with nothing, and no pack declares a
238
+ * default for what a boundary is called, so the value the builder would
239
+ * put there is the value this gives back. Hand the reading over as well and
240
+ * the reason still becomes a gap.
241
+ */
242
+ declare function valueToReadFurtherFrom<T>(reading: Reading<T>): T | null;
243
+
244
+ /**
245
+ * The `PatternPack` interface, which is what a framework pack gives a language
246
+ * adapter. The pack says WHAT to look for (which import, which call, which
247
+ * decorator); the adapter knows HOW to find that in its language's AST.
248
+ *
249
+ * Everything here is data, never code. A pack describes a library once and any
250
+ * adapter that understands these patterns can apply it. If you want a pack to
251
+ * compute something, the answer is usually another declarative field here.
252
+ *
253
+ * The sections run in the order an adapter uses them: discovery finds candidate
254
+ * code units, terminals describe how a unit finishes, contract reading and
255
+ * input mapping describe what it declares and takes in, and response property
256
+ * semantics say which property is the body and which is the status.
257
+ */
2
258
 
3
259
  type DiscoveryMatch = {
4
260
  type: "namedExport";
@@ -20,7 +276,7 @@ type DiscoveryMatch = {
20
276
  type: "clientCall";
21
277
  /** Module the client is imported from, or "global" for built-ins like fetch */
22
278
  importModule: string;
23
- /** Named export or identifier — e.g. "initClient", "fetch" */
279
+ /** Named export or identifier, for example "initClient" or "fetch" */
24
280
  importName: string;
25
281
  /** If set, only match calls to these methods on the client (e.g. ["getUser"]).
26
282
  * Unset means any method call (or bare call for globals). */
@@ -37,11 +293,11 @@ type DiscoveryMatch = {
37
293
  } | {
38
294
  /**
39
295
  * A constructor or factory call that takes a configuration object
40
- * containing a resolver map — the idiomatic GraphQL code-first
41
- * shape. The map is two levels deep: outer keys are GraphQL type
42
- * names (`Query`, `Mutation`, `Subscription`, or object-type
43
- * names like `User`); inner keys are field names whose values
44
- * are resolver functions.
296
+ * containing a resolver map, which is how code-first GraphQL servers
297
+ * are usually written. The map is two levels deep: outer keys are
298
+ * GraphQL type names (`Query`, `Mutation`, `Subscription`, or
299
+ * object-type names like `User`), and inner keys are field names
300
+ * whose values are resolver functions.
45
301
  *
46
302
  * Example (Apollo Server v4):
47
303
  * ```ts
@@ -56,41 +312,42 @@ type DiscoveryMatch = {
56
312
  * ```
57
313
  *
58
314
  * Each inner function becomes one discovered unit whose binding
59
- * semantics is `graphql-resolver(typeName, fieldName)`. Matches
60
- * both `new Ctor(cfg)` and `ctor(cfg)` — Apollo's standalone
61
- * server uses `new`, yoga uses a bare call.
315
+ * semantics is `graphql-resolver(typeName, fieldName)`. Both
316
+ * `new Ctor(cfg)` and `ctor(cfg)` match, because Apollo's standalone
317
+ * server uses `new` and yoga uses a bare call.
62
318
  */
63
319
  type: "resolverMap";
64
320
  importModule: string;
65
321
  importName: string;
66
322
  /**
67
- * Name of the property on the config object that holds the
68
- * resolver map. Defaults to `"resolvers"` when unset, matching
69
- * Apollo / yoga / graphql-tools convention.
323
+ * The property on the config object that contains the resolver map.
324
+ * This is the library's own config key, so the pack has to give it and
325
+ * the adapter ships no default. Apollo, yoga, and graphql-tools all
326
+ * spell it `"resolvers"`.
70
327
  */
71
- mapProperty?: string;
328
+ mapProperty: string;
72
329
  /**
73
- * GraphQL type names whose fields we DON'T treat as resolvers —
74
- * opt-out for meta-types like `Subscription` that we may want
75
- * to handle differently later. Unset means discover every type.
330
+ * GraphQL types whose fields we DON'T treat as resolvers. This is the
331
+ * opt-out for meta-types like `Subscription` that we may want to handle
332
+ * differently later. Leave it unset to discover every type.
76
333
  */
77
334
  excludeTypes?: string[];
78
335
  } | {
79
336
  /**
80
- * Consumer-side GraphQL hook call — the canonical Apollo Client
81
- * and urql shape. Each call to one of the listed hooks becomes
337
+ * A consumer-side GraphQL hook call, the way Apollo Client and urql
338
+ * are normally used. Each call to one of the listed hooks becomes
82
339
  * a `client`-kind code unit whose binding semantics is
83
340
  * `graphql-operation(operationType, operationName?)`.
84
341
  *
85
- * The document argument resolves through several shapes: an inline
86
- * `gql`-tagged template, a const binding (same module or imported
87
- * from another module), a `.graphql` / `.gql` file import, and a
342
+ * The document argument can be written several ways: an inline
343
+ * `gql`-tagged template, a const binding (in this module or imported
344
+ * from another one), a `.graphql` or `.gql` file import, or a
88
345
  * generated `TypedDocumentNode` object literal from graphql-codegen
89
- * client-preset. When the document body isn't statically readable,
346
+ * client-preset. When the document body cannot be read statically,
90
347
  * the operation header falls back to the `TypedDocumentNode` type
91
- * arguments; a still-unresolvable document surfaces on the summary
92
- * as `metadata.graphql.unresolvedDocument` rather than dropping the
93
- * boundary.
348
+ * arguments. A document that still cannot be resolved shows up on the
349
+ * summary as `metadata.graphql.unresolvedDocument`, so the boundary is
350
+ * kept rather than dropped.
94
351
  *
95
352
  * Example:
96
353
  * ```ts
@@ -102,21 +359,21 @@ type DiscoveryMatch = {
102
359
  * }
103
360
  * ```
104
361
  *
105
- * The adapter records the operation name / type on the
106
- * DiscoveredUnit's `operationInfo`; binding construction uses that
362
+ * The adapter records the operation name and type on the
363
+ * DiscoveredUnit's `operationInfo`, and binding construction uses that
107
364
  * to emit `graphql-operation(...)`. The per-hook `operationType`
108
- * is authoritative when the document header can't be read (mirrors
109
- * `graphqlImperativeCall.methods`).
365
+ * wins when the document header cannot be read, the same way
366
+ * `graphqlImperativeCall.methods` does.
110
367
  */
111
368
  type: "graphqlHookCall";
112
369
  importModule: string;
113
370
  /**
114
- * Hooks to match on that import, each mapped to the operation
115
- * type it performs (`useQuery` → query, `useMutation` → mutation,
116
- * `useSubscription` → subscription). The mapping supplies the
117
- * operation type when the document body isn't statically readable.
118
- * Each hook is reported as `kind = "client"` by default; packs can
119
- * override via the enclosing `DiscoveryPattern.kind`.
371
+ * Hooks to match on that import, each mapped to the operation type it
372
+ * performs (`useQuery` to query, `useMutation` to mutation,
373
+ * `useSubscription` to subscription). That mapping supplies the
374
+ * operation type when the document body cannot be read statically.
375
+ * Each hook is reported as `kind = "client"` unless a pack overrides
376
+ * that through the enclosing `DiscoveryPattern.kind`.
120
377
  */
121
378
  hooks: Array<{
122
379
  hookName: string;
@@ -124,22 +381,21 @@ type DiscoveryMatch = {
124
381
  }>;
125
382
  } | {
126
383
  /**
127
- * Imperative Apollo-Client-style call — `client.query({ query })`,
128
- * `client.mutate({ mutation })`, `client.subscribe({ query })`.
129
- * Distinct from hook calls because the document lives on a
130
- * config-object property rather than the first positional arg.
131
- * Discovery is gated on an import of the named constructor
132
- * (typically `ApolloClient`) to reduce false positives — any
133
- * method-named "query" on a random object could look like this
134
- * shape.
384
+ * An imperative Apollo-Client-style call: `client.query({ query })`,
385
+ * `client.mutate({ mutation })`, `client.subscribe({ query })`. This is
386
+ * separate from hook calls because the document is on a config-object
387
+ * property rather than the first positional argument.
388
+ *
389
+ * Discovery only fires when the named constructor (usually
390
+ * `ApolloClient`) is imported, because otherwise any object at all
391
+ * with a `query` method would look like a match.
135
392
  *
136
- * Each entry in `methods` specifies the method name that gets
137
- * called on the client (`"query"` / `"mutate"` / `"subscribe"`)
138
- * and the config-object property that holds the gql document
139
- * (`"query"` / `"mutation"` / `"query"` respectively). The
140
- * method name drives the operation type (query / mutation /
141
- * subscription) when the gql document's header is anonymous;
142
- * a named document's header wins otherwise.
393
+ * Each entry in `methods` specifies the method called on the client
394
+ * (`"query"`, `"mutate"`, or `"subscribe"`) and the config-object
395
+ * property that contains the gql document
396
+ * (`"query"`, `"mutation"`, and `"query"` respectively). The method
397
+ * name decides the operation type when the gql document's header is
398
+ * anonymous. When the document has a name, its header wins.
143
399
  */
144
400
  type: "graphqlImperativeCall";
145
401
  importModule: string;
@@ -152,55 +408,53 @@ type DiscoveryMatch = {
152
408
  } | {
153
409
  /**
154
410
  * Treats a TypeScript package's public export surface as a
155
- * boundary. The adapter reads `package.json` at
156
- * `packageJsonPath`, resolves each reachable entry point
157
- * (root `.` and any sub-path `exports`), follows barrel
158
- * re-exports, and emits one discovered unit per exported
159
- * function — provider side of an in-process `function-call`
160
- * boundary.
411
+ * boundary. The adapter reads `package.json` at `packageJsonPath`,
412
+ * resolves each reachable entry point (root `.` and any sub-path
413
+ * `exports`), follows barrel re-exports, and emits one discovered
414
+ * unit per exported function. Those units are the provider side of
415
+ * an in-process `function-call` boundary.
161
416
  *
162
- * Produced bindings carry identity
417
+ * The bindings this produces have the identity
163
418
  * `{ transport: "in-process",
164
419
  * semantics: { name: "function-call",
165
420
  * package: <pkg.name>,
166
421
  * exportPath: [...] },
167
422
  * recognition: <pack.name> }`.
168
423
  *
169
- * Sub-path exports identify as e.g.
170
- * `@suss/behavioral-ir/schemas::BehavioralSummarySchema` —
171
- * `exportPath = ["schemas", "BehavioralSummarySchema"]`.
172
- * Root exports omit the sub-path segment.
424
+ * A sub-path export is identified as, for example,
425
+ * `@suss/behavioral-ir/schemas::BehavioralSummarySchema`, giving
426
+ * `exportPath = ["schemas", "BehavioralSummarySchema"]`. A root export
427
+ * leaves the sub-path segment out.
173
428
  *
174
- * v0 scope: resolves `types` / `default` / `import` conditions
175
- * on `exports`, falls back to `types` + `main` + `module`
176
- * when no `exports` field is set. Pattern exports (`./utils/*`)
177
- * and `development`-conditional resolution are deferred.
429
+ * As of v0 this resolves the `types`, `default`, and `import`
430
+ * conditions on `exports`, and falls back to `types`, `main`, `module`
431
+ * when there is no `exports` field. Pattern exports (`./utils/*`) and
432
+ * `development` conditions are not handled yet.
178
433
  */
179
434
  type: "packageExports";
180
435
  /** Absolute path to the package's `package.json`. */
181
436
  packageJsonPath: string;
182
437
  /**
183
- * Restrict to these `exports` keys (without leading `./`).
184
- * The root export is keyed `"."`. Unset means all resolvable
185
- * sub-paths.
438
+ * Restrict to these `exports` keys (without the leading `./`). The
439
+ * root export is keyed `"."`. Leave it unset for every sub-path that
440
+ * resolves.
186
441
  */
187
442
  subPaths?: string[];
188
443
  /**
189
- * Export names to skip — typically `["default"]` when a pack
190
- * wants to treat default exports separately or not at all.
444
+ * Export names to skip, usually `["default"]` when a pack wants to
445
+ * treat default exports separately or ignore them.
191
446
  */
192
447
  excludeNames?: string[];
193
448
  } | {
194
449
  /**
195
- * Class methods that carry a specific decorator, on classes that
196
- * carry a specific class-level decorator. Used by NestJS-style
197
- * frameworks where resolvers / handlers / controllers are
198
- * declared by decorator, not by registering a function in an
199
- * object literal.
450
+ * Class methods with a particular decorator, on classes with a
451
+ * particular class-level decorator. NestJS-style frameworks work this
452
+ * way: resolvers, handlers, and controllers are declared by decorator
453
+ * rather than by registering a function in an object literal.
200
454
  *
201
- * Discovery gates on an `importModule` of `classDecorator` and
202
- * each `methodDecorators` entry — random user-defined decorators
203
- * with the same names won't trip the matcher.
455
+ * Discovery only fires when `classDecorator` and each
456
+ * `methodDecorators` entry come from `importModule`, so a user-defined
457
+ * decorator that happens to share a name will not match.
204
458
  *
205
459
  * For a NestJS GraphQL pack:
206
460
  * `{ importModule: "@nestjs/graphql",
@@ -208,75 +462,73 @@ type DiscoveryMatch = {
208
462
  * methodDecorators: ["Query", "Mutation", "ResolveField",
209
463
  * "Subscription"] }`
210
464
  *
211
- * The adapter populates `DiscoveredUnit.resolverInfo` so the
212
- * produced binding carries `graphql-resolver(typeName, fieldName)`.
465
+ * The adapter fills in `DiscoveredUnit.resolverInfo` so the binding
466
+ * comes out as `graphql-resolver(typeName, fieldName)`.
213
467
  * `typeName` resolves from the class decorator's first argument
214
- * (`@Resolver(() => User)` → `"User"`), and from
468
+ * (`@Resolver(() => User)` gives `"User"`), or from
215
469
  * `methodDecoratorTypeMap` when the class decorator has no
216
- * argument. `fieldName` reads the method decorator's `{ name }`
217
- * option override when present, otherwise the method name.
470
+ * argument. `fieldName` comes from the method decorator's `{ name }`
471
+ * option when that is set, and otherwise from the method name.
218
472
  */
219
473
  type: "decoratedMethod";
220
474
  /**
221
- * The module a pack-recognised decorator must be imported from
222
- * for discovery to fire. Codebases sometimes wrap framework
223
- * decorators in their own re-exports, composing the framework's
224
- * decorator with extra metadata. When that happens, the
225
- * import-module gate would miss them; pass an array of accepted
226
- * modules and any one match suffices.
475
+ * The module a decorator has to be imported from before discovery will
476
+ * fire. Codebases sometimes re-export a framework decorator wrapped
477
+ * with extra metadata of their own, and checking one module would miss
478
+ * those. Pass an array of acceptable modules and any one of them
479
+ * matching is enough.
227
480
  */
228
481
  importModule: string | string[];
229
482
  /**
230
- * Class decorator names to recognise. The first entry that
231
- * appears on a class wins for typeName extraction (the rest are
232
- * fallbacks for codebases with multiple wrapper styles). A pack
233
- * ships only what its own framework declares here and takes a
234
- * project's wrappers through its options.
483
+ * Class decorators to recognise. The first one that appears on a class
484
+ * is the one typeName is read from; the rest are fallbacks for
485
+ * codebases with several wrapper styles. A pack ships only what its own
486
+ * framework declares here, and takes a project's own wrappers through
487
+ * its options instead.
235
488
  */
236
489
  classDecorators: string[];
237
490
  methodDecorators: string[];
238
491
  /**
239
- * Method decorator name → the type its field hangs off when the
240
- * class decorator names none. NestJS puts `@Query` on the root
241
- * `Query` type and `@Mutation` on `Mutation` whatever the class
242
- * says, so those two answer for themselves.
492
+ * Maps a method decorator to the type its field belongs to when the
493
+ * class decorator does not say. NestJS puts `@Query` on the root
494
+ * `Query` type and `@Mutation` on `Mutation` no matter what the class
495
+ * says, so those two settle it themselves.
243
496
  *
244
- * A decorator the map leaves out has no answer of its own, and a
245
- * class that names no type leaves the field's owner unread. The
246
- * binding then names no type and pairs with nothing, rather than
247
- * claiming a field the schema does not have.
497
+ * When the map leaves a decorator out and the class decorator gives no
498
+ * type either, nothing here works out which type owns the field. The
499
+ * binding then goes out with no type and pairs with nothing, instead
500
+ * of claiming a field the schema does not have.
248
501
  */
249
502
  methodDecoratorTypeMap: Record<string, string>;
250
503
  } | {
251
504
  /**
252
- * NestJS-style REST controller discovery. Class decorated with
253
- * `@Controller(pathPrefix?)`; methods decorated with
254
- * `@Get(subpath?)` / `@Post` / `@Put` / `@Delete` / etc. The
255
- * decorator NAME determines the HTTP method via
256
- * `methodDecoratorRouteMap`; the route path is the class
257
- * decorator's first arg + the method decorator's first arg
258
- * (slash-joined, both optional).
505
+ * NestJS-style REST controller discovery: a class decorated with
506
+ * `@Controller(pathPrefix?)`, and methods decorated with
507
+ * `@Get(subpath?)`, `@Post`, `@Put`, `@Delete`, and so on. The
508
+ * decorator's NAME is what determines the HTTP method, through
509
+ * `methodDecoratorRouteMap`. The route path is the class decorator's
510
+ * first argument joined with a slash to the method decorator's first
511
+ * argument, and both of those are optional.
259
512
  *
260
- * Same wrapper-decorator tolerance as `decoratedMethod`: the
261
- * import-module gate fires on at least one method-route
262
- * decorator from the framework module, but class decorators
263
- * match by name only, so a project-internal wrapper composing
264
- * the framework's decorator matches once the project names it
265
- * in the pack's options.
513
+ * Wrapper decorators are tolerated the same way as in
514
+ * `decoratedMethod`: at least one method-route decorator has to come
515
+ * from the framework module, but class decorators
516
+ * are matched by name alone, so a project's own wrapper around the
517
+ * framework's decorator matches once the project lists it in the
518
+ * pack's options.
266
519
  *
267
- * The adapter populates `DiscoveredUnit.routeInfo` so the
268
- * produced binding carries `rest(method, path)`.
520
+ * The adapter fills in `DiscoveredUnit.routeInfo` so the binding comes
521
+ * out as `rest(method, path)`.
269
522
  */
270
523
  type: "decoratedRoute";
271
524
  importModule: string | string[];
272
525
  classDecorators: string[];
273
526
  /**
274
- * Decorator name → HTTP method. NestJS uses one decorator per
275
- * verb (`@Get` / `@Post` / `@Put` / `@Delete` / `@Patch` /
276
- * `@Options` / `@Head` / `@All`); other frameworks may follow
277
- * the same convention. The values become the `method` field on
278
- * the produced REST binding; `"*"` is acceptable for catch-all
279
- * decorators.
527
+ * Maps a decorator to an HTTP method. NestJS uses one decorator per
528
+ * verb (`@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`, `@Options`,
529
+ * `@Head`, `@All`), and other frameworks may do the same. The values
530
+ * become the `method` field on the REST binding, and `"*"` is fine for
531
+ * a catch-all decorator.
280
532
  */
281
533
  methodDecoratorRouteMap: Record<string, string>;
282
534
  } | {
@@ -291,15 +543,15 @@ type DiscoveryMatch = {
291
543
  * ];
292
544
  * for (const r of routes) app[r.method](r.path, r.handler);
293
545
  *
294
- * `elementShape` declares which keys on each element carry the
295
- * method, path, and handler. The loop body is required to
296
- * contain at least one call expression that references the
297
- * loop variable (filters out unrelated loops); the call's
298
- * shape itself is not checked beyond that.
546
+ * `elementShape` declares which keys on each element give the method,
547
+ * the path, and the handler. The loop body has to contain at least
548
+ * one call expression that references the
549
+ * loop variable, which filters out unrelated loops. Nothing else
550
+ * about that call is checked.
299
551
  *
300
- * Iterables that resolve to an `ArrayLiteralExpression` (inline
301
- * or single-hop `const`-bound) are expanded; cross-file or
302
- * computed iterables are out of v0 scope.
552
+ * An iterable that resolves to an `ArrayLiteralExpression`, inline or
553
+ * bound to a `const` one hop away, gets expanded. Cross-file and
554
+ * computed iterables are outside v0.
303
555
  *
304
556
  * Pack-author docs: `docs/internal/proposals/dynamic-registration.md`.
305
557
  */
@@ -311,11 +563,11 @@ type DiscoveryMatch = {
311
563
  };
312
564
  } | {
313
565
  /**
314
- * Helper-call expansion: a single function call at the user
315
- * site is treated as if it were N inline registrations, with
316
- * arguments substituted into per-registration templates. Used
317
- * for patterns like `registerCrud(app, 'users', userHandlers)`
318
- * that today's `registrationCall` discovery can't see.
566
+ * Helper-call expansion: one function call at the user's site is
567
+ * treated as if it were N inline registrations, with the call's
568
+ * arguments substituted into a template per registration. Used
569
+ * for calls like `registerCrud(app, 'users', userHandlers)` that
570
+ * `registrationCall` discovery cannot see today.
319
571
  *
320
572
  * Each entry in `registrations` describes one virtual route
321
573
  * the helper produces. `pathTemplate` and `handlerArg` use
@@ -325,9 +577,9 @@ type DiscoveryMatch = {
325
577
  * non-literal args, with the slot marked opaque). `{N}.prop`
326
578
  * reads `prop` from the argument's resolved object.
327
579
  *
328
- * `importModule` optionally narrows matches to helpers
329
- * imported from a specific module — useful when two packages
330
- * happen to export a function with the same name.
580
+ * `importModule` optionally narrows matches to helpers imported from
581
+ * one specific module, which helps when two packages happen to export
582
+ * a function with the same name.
331
583
  *
332
584
  * Pack-author docs: `docs/internal/proposals/dynamic-registration.md`.
333
585
  */
@@ -341,23 +593,94 @@ type DiscoveryMatch = {
341
593
  }>;
342
594
  } | {
343
595
  /**
344
- * Consumer side of the package-export boundary. Scans source
345
- * files for imports of the named packages and records every
346
- * call site, emitting one `caller`-kind unit per enclosing
347
- * function. Produced bindings carry
348
- * `function-call { package, exportPath }` matching the
349
- * provider summaries from `packageExports`.
596
+ * Routes declared as JSX elements, the way client-side routers
597
+ * write them: an element imported from the router library whose
598
+ * attributes give a URL path pattern and the element it
599
+ * renders. Covers the tree form (route elements nested inside
600
+ * one another, child paths joining the parent's, index routes
601
+ * taking the parent's path) and the object-array form (a
602
+ * factory call whose first argument is an array of route
603
+ * objects using the same property names).
604
+ *
605
+ * The pack says what its library exports: the route element, the
606
+ * path, element, and index attributes, and any factories that
607
+ * take an array of route objects. The adapter walks JSX and arrays,
608
+ * and knows none of those names itself.
609
+ *
610
+ * Each route with a readable path becomes one unit whose target
611
+ * is the component the element attribute references, resolved
612
+ * only when the reference is a single identifier. A route whose
613
+ * component cannot be read is still reported, as a boundary with
614
+ * nothing behind it. A route whose path cannot be read gets no path
615
+ * and reports that in a gap instead of guessing at one.
616
+ */
617
+ type: "jsxElementRoute";
618
+ /**
619
+ * Module(s) the route element and factories must be imported
620
+ * from. Exact module specifiers, matched against the file's
621
+ * import declarations; aliased imports are followed.
622
+ */
623
+ importModule: string | string[];
624
+ /** The route element's exported name. */
625
+ routeElement: string;
626
+ /** The attribute with the route's path pattern on it. */
627
+ pathAttribute: string;
628
+ /** The attribute with the JSX the route renders on it. */
629
+ elementAttribute: string;
630
+ /**
631
+ * The attribute that marks an index route, which renders at its
632
+ * parent's path. Leave it unset if the library has no index routes.
633
+ */
634
+ indexAttribute?: string;
635
+ /**
636
+ * The property with the routes nested under a route object on it,
637
+ * which is how the object form expresses what the JSX form expresses
638
+ * by nesting. Paths compose the same way in both. Leave it unset if
639
+ * the library's route objects do not nest.
640
+ */
641
+ childrenAttribute?: string;
642
+ /**
643
+ * Factory functions whose first argument is an array of route
644
+ * objects keyed by the same three attribute names. The array is read
645
+ * where it is written, or through the same value resolution the rest
646
+ * of discovery uses, so a `const` binding one hop away works the
647
+ * same as an inline literal.
648
+ */
649
+ routeObjectFactories?: string[];
650
+ /**
651
+ * Factory functions that turn JSX route elements into the route
652
+ * objects the library consumes. The elements themselves are read
653
+ * by the JSX walk wherever they appear, so a route-object factory
654
+ * handed one of these calls has nothing more to add. Listing them
655
+ * here is what stops that case from being reported as an
656
+ * unreadable route array.
657
+ */
658
+ elementsFactories?: string[];
659
+ /**
660
+ * The HTTP method recorded on each route binding this produces. A page
661
+ * route serves navigations, and the pack has to say which method those
662
+ * use rather than the adapter assuming one.
663
+ */
664
+ method: string;
665
+ } | {
666
+ /**
667
+ * Consumer side of the package-export boundary. Scans source files for
668
+ * imports of the listed packages and records every call site,
669
+ * emitting one `caller`-kind unit per enclosing
670
+ * function. The bindings this produces are
671
+ * `function-call { package, exportPath }`, which match the provider
672
+ * summaries `packageExports` produces.
350
673
  *
351
- * `packages` is a list of exact package names (possibly with a
352
- * sub-path, e.g. `"@suss/behavioral-ir/schemas"`) whose imports
353
- * to track. Pass multiple package names to track a family at
674
+ * `packages` lists exact package names to track imports of, possibly
675
+ * with a sub-path such as `"@suss/behavioral-ir/schemas"`. Pass
676
+ * several package names to track a family at
354
677
  * once. Imports of any other package are ignored.
355
678
  *
356
- * v0 scope: named imports + default imports. Namespace imports
357
- * (`import * as X from`) are not yet tracked. Re-imports within
679
+ * As of v0 this covers named and default imports. Namespace imports
680
+ * (`import * as X from`) are not tracked yet. Re-imports within
358
681
  * the consumer repo (consumer A imports from consumer B which
359
- * re-exports from pkg) produce units against the intermediate,
360
- * not the original — full symbol resolution is deferred.
682
+ * re-exports from pkg) produce units against the intermediate rather
683
+ * than the original, because full symbol resolution is not built yet.
361
684
  */
362
685
  type: "packageImport";
363
686
  packages: string[];
@@ -366,6 +689,13 @@ type BindingExtraction = {
366
689
  method: {
367
690
  type: "fromRegistration";
368
691
  position: "methodName" | number;
692
+ /**
693
+ * Registrations whose recorded method is something other than their
694
+ * own name uppercased, the way `.all` registers every method and is
695
+ * recorded as `"*"`. Anything missing from the map is recorded as
696
+ * its own name uppercased.
697
+ */
698
+ nameMap?: Record<string, string>;
369
699
  } | {
370
700
  type: "fromExportName";
371
701
  } | {
@@ -390,11 +720,11 @@ type BindingExtraction = {
390
720
  property: string;
391
721
  } | {
392
722
  /**
393
- * The route path comes from where the file sits, which is how
394
- * Next.js and React Router describe their routes. The pack
395
- * spells out its own convention here, since the adapter knows
396
- * about files and the pack knows what the framework does with
397
- * their names.
723
+ * The route path comes from where the file is on disk, which is how
724
+ * Next.js and React Router describe their routes. The pack spells
725
+ * out its own convention here, because the adapter knows
726
+ * about files and the pack knows what the framework does with their
727
+ * names.
398
728
  *
399
729
  * `app/api/orders/[id]/route.ts` under `{ root: "app",
400
730
  * dropBasenames: ["route"], dynamic: "brackets" }` comes out as
@@ -403,14 +733,14 @@ type BindingExtraction = {
403
733
  */
404
734
  type: "fromFilename";
405
735
  /**
406
- * Where a route path starts. The directories below it become
407
- * the path; everything above it belongs to the project layout
408
- * and is dropped.
736
+ * Where a route path starts. The directories below it become the
737
+ * path, and everything above it belongs to the project's own layout
738
+ * and gets dropped.
409
739
  */
410
740
  root: string;
411
741
  /**
412
- * Filenames that say what a file is rather than adding a
413
- * segment: `route`, `page`, `index`, `_index`.
742
+ * Filenames that say what kind of file it is rather than adding a
743
+ * path segment: `route`, `page`, `index`, `_index`.
414
744
  */
415
745
  dropBasenames?: string[];
416
746
  /**
@@ -424,8 +754,8 @@ type BindingExtraction = {
424
754
  */
425
755
  dropParenthesized?: boolean;
426
756
  /**
427
- * Whether one filename holds the whole path with dots between
428
- * the segments, as `routes/orders.$id.tsx` does.
757
+ * Whether one filename contains the whole path with dots between the
758
+ * segments, the way `routes/orders.$id.tsx` does.
429
759
  */
430
760
  flat?: boolean;
431
761
  } | {
@@ -443,22 +773,48 @@ interface DiscoveryPattern {
443
773
  match: DiscoveryMatch;
444
774
  bindingExtraction?: BindingExtraction;
445
775
  /**
446
- * Files whose import declarations include any of these module
447
- * specifiers (or sub-paths of them) get this pattern's discovery
448
- * dispatch. Empty array = no gate (pattern is dispatched against
449
- * every file). Undefined = treated as no gate, but pack authors
450
- * SHOULD declare it explicitly — the `[]` form is the deliberate
451
- * "match every file" choice (typically because the pattern keys on
452
- * something other than imports — e.g. the fetch runtime matching
453
- * global `fetch(...)` calls).
776
+ * How the routable this pattern discovers (Express's `Router()`,
777
+ * Hono's `new Hono()`, and similar) can itself be mounted onto
778
+ * another one under a path prefix, as in Express's
779
+ * `app.use(prefix, router)` or Hono's `app.route(prefix, sub)`.
780
+ * This only means anything when `match.type` is `"registrationCall"`,
781
+ * because mount discovery reuses that match's `importModule` and
782
+ * `importName` to work out which variables in a file are the routable
783
+ * that a mount call is being made on.
454
784
  *
455
- * Match semantics: prefix on the import module specifier. An entry
456
- * `"@nestjs/graphql"` matches `from "@nestjs/graphql"` AND
457
- * `from "@nestjs/graphql/dist/foo"` AND any other sub-path.
785
+ * When set, the adapter composes the mount's prefix into the path
786
+ * of every route discovered on the mounted value, whether it is
787
+ * declared in the mounting file or, by following the mounted value
788
+ * through an import, in whichever file declares it. A mount whose
789
+ * prefix is not a string literal, or whose target the resolution store
790
+ * cannot follow to a concrete value, contributes nothing, and the routes
791
+ * under it keep the path they were written with.
792
+ */
793
+ mount?: {
794
+ /** Method name that registers a sub-router at a prefix, e.g. "use" or "route". */
795
+ method: string;
796
+ /** Argument position of the prefix string. */
797
+ prefixPosition: number;
798
+ /** Argument position of the mounted router/sub-app value. */
799
+ targetPosition: number;
800
+ };
801
+ /**
802
+ * This pattern only runs against files that import one of these module
803
+ * specifiers, or a sub-path of one. An empty array means no gate at all
804
+ * (the pattern is dispatched against
805
+ * every file). Leaving it undefined does the same, but pack authors
806
+ * SHOULD write it out, because `[]` is the deliberate
807
+ * "match every file" choice, usually because the pattern keys on
808
+ * something other than imports. The fetch runtime does that, since it
809
+ * matches global `fetch(...)` calls.
458
810
  *
459
- * Pre-filter is purely a perf optimisation: the closure walk and
460
- * other post-passes still have access to every loaded file via
461
- * symbol resolution.
811
+ * Matching is by prefix on the import module specifier. An entry of
812
+ * `"@nestjs/graphql"` matches `from "@nestjs/graphql"` and
813
+ * `from "@nestjs/graphql/dist/foo"` and any other sub-path.
814
+ *
815
+ * This pre-filter is only there for speed. The closure walk and the other
816
+ * post-passes can still reach every loaded file through symbol
817
+ * resolution.
462
818
  */
463
819
  requiresImport?: string[];
464
820
  }
@@ -471,8 +827,8 @@ type TerminalMatch = {
471
827
  * Skip ReturnStatements whose returned expression is a CallExpression
472
828
  * (or NewExpression). For frameworks where `return reply.send(...)`
473
829
  * also lands as a `parameterMethodCall` match on the inner call,
474
- * this prevents the same `return reply.send(...)` from producing
475
- * two terminals — one from the wrapping returnStatement, one from
830
+ * this stops the same `return reply.send(...)` producing two
831
+ * terminals, one from the wrapping returnStatement and one from
476
832
  * the inner method-call chain. Bare returns (`return user`,
477
833
  * `return { id }`, `return await fn()`) still match.
478
834
  */
@@ -489,43 +845,43 @@ type TerminalMatch = {
489
845
  functionName: string;
490
846
  /**
491
847
  * Only match when the name was imported from one of these modules.
492
- * Same name, shape, and prefix semantics as a DiscoveryPattern's
493
- * gate: "react-router" also matches "react-router/server".
848
+ * The field works exactly like a DiscoveryPattern's gate, matching by
849
+ * prefix: "react-router" also matches "react-router/server".
494
850
  *
495
- * Set it whenever the function belongs to a library, because
496
- * matching a bare name claims every same-named function in the
851
+ * Set it whenever the function belongs to a library, because matching
852
+ * on a bare name picks up every function with that name in the
497
853
  * user's project too. `json` is a common name for a project's own
498
854
  * response helper, and reading a library's argument order into one
499
- * of those produces a confident wrong answer.
855
+ * of those gives you a confident wrong answer.
500
856
  *
501
- * Leave it unset only when the function is not a library's at all.
502
- * A pack should generally not name a project's own helper: declare
503
- * the envelope shape instead, through a `returnShape` terminal, and
857
+ * Leave it unset only when the function belongs to no library at all.
858
+ * A pack should generally not target a project's own helper. Declare
859
+ * the envelope structure instead, with a `returnShape` terminal, and
504
860
  * the adapter follows a returned call into the project and reads
505
- * the helper's parameters. That covers helpers whatever they are
506
- * named and in whichever argument order they were written.
861
+ * the helper's parameters. That covers a helper whatever it is called
862
+ * and whatever order its arguments come in.
507
863
  */
508
864
  requiresImport?: string[];
509
865
  } | {
510
866
  /**
511
- * Return statement whose value is a JSX element or fragment. The
512
- * root element/component name is recorded in RawTerminal.component.
513
- * Used by React (and any other JSX-based framework pack) to
514
- * classify component outputs as `render` terminals.
867
+ * A return statement whose value is a JSX element or fragment. The
868
+ * root element or component name is recorded in
869
+ * `RawTerminal.component`. React, and any other JSX-based framework
870
+ * pack, uses this to classify component output as a `render` terminal.
515
871
  */
516
872
  type: "jsxReturn";
517
873
  } | {
518
874
  /**
519
- * Synthetic terminal for the implicit fall-through at the end of a
520
- * function body. Fires when the function has no explicit
521
- * `ReturnStatement` / `ThrowStatement` as its last statement —
522
- * covers the common case of handler / effect bodies that execute
523
- * side-effects and return `undefined` implicitly. Without this,
875
+ * A synthetic terminal for the implicit fall-through at the end of a
876
+ * function body. It fires when the function's last statement is
877
+ * neither a `ReturnStatement` nor a `ThrowStatement`, which
878
+ * covers the common case of handler and effect bodies that run
879
+ * side effects and return `undefined` implicitly. Without this,
524
880
  * handler summaries come out with `transitions: []` because
525
- * `findTerminals` has nothing to match. Packs that always expect
526
- * explicit returns (HTTP handlers) shouldn't include this in
527
- * their terminals; packs for callback bodies (React handlers,
528
- * `useEffect` bodies, Node `.on(...)` callbacks) should.
881
+ * `findTerminals` has nothing to match. A pack that always expects
882
+ * explicit returns (HTTP handlers) should leave this out of
883
+ * its terminals. A pack for callback bodies (React handlers,
884
+ * `useEffect` bodies, Node `.on(...)` callbacks) should include it.
529
885
  */
530
886
  type: "functionFallthrough";
531
887
  };
@@ -568,15 +924,15 @@ interface TerminalPattern {
568
924
  extraction: TerminalExtraction;
569
925
  }
570
926
  interface ContractPattern {
571
- /** How to find the contract object. Contracts are data structures, not code units,
572
- * so this is a simpler shape than DiscoveryPattern. */
927
+ /** How to find the contract object. A contract is a data structure rather
928
+ * than a code unit, so this needs less than a DiscoveryPattern does. */
573
929
  discovery: {
574
930
  importModule: string;
575
931
  importName: string;
576
932
  registrationChain: string[];
577
933
  };
578
934
  responseExtraction: {
579
- /** Property on the contract object that holds the responses map */
935
+ /** The property on the contract object with the responses map on it */
580
936
  property: string;
581
937
  };
582
938
  paramsExtraction?: {
@@ -606,14 +962,14 @@ type InputMappingPattern = {
606
962
  * Component props, React / Vue / Svelte-style: one parameter that
607
963
  * the caller destructures at will, with prop names only visible at
608
964
  * the call site. When the parameter is destructured, each bound
609
- * name becomes its own Input with role equal to the name. When
610
- * it's not destructured (e.g. `function X(props) {...}`), one
611
- * Input is emitted with `wholeParamRole` (default `"props"`).
965
+ * name becomes its own Input with the name as its role. When it is
966
+ * not destructured (`function X(props) {...}`), a single Input comes
967
+ * out with `wholeParamRole`, which defaults to `"props"`.
612
968
  *
613
- * Differs from `destructuredObject` in that prop names are not
614
- * declared by the pack up-front — they are whatever the component
615
- * author wrote. Differs from `singleObjectParam` in that the
616
- * destructuring pattern is honored when present.
969
+ * This differs from `destructuredObject` because the pack does not
970
+ * declare the prop names up front; they are whatever the component
971
+ * author wrote. It differs from `singleObjectParam` because the
972
+ * destructuring pattern is respected when there is one.
617
973
  */
618
974
  type: "componentProps";
619
975
  paramPosition: number;
@@ -621,41 +977,42 @@ type InputMappingPattern = {
621
977
  wholeParamRole?: string;
622
978
  } | {
623
979
  /**
624
- * Emit one `Input` per declared parameter, in source order, with
625
- * `role = param name` (or `defaultRole` when set). Used by the
626
- * reachable-closure pass for internal library functions — there
627
- * is no framework-declared role space, so the caller-visible
628
- * name IS the role. Captures destructured-parameter bindings the
629
- * same way `destructuredObject` does, so `(ctx, { userId })`
630
- * reads as two inputs: `ctx` and `userId`.
980
+ * Emit one `Input` per declared parameter, in source order, using the
981
+ * parameter's name as its role, or `defaultRole` when set. Used by the
982
+ * reachable-closure pass for internal library functions, where no
983
+ * framework declares a set of roles, so the name a caller sees IS
984
+ * the role. Destructured parameters are captured the
985
+ * same way `destructuredObject` captures them, so `(ctx, { userId })`
986
+ * gives two inputs, `ctx` and `userId`.
631
987
  */
632
988
  type: "allPositional";
633
989
  defaultRole?: string;
634
990
  } | {
635
991
  /**
636
- * Decorator-driven parameter mapping (NestJS-style). For each
637
- * declared parameter, the adapter reads the parameter's first
638
- * decorator and looks up its name in `decoratorRoleMap` —
639
- * matched decorators map the parameter to that role; unmatched
640
- * parameters fall back to `defaultRole` (or skip when unset).
992
+ * Decorator-driven parameter mapping, NestJS-style. For each declared
993
+ * parameter, the adapter reads the parameter's first decorator and
994
+ * looks its name up in `decoratorRoleMap`.
995
+ * A decorator that matches gives the parameter that role. One that
996
+ * matches nothing falls back to `defaultRole`, or is skipped when
997
+ * `defaultRole` is unset.
641
998
  *
642
999
  * For `@nestjs/graphql` resolvers:
643
1000
  * `{ "Args": "args", "Parent": "parent",
644
1001
  * "Context": "context", "Info": "info" }`.
645
1002
  *
646
- * Decorator gating is by name only — multiple frameworks
647
- * defining `@Args` would all map. Packs that need to
648
- * disambiguate by import module add it later when the use case
649
- * justifies the cost.
1003
+ * Decorators are matched by name alone, so if several frameworks
1004
+ * define `@Args`, all of them map. Packs that need to
1005
+ * tell them apart by import module can add that later, once there
1006
+ * is a use case worth the cost.
650
1007
  */
651
1008
  type: "decoratedParams";
652
1009
  decoratorRoleMap: Record<string, string>;
653
1010
  defaultRole?: string;
654
1011
  };
655
1012
  /**
656
- * What a property on the API response object semantically represents.
657
- * Declared in the pack so the adapter can resolve derived properties
658
- * (e.g. `.ok` → status range 200–299) at extraction time.
1013
+ * What a property on the API response object means. The pack declares this so
1014
+ * the adapter can work out a derived property at extraction time, the way
1015
+ * `.ok` means a status somewhere in 200 to 299.
659
1016
  */
660
1017
  type ResponsePropertyMeaning = {
661
1018
  type: "statusCode";
@@ -673,19 +1030,19 @@ interface ResponsePropertyMapping {
673
1030
  name: string;
674
1031
  /** How this member is accessed: property read or method call */
675
1032
  access: "property" | "method";
676
- /** What the value semantically represents */
1033
+ /** What the value means */
677
1034
  semantics: ResponsePropertyMeaning;
678
1035
  }
679
1036
  /**
680
- * A library wrapper whose result is the function it was handed. The
681
- * adapter works this out on its own for factories declared in the
682
- * project by reading their bodies. A library's body is not readable,
683
- * so the pack states it.
1037
+ * A library wrapper that returns the function it was handed. For a factory
1038
+ * declared inside the project, the adapter works this out on its own by
1039
+ * reading the body. A library's body is not there to read, so the pack has to
1040
+ * say so.
684
1041
  */
685
1042
  interface TransparentWrapper {
686
1043
  /** Callee text as written, e.g. "Sentry.wrapHandler". */
687
1044
  callee: string;
688
- /** Which argument holds the wrapped function. */
1045
+ /** Which argument the wrapped function is passed as. */
689
1046
  argument: number;
690
1047
  /**
691
1048
  * The module the callee has to have been imported from. Without it a
@@ -705,9 +1062,9 @@ interface PatternPack {
705
1062
  * the pack does. The CLI folds a hash of the file it loaded and of
706
1063
  * the config it passed into this stamp, so a pack run through the CLI
707
1064
  * invalidates on an edit whether or not it declares a version. A host
708
- * that builds packs some other way carries that responsibility
709
- * itself. A pack with nothing to stamp reads as `"unset"`, and warm
710
- * caches will answer for code that has since changed.
1065
+ * that builds packs some other way takes on that responsibility itself.
1066
+ * A pack with nothing to stamp comes out as `"unset"`, and a warm cache
1067
+ * will then serve results for code that has since changed.
711
1068
  */
712
1069
  version?: string;
713
1070
  languages: string[];
@@ -717,29 +1074,29 @@ interface PatternPack {
717
1074
  inputMapping: InputMappingPattern;
718
1075
  /**
719
1076
  * Transport (wire protocol) used in the `BoundaryBinding.transport`
720
- * of discovered units. Every pack states its transport explicitly
721
- * rather than leaning on a hardcoded HTTP default — "what transport
1077
+ * of discovered units. Every pack has to say what its transport is
1078
+ * rather than falling back on a hardcoded HTTP default. "What transport
722
1079
  * does this pack cover?" is a question every pack should have to
723
- * answer, and making the field required keeps future packs (React,
724
- * GraphQL, Lambda-invoke, queues) from silently inheriting an
725
- * HTTP-shaped default that doesn't fit.
1080
+ * answer, and requiring the field stops a later pack (React, GraphQL,
1081
+ * Lambda-invoke, queues) from quietly inheriting an HTTP-shaped default
1082
+ * that does not fit it.
726
1083
  *
727
- * The pack's `name` separately populates `BoundaryBinding.recognition`
728
- * on produced summaries — so `{ transport, recognition }` come from
729
- * the pack directly, and `semantics` is derived by the adapter from
730
- * the discovery pattern's binding-extraction rules.
1084
+ * The pack's `name` separately fills in `BoundaryBinding.recognition`
1085
+ * on the summaries, so `{ transport, recognition }` come from
1086
+ * the pack directly and the adapter derives `semantics` from the
1087
+ * discovery pattern's binding-extraction rules.
731
1088
  */
732
1089
  protocol: string;
733
1090
  /**
734
- * Semantics of properties on the API response object (consumer side).
735
- * Tells the adapter how to resolve derived properties like `.ok` or
736
- * `.json()` to structured IR constructs instead of leaving them opaque.
1091
+ * What the properties on the API response object mean, consumer side.
1092
+ * This tells the adapter how to turn a derived property like `.ok` or
1093
+ * `.json()` into a structured IR construct instead of leaving it opaque.
737
1094
  */
738
1095
  responseSemantics?: ResponsePropertyMapping[];
739
1096
  /**
740
- * Synthesize additional code units from a parent unit's body —
741
- * "one user-authored construct implicitly spawns multiple
742
- * runtime-scheduled units." Used when a framework's runtime
1097
+ * Synthesize extra code units out of a parent unit's body, for when one
1098
+ * construct the user wrote implicitly spawns several units the runtime
1099
+ * schedules. Used when a framework's runtime
743
1100
  * schedules callbacks that aren't visible as top-level declarations:
744
1101
  * React event handlers on JSX elements, React `useEffect` bodies,
745
1102
  * Node `emitter.on("event", handler)`, class-component lifecycle
@@ -747,53 +1104,53 @@ interface PatternPack {
747
1104
  *
748
1105
  * `ctx` is typed `unknown` here because the extractor has no
749
1106
  * knowledge of which adapter is driving it; each language adapter
750
- * defines its own context shape (e.g. `TsSubUnitContext` in
751
- * `@suss/adapter-typescript`) with the primitives packs need to
1107
+ * defines its own context type (`TsSubUnitContext` in
1108
+ * `@suss/adapter-typescript`, say) with the primitives a pack needs to
752
1109
  * walk the parent's AST. Packs import and cast to the adapter
753
- * context they're written against — the cast is the explicit
754
- * "this pack requires the TypeScript adapter" contract.
1110
+ * context they were written against, and that cast is how a pack says
1111
+ * out loud that it requires the TypeScript adapter.
755
1112
  *
756
1113
  * Returned units are fed through the adapter's extraction pipeline
757
1114
  * the same way top-level discovered units are, so each becomes its
758
- * own `BehavioralSummary`. Carry per-unit `terminals` and
759
- * `inputMapping` on the `DiscoveredUnit` if the sub-unit's shape
760
- * differs from the parent pack's defaults.
1115
+ * own `BehavioralSummary`. Put per-unit `terminals` and `inputMapping`
1116
+ * on the `DiscoveredUnit` when a sub-unit is written differently from
1117
+ * the parent pack's defaults.
761
1118
  */
762
1119
  subUnits?: (parent: DiscoveredSubUnitParent, ctx: unknown) => DiscoveredSubUnit[];
763
1120
  /**
764
- * Pack-supplied top-level discovery callback. Sibling of `subUnits`
765
- * for the discovery layer: when a framework's discovery convention
766
- * doesn't fit one of the data-driven `DiscoveryMatch` variants (REST
1121
+ * A top-level discovery callback the pack supplies. It is to discovery
1122
+ * what `subUnits` is to sub-units: when a framework's convention does
1123
+ * not fit one of the data-driven `DiscoveryMatch` variants (REST
767
1124
  * registration, decorator-based controllers, named-export shapes,
768
1125
  * etc.), the pack ships its own walker here. The adapter calls it
769
1126
  * once per source file alongside the data-driven dispatch.
770
1127
  *
771
- * Use this for framework-specific shapes that don't generalize:
772
- * React's component-export heuristic (PascalCase + JSX-return),
1128
+ * Use this for framework-specific patterns that do not generalize:
1129
+ * React's component-export heuristic (PascalCase plus a JSX return),
773
1130
  * Vue's `.vue` SFC slots, Solid's component conventions, Storybook's
774
- * `.stories.tsx` file convention. These are real conventions but
775
- * baking each into the central `DiscoveryMatch` union forces every
776
- * unrelated pack to know about them. Callbacks keep the central
777
- * union for the generic primitives and let packs own their own
1131
+ * `.stories.tsx` file convention. Those are all legitimate conventions,
1132
+ * but baking each one into the central `DiscoveryMatch` union forces
1133
+ * every unrelated pack to know about them. Callbacks leave the central
1134
+ * union for the generic primitives and let each pack own its own
778
1135
  * conventions.
779
1136
  *
780
- * `ctx` is typed `unknown` for the same reason as `subUnits` — each
1137
+ * `ctx` is typed `unknown` for the same reason as in `subUnits`: each
781
1138
  * adapter ships its own context primitive (`TsDiscoveryContext` in
782
- * `@suss/adapter-typescript`) and packs cast to whichever they're
783
- * written against. The cast is the "I require the TS adapter"
784
- * contract.
1139
+ * `@suss/adapter-typescript`) and a pack casts to whichever one it was
1140
+ * written against. That cast is how the pack says it requires the TS
1141
+ * adapter.
785
1142
  *
786
- * Returned units flow through the adapter's normal pipeline — they
787
- * get terminals + effects extracted, sub-units synthesized, and
1143
+ * The units you return go through the adapter's normal pipeline. They
1144
+ * get their terminals and effects extracted, sub-units synthesized, and
788
1145
  * summaries assembled exactly as units from data-driven discovery
789
- * do. Per-unit `terminals` / `inputMapping` overrides on
790
- * `DiscoveredUnit` work the same way.
1146
+ * do. Per-unit `terminals` and `inputMapping` overrides on
1147
+ * `DiscoveredUnit` work the same way too.
791
1148
  *
792
1149
  * **Cross-pack dedup.** When this callback discovers a unit at the
793
1150
  * same `(func, kind)` as a unit from another pack's data-driven
794
- * discovery, the cross-pack claim dedup in the adapter keeps the
795
- * first claimant. Pack ordering in the framework list is the
796
- * precedence signal.
1151
+ * discovery, the adapter's cross-pack claim dedup keeps whichever
1152
+ * claimed it first. The order packs appear in the framework list is
1153
+ * what decides precedence.
797
1154
  */
798
1155
  discoverUnits?: (sourceFile: unknown, ctx: unknown) => DiscoveredCustomUnit[];
799
1156
  /**
@@ -804,77 +1161,77 @@ interface PatternPack {
804
1161
  * the function body and dispatches to every registered recognizer
805
1162
  * for each call. Walking skips nested function bodies (those are
806
1163
  * their own units with their own recognizer dispatch). The walk is
807
- * INDEPENDENT of the existing invocation-effect walker — the
808
- * existing walker is intentionally narrow (only captures
809
- * `invocation` effects from bare expression statements + container
1164
+ * INDEPENDENT of the existing invocation-effect walker, which is
1165
+ * deliberately narrow (it only captures
1166
+ * `invocation` effects from bare expression statements and container
810
1167
  * composition, to avoid double-counting calls that already become
811
- * terminals). Recognizers don't have that concern; they fire on
1168
+ * terminals). Recognizers do not have that problem, so they fire on
812
1169
  * every call regardless of position, including
813
1170
  * `const x = await fn(...)` initializers and nested call args
814
1171
  * (which the invocation walker skips). This independence means
815
1172
  * recognizer authors can rely on seeing every call in scope.
816
1173
  *
817
- * **Cross-pack visibility.** Recognizers fire regardless of which
818
- * pack discovered the enclosing function — so
1174
+ * **Cross-pack visibility.** Recognizers fire regardless of which pack
1175
+ * discovered the enclosing function, so
819
1176
  * `@suss/framework-prisma`'s recognizer can fire on Prisma calls
820
1177
  * inside an `@suss/framework-express` handler. Pack authors don't
821
1178
  * need to coordinate.
822
1179
  *
823
- * **Emission contract.** Returning effects ADDS them to the
824
- * enclosing default-branch transition; the generic `invocation`
825
- * effect is preserved either way (typed effects coexist with raw
826
- * call capture so inspect rendering keeps callee text + args while
827
- * the checker pairs on the typed shape). Returning `null` / `[]`
828
- * is the no-match path.
1180
+ * **Emission contract.** Returning effects ADDS them to the enclosing
1181
+ * default-branch transition, and the generic `invocation` effect is
1182
+ * kept either way (typed effects live alongside the raw
1183
+ * call capture, so inspect can still render the callee text and
1184
+ * arguments while the checker pairs on the typed form). Return `null`
1185
+ * or `[]` for no match.
829
1186
  *
830
- * **Dedup is the recognizer's responsibility.** The dispatcher
831
- * doesn't dedupe across calls. A recognizer that wants to fire
832
- * once per identifier (e.g., to dedupe reads bound to a const
833
- * that's used N times) tracks its own state across invocations.
1187
+ * **Dedup is the recognizer's responsibility.** The dispatcher does
1188
+ * not dedupe across calls. A recognizer that wants to fire
1189
+ * once per identifier, to collapse reads bound to a const used N
1190
+ * times, has to track that state itself across invocations.
834
1191
  *
835
- * **Exceptions are caught and logged.** A recognizer that throws
836
- * is logged to stderr with file path + line number and skipped
837
- * for that call; the extraction continues. Buggy recognizers
838
- * don't crash the run.
1192
+ * **Exceptions are caught and logged.** A recognizer that throws gets
1193
+ * logged to stderr with the file path and line number, and is skipped
1194
+ * for that one call while the extraction carries on. A buggy
1195
+ * recognizer will not crash the run.
839
1196
  *
840
1197
  * `call` is the language adapter's call-expression handle (opaque
841
1198
  * here; ts-morph `CallExpression` in `@suss/adapter-typescript`).
842
1199
  * `ctx` is the adapter's recognizer context (source file, an
843
1200
  * `extractArgs()` helper that reuses the adapter's own EffectArg
844
- * builder). Recognizers cast both to the adapter context they're
845
- * written against — same "this pack requires the TypeScript
846
- * adapter" contract `subUnits` uses.
1201
+ * builder). A recognizer casts both to the adapter context it was
1202
+ * written against, which is the same way `subUnits` says a pack
1203
+ * requires the TypeScript adapter.
847
1204
  */
848
1205
  invocationRecognizers?: InvocationRecognizer[];
849
1206
  /**
850
1207
  * Optional pack-level import gate. When set, the adapter's
851
1208
  * pre-filter only considers this pack applicable to source files
852
1209
  * whose imports include at least one of the listed modules
853
- * (prefix match — `"@aws-sdk/client-sqs"` matches that module
854
- * AND any `"@aws-sdk/client-sqs/sub-path"`).
1210
+ * (matched by prefix, so `"@aws-sdk/client-sqs"` matches that module
1211
+ * and any `"@aws-sdk/client-sqs/sub-path"`).
855
1212
  *
856
1213
  * Useful for recognizer-only packs that target a specific library:
857
1214
  * `@suss/framework-aws-sqs` declares `["@aws-sdk/client-sqs"]`,
858
1215
  * `@suss/framework-prisma` declares `["@prisma/client"]`. Without
859
- * a gate, recognizer-only packs walk every file in the project —
860
- * correct but wasted work on large monorepos where most files
861
- * don't import the library.
1216
+ * a gate, a recognizer-only pack walks every file in the project. That
1217
+ * is correct but wasteful in a large monorepo where most files never
1218
+ * import the library.
862
1219
  *
863
- * Discovery-pattern packs already have per-pattern `requiresImport`
864
- * on `DiscoveryPattern`; this is the pack-level equivalent for
865
- * packs whose ONLY mechanism is recognizers (no discovery).
1220
+ * A discovery-pattern pack already has a per-pattern `requiresImport`
1221
+ * on `DiscoveryPattern`. This is the pack-level version of that, for a
1222
+ * pack whose ONLY mechanism is recognizers and which has no discovery.
866
1223
  *
867
- * Empty / undefined means "no gate" — pack walks every file (the
868
- * default for truly universal recognizers like `@suss/runtime-node`'s
869
- * process-surface / env-var recognizers, since `process.*` is
870
- * available without an import).
1224
+ * Empty or undefined means no gate, so the pack walks every file (the
1225
+ * default for universal recognizers like `@suss/runtime-node`'s
1226
+ * process-surface and env-var recognizers, since `process.*` is
1227
+ * available without importing anything).
871
1228
  */
872
1229
  requiresImport?: string[];
873
1230
  /**
874
- * Library wrappers whose result is the wrapped function. The adapter
875
- * derives this automatically for project-local factories by reading
876
- * their bodies; a library wrapper's body is not readable, so the
877
- * pack states the judgment: a call to `callee` resolves to its
1231
+ * Library wrappers that return the function they wrapped. The adapter
1232
+ * works this out on its own for a factory inside the project by reading
1233
+ * its body. A library wrapper's body is not there to read, so the
1234
+ * pack has to say it: a call to `callee` resolves to its
878
1235
  * `argument`-th argument.
879
1236
  *
880
1237
  * `callee` matches the call expression text as written, e.g.
@@ -882,21 +1239,21 @@ interface PatternPack {
882
1239
  */
883
1240
  transparentWrappers?: TransparentWrapper[];
884
1241
  /**
885
- * Per-property-access recognizers — sister to
886
- * `invocationRecognizers` but firing on `PropertyAccessExpression`
1242
+ * Per-property-access recognizers, the counterpart to
1243
+ * `invocationRecognizers`, firing on `PropertyAccessExpression`
887
1244
  * nodes rather than `CallExpression` nodes. Use these for patterns
888
1245
  * that read a value through property access without invoking it:
889
1246
  * `process.env.X` env-var reads, `Date.now()`-style time reads
890
1247
  * (which is actually a call, see invocationRecognizers), bare
891
1248
  * `module.constant` reads, etc.
892
1249
  *
893
- * Same scope rules as invocationRecognizers — fires on every
894
- * PropertyAccessExpression in the function body, skipping nested
895
- * function bodies. Same emission contract: returned effects land
1250
+ * The scope rules are the same as for invocationRecognizers: it fires
1251
+ * on every PropertyAccessExpression in the function body and skips
1252
+ * nested function bodies. The emission contract is the same, so effects
896
1253
  * on the enclosing default-branch transition.
897
1254
  *
898
- * Same reasoning as invocationRecognizers for the opaque-arg /
899
- * adapter-narrows pattern.
1255
+ * The arguments are opaque here and narrowed by the adapter, for the
1256
+ * same reason as in invocationRecognizers.
900
1257
  */
901
1258
  accessRecognizers?: AccessRecognizer[];
902
1259
  }
@@ -911,12 +1268,12 @@ type InvocationRecognizer<TCtx = unknown> = (call: unknown, ctx: TCtx) => Effect
911
1268
  */
912
1269
  type AccessRecognizer<TCtx = unknown> = (access: unknown, ctx: TCtx) => Effect[] | null;
913
1270
  /**
914
- * Minimal handle-shaped description of the parent code unit that
915
- * `subUnits` operates within. `func` is left opaque here — the
916
- * language adapter brands its own FunctionRoot type. This interface
917
- * lives in the extractor only so `PatternPack` can name it;
918
- * adapter-level context types (`TsSubUnitContext`) narrow `func` to
919
- * a concrete AST handle.
1271
+ * The bare minimum a `subUnits` hook needs to know about the parent code
1272
+ * unit it is working inside. `func` is left opaque here because each
1273
+ * language adapter brands its own FunctionRoot type. This interface lives in
1274
+ * the extractor only so `PatternPack` can refer to it, and the adapter-level
1275
+ * context types like `TsSubUnitContext` narrow `func` to a concrete AST
1276
+ * handle.
920
1277
  */
921
1278
  interface DiscoveredSubUnitParent {
922
1279
  /** Handle to the parent's function body. Opaque at extractor level. */
@@ -927,17 +1284,15 @@ interface DiscoveredSubUnitParent {
927
1284
  kind: string;
928
1285
  }
929
1286
  /**
930
- * What a pack's `discoverUnits` hook returns per discovered top-level
931
- * unit. Mirrors `DiscoveredSubUnit` for the discovery layer — the
932
- * adapter widens these into its own internal `DiscoveredUnit` type
933
- * (which carries adapter-specific fields like `routeInfo`,
934
- * `packageExportInfo`, etc.) and pipes them through the normal
935
- * extraction pipeline.
1287
+ * What a pack's `discoverUnits` hook returns for each top-level unit it
1288
+ * finds. It is to discovery what `DiscoveredSubUnit` is to sub-units. The
1289
+ * adapter widens these into its own internal `DiscoveredUnit` type, which has
1290
+ * adapter-specific fields like `routeInfo` and `packageExportInfo` on it, and
1291
+ * then runs them through the normal extraction pipeline.
936
1292
  *
937
- * Pack authors stay in opaque-handle land — `func` is whatever the
938
- * adapter's primitive returns. Adapter-narrows this to its concrete
939
- * function-root type (e.g. `FunctionRoot` in
940
- * `@suss/adapter-typescript`).
1293
+ * Pack authors only ever see opaque handles: `func` is whatever the adapter's
1294
+ * primitive returned, and the adapter narrows it to its concrete
1295
+ * function-root type (`FunctionRoot` in `@suss/adapter-typescript`).
941
1296
  */
942
1297
  interface DiscoveredCustomUnit {
943
1298
  /** Function body handle, opaque here. */
@@ -959,15 +1314,15 @@ interface DiscoveredCustomUnit {
959
1314
  /**
960
1315
  * REST route identity for units a callback discovers against an
961
1316
  * external manifest (a SAM/CFN template's `Events` block, an infra
962
- * routing declaration, etc.) rather than an in-code registration.
963
- * When set, the adapter builds a `rest` binding from `(method, path)`
964
- * — the same binding NestJS controllers get via decorator-derived
965
- * `routeInfo`, without the discoverUnits callback needing to reach
966
- * into the adapter's binding machinery.
1317
+ * routing declaration, and so on) rather than an in-code registration.
1318
+ * When set, the adapter builds a `rest` binding from `(method, path)`,
1319
+ * the same binding a NestJS controller gets from decorator-derived
1320
+ * `routeInfo`, and the discoverUnits callback never has to reach into
1321
+ * the adapter's binding machinery.
967
1322
  *
968
1323
  * One function bound to several routes emits one DiscoveredCustomUnit
969
- * per route; the adapter's per-file claim dedup keys on
970
- * `(func, kind, method, path)` so the variants survive.
1324
+ * per route. The adapter's per-file claim dedup keys on
1325
+ * `(func, kind, method, path)`, so all of those variants survive.
971
1326
  */
972
1327
  routeInfo?: {
973
1328
  method: string;
@@ -986,9 +1341,9 @@ interface DiscoveredCustomUnit {
986
1341
  fieldName: string;
987
1342
  };
988
1343
  /**
989
- * Message-bus channel identity for consumer units a callback
990
- * discovers against a subject the code itself names (a handler
991
- * factory whose config carries the expected subject). When set, the
1344
+ * Message-bus channel identity for consumer units a callback discovers
1345
+ * against a subject the code itself gives (a handler factory whose
1346
+ * config states the subject it expects). When set, the
992
1347
  * adapter builds a `message-bus` binding from `(messageBus, channel)`,
993
1348
  * which pairs with producers sending on the same channel.
994
1349
  */
@@ -1016,13 +1371,13 @@ interface DiscoveredSubUnit {
1016
1371
  /** Qualified name (e.g. "Counter.button.onClick"). */
1017
1372
  name: string;
1018
1373
  /**
1019
- * Terminal patterns to extract from this sub-unit's body. Defaults
1020
- * to `return` + `throw` when unset — fits handlers / effects cleanly.
1374
+ * Terminal patterns to extract from this sub-unit's body. When unset it
1375
+ * defaults to `return` and `throw`, which suits handlers and effects.
1021
1376
  */
1022
1377
  terminals?: TerminalPattern[];
1023
1378
  /**
1024
- * Input mapping for this sub-unit. Defaults to an empty positional
1025
- * mapping when unset — event handlers with one arg should pass
1379
+ * Input mapping for this sub-unit. When unset it defaults to an empty
1380
+ * positional mapping, so an event handler with one argument should pass
1026
1381
  * `{ type: "positionalParams", params: [{ position: 0, role: "event" }] }`.
1027
1382
  */
1028
1383
  inputMapping?: InputMappingPattern;
@@ -1034,20 +1389,29 @@ interface DiscoveredSubUnit {
1034
1389
  }
1035
1390
 
1036
1391
  /**
1037
- * Build the `discovery` entries for an HTTP-server framework whose
1038
- * handlers are registered via `app.get(path, handler)` /
1039
- * `router.post(...)` / etc.
1392
+ * Small helpers for pattern packs that are built the same way.
1393
+ *
1394
+ * The pack interface is deliberately declarative: a PatternPack is a data
1395
+ * object the adapter interprets, and most differences between frameworks are
1396
+ * best expressed that way. A few patterns, though, repeat word for word
1397
+ * across packs, and this module collects those so they are written once.
1398
+ */
1399
+
1400
+ /**
1401
+ * Build the `discovery` entries for an HTTP-server framework whose handlers
1402
+ * are registered with `app.get(path, handler)`, `router.post(...)`, and the
1403
+ * like.
1040
1404
  *
1041
- * Each `importNames` entry produces one DiscoveryPattern — libraries
1042
- * typically expose both a default export and a named export that both
1043
- * produce the routable instance (e.g. Express has `express()` and
1044
- * `Router()`; Fastify has `fastify()` and `Fastify`). The binding
1045
- * extraction (method from registration, path from position 0) is
1046
- * identical across every HTTP server framework we support.
1405
+ * Each `importNames` entry produces one DiscoveryPattern, because a library
1406
+ * usually exposes both a default export and a named export that each produce
1407
+ * the routable instance (Express has `express()` and
1408
+ * `Router()`, Fastify has `fastify()` and `Fastify`). The binding
1409
+ * extraction, method from the registration and path from position 0, is
1410
+ * the same for every HTTP server framework we support.
1047
1411
  *
1048
- * Callers still supply the `methods` list directly: different
1049
- * frameworks support different HTTP verbs (Fastify includes `.head` /
1050
- * `.options`, Express historically does not by default).
1412
+ * Callers still pass the `methods` list themselves, because frameworks
1413
+ * support different HTTP verbs. Fastify includes `.head` and `.options`;
1414
+ * Express historically does not by default.
1051
1415
  *
1052
1416
  * @example
1053
1417
  * discovery: httpRouteDiscovery({
@@ -1062,19 +1426,160 @@ declare function httpRouteDiscovery(opts: {
1062
1426
  methods: readonly string[];
1063
1427
  /** Defaults to "handler". Override for packs that want a different kind. */
1064
1428
  kind?: string;
1429
+ /**
1430
+ * How this framework's routable can itself be mounted onto another
1431
+ * one under a path prefix, so a route declared on the mounted value gets
1432
+ * summarized with the prefix built into its path. See `DiscoveryPattern`.
1433
+ */
1434
+ mount?: DiscoveryPattern["mount"];
1065
1435
  }): DiscoveryPattern[];
1066
1436
 
1437
+ /**
1438
+ * The path engine, written against `StructuredStatement` rather than any one
1439
+ * language. Every control-flow path from entry to a terminal contributes its
1440
+ * own conjunction of conditions, so a terminal you can reach along several
1441
+ * paths becomes several entries instead of one branch with an invented
1442
+ * conjunction, or none at all.
1443
+ *
1444
+ * Nothing here touches a language-specific AST node. A language adapter
1445
+ * lowers its own tree into `StructuredStatement` and hands that over.
1446
+ *
1447
+ * Constructs a lowering declines to model come back as thrown
1448
+ * `PathBudgetExceeded` or `UnmodeledFlow`, and the caller falls back to
1449
+ * enclosure conditions plus an opaque conjunct instead of guessing.
1450
+ */
1451
+
1452
+ /** A construct the lowering step will not model safely, such as switch and case rules the enumeration does not cover. Callers catch this and degrade. */
1453
+ declare class UnmodeledFlow extends Error {
1454
+ }
1455
+ /** The path budget was exceeded. Callers catch this and degrade. */
1456
+ declare class PathBudgetExceeded extends Error {
1457
+ }
1458
+ interface StructuredPathConditionsInput<Cond, Terminal> {
1459
+ /** The unit's top-level statements, in source order. */
1460
+ statements: StatementBlock<Cond>;
1461
+ /**
1462
+ * Every StructuredStatement reachable from `statements`, mapped to the
1463
+ * terminals the caller gave us that sit directly at that node: its own
1464
+ * position (a return, throw, or opaque leaf) or, for a branching
1465
+ * construct, its header or test expression. A lowering builds this at the
1466
+ * same time as the tree, so the engine never walks a raw source AST to
1467
+ * find where a terminal lives.
1468
+ */
1469
+ terminalsByStmt: ReadonlyMap<StructuredStatement<Cond>, readonly Terminal[]>;
1470
+ }
1471
+ interface StructuredPathConditionsResult<Cond, Terminal> {
1472
+ /** The paths to each terminal, keyed by the caller's own terminal handle. */
1473
+ byTerminal: Map<Terminal, ConditionInfo<Cond>[][]>;
1474
+ /** Condition lists for paths that fall through the end of the body. */
1475
+ fallthrough: ConditionInfo<Cond>[][];
1476
+ }
1477
+ /**
1478
+ * Work out the conditions on each path, for every StructuredStatement
1479
+ * reachable from `input.statements`. Throws `PathBudgetExceeded` when the
1480
+ * path count crosses the cap, or `UnmodeledFlow` when a switch or match
1481
+ * turns up that the enumeration does not cover (a stray break, an unsafe
1482
+ * fallthrough). The caller catches both and degrades to enclosure conditions
1483
+ * plus an opaque conjunct, rather than claiming something it did not read.
1484
+ */
1485
+ declare function enumerateStructuredPaths<Cond, Terminal>(input: StructuredPathConditionsInput<Cond, Terminal>): StructuredPathConditionsResult<Cond, Terminal>;
1486
+ /**
1487
+ * Enumerate, and when the engine gives up say so instead of throwing. Each
1488
+ * terminal comes back reachable under one condition nobody can read, which
1489
+ * keeps everything the caller already knew about it. A caller that can do
1490
+ * better, by walking the terminal's own ancestors for the conditions that
1491
+ * enclose it, should.
1492
+ */
1493
+ declare function enumerateOrDegrade<Cond, Terminal>(input: StructuredPathConditionsInput<Cond, Terminal>, terminals: Iterable<Terminal>): StructuredPathConditionsResult<Cond, Terminal> & {
1494
+ /** Why the engine gave up, or null when it read the whole body. */
1495
+ degraded: string | null;
1496
+ };
1497
+
1498
+ /**
1499
+ * The conditions every path to a terminal agrees on. A terminal reached more
1500
+ * than one way keeps only the shared ones, so a condition is never claimed
1501
+ * for a path without it.
1502
+ *
1503
+ * `readPredicate` turns the language's own expression handle into a
1504
+ * predicate. Without one every condition stays opaque, which is what a
1505
+ * caller that has no reader gets.
1506
+ */
1507
+ declare function sharedGatingConditions<Cond>(paths: readonly (readonly ConditionInfo<Cond>[])[] | undefined, readPredicate?: (expression: Cond) => Predicate): RawCondition[];
1508
+
1509
+ /** Anything with a stable id, which is what these key on. */
1510
+ interface Identified {
1511
+ readonly id: number;
1512
+ }
1513
+ /** A set of nodes, compared by id. */
1514
+ declare class IdSet<T extends Identified> implements Iterable<T> {
1515
+ private readonly byKey;
1516
+ constructor(nodes?: Iterable<T>);
1517
+ add(node: T): this;
1518
+ has(node: T): boolean;
1519
+ /** The node this set was built with, which is the caller's own handle for it. */
1520
+ get(node: T): T | undefined;
1521
+ get size(): number;
1522
+ [Symbol.iterator](): Iterator<T>;
1523
+ }
1524
+ /** A map keyed by node, compared by id. */
1525
+ declare class IdMap<T extends Identified, V> implements Iterable<[T, V]> {
1526
+ private readonly entries;
1527
+ set(node: T, value: V): this;
1528
+ get(node: T): V | undefined;
1529
+ has(node: T): boolean;
1530
+ get size(): number;
1531
+ [Symbol.iterator](): Iterator<[T, V]>;
1532
+ }
1533
+
1534
+ /**
1535
+ * The small surface every language adapter implements, whatever that
1536
+ * language's own tooling looks like underneath.
1537
+ *
1538
+ * Anything TypeScript-specific, such as the ts-morph Project a caller needs
1539
+ * for corroborate's sandboxed execution, belongs on that adapter's own type
1540
+ * rather than here. This interface never gets a field that mentions one
1541
+ * particular language's tooling.
1542
+ */
1543
+
1544
+ interface LanguageAdapter {
1545
+ /**
1546
+ * Extract summaries from a specific list of files. Returns a
1547
+ * Promise so an implementation can do concurrent I/O during
1548
+ * discovery without bottlenecking on synchronous reads.
1549
+ */
1550
+ extractFromFiles(filePaths: string[]): Promise<BehavioralSummary[]>;
1551
+ /** Extract summaries from every source file the adapter's project knows about. */
1552
+ extractAll(): Promise<BehavioralSummary[]>;
1553
+ }
1554
+
1555
+ /**
1556
+ * The assembly engine: a `RawCodeStructure` from a language adapter goes in,
1557
+ * a `BehavioralSummary` comes out. The package README explains where that step
1558
+ * fits in the pipeline.
1559
+ *
1560
+ * Two things surprise people reading this file. First, `RawCodeStructure` and
1561
+ * the raw types around it are the contract every adapter and pack implements,
1562
+ * so a field added here is a change to that contract. Second, this is the only
1563
+ * module allowed to turn a `Reading` into a claim on a summary; adapters hand
1564
+ * readings over uncollapsed and the rule for collapsing them lives here.
1565
+ */
1566
+
1067
1567
  interface RawParameter {
1068
1568
  name: string;
1069
1569
  position: number;
1070
- role: string;
1570
+ /**
1571
+ * What the parameter is for, in the library's own vocabulary. When the
1572
+ * adapter could not tell, this is null rather than a guess, and the reason
1573
+ * goes in `readings`.
1574
+ */
1575
+ role: string | null;
1071
1576
  typeText: string | null;
1072
1577
  }
1073
1578
  interface RawCondition {
1074
1579
  sourceText: string;
1075
1580
  structured: Predicate | null;
1076
1581
  polarity: "positive" | "negative";
1077
- source: "explicit" | "earlyReturn" | "earlyThrow" | "catchBlock";
1582
+ source: ConditionSource;
1078
1583
  }
1079
1584
  interface RawTerminal {
1080
1585
  kind: "response" | "throw" | "return" | "render" | "delegate" | "emit" | "void";
@@ -1091,18 +1596,10 @@ interface RawTerminal {
1091
1596
  } | null;
1092
1597
  exceptionType: string | null;
1093
1598
  message: string | null;
1094
- /** For render terminals: the component being rendered */
1095
1599
  component: string | null;
1096
- /**
1097
- * For render terminals (JSX / component-tree output): the nested
1098
- * render tree if the pack's extractor can produce one. Flows
1099
- * through to `Output.render.root`. Absent when the extractor sees
1100
- * only the root element name or opted out of tree extraction.
1101
- */
1600
+ /** Null when the pack read only the root element name, not the tree under it. */
1102
1601
  renderTree: RenderNode | null;
1103
- /** For delegate terminals: where control is passed */
1104
1602
  delegateTarget: string | null;
1105
- /** For emit terminals: the event/channel name */
1106
1603
  emitEvent: string | null;
1107
1604
  location: {
1108
1605
  start: number;
@@ -1110,22 +1607,9 @@ interface RawTerminal {
1110
1607
  };
1111
1608
  }
1112
1609
  /**
1113
- * A structured capture of an invocation argument. Literal values
1114
- * (string, number, boolean), object/array literals, template literals,
1115
- * identifier references, and nested call expressions are all captured
1116
- * as structured variants so readers can see *what* was passed even when
1117
- * the runtime value isn't statically resolvable. `null` is reserved for
1118
- * the rare case where the argument shape doesn't match any variant
1119
- * (type assertions with computed operands, arithmetic, etc.) — the
1120
- * caller still knows how many arguments the call had.
1121
- *
1122
- * Useful for recognising literal-string discriminators like
1123
- * `findings.push({ kind: "scenarioCoverageGap" })` or
1124
- * `dispatch({ type: "USER_LOGGED_IN" })` at the summary level, and for
1125
- * preserving argument *shape* (`{ userId, count }`, `getUser(id)`) so
1126
- * downstream consumers (AI agents, error-taxonomy tooling,
1127
- * release-note generators) can reason about composition without
1128
- * re-reading source.
1610
+ * An invocation argument as the adapter read it. An argument that fits none of
1611
+ * these variants, arithmetic for instance, comes through as null instead of
1612
+ * being dropped, so a reader still knows how many arguments the call had.
1129
1613
  */
1130
1614
  type EffectArg = {
1131
1615
  kind: "string";
@@ -1142,39 +1626,19 @@ type EffectArg = {
1142
1626
  } | {
1143
1627
  kind: "array";
1144
1628
  items: EffectArg[];
1145
- }
1146
- /**
1147
- * Template literal with interpolation (`` `Error: ${e.message}` ``).
1148
- * The resolved runtime value is unknown, but the source text is
1149
- * preserved so readers can see which variables compose it — useful
1150
- * for log messages, computed keys, and dedup-style key builders.
1151
- */
1152
- | {
1629
+ } | {
1153
1630
  kind: "template";
1154
1631
  sourceText: string;
1155
1632
  }
1156
1633
  /**
1157
- * Identifier reference — a bare variable (`userId`), a property-access
1158
- * chain (`user.profile.email`, `process.env.DATABASE_URL`), or an
1159
- * element-access chain (`config["host"]`). `name` holds the full
1160
- * source text so readers can tell which binding flowed into the call
1161
- * without inferring it from context. Identifiers that resolve to a
1162
- * module-level `const` with a simple initializer (literal, process.env
1163
- * access, etc.) are inlined to the initializer's EffectArg form so
1164
- * closure-over-constants doesn't hide the actual value at call sites.
1634
+ * A bare variable or a whole access chain, written out as it appears. An
1635
+ * identifier bound to a module-level const with a simple initializer is
1636
+ * replaced by that initializer, so a constant does not hide the value.
1165
1637
  */
1166
1638
  | {
1167
1639
  kind: "identifier";
1168
1640
  name: string;
1169
- }
1170
- /**
1171
- * Nested call expression passed as an argument — `log(formatError(e))`,
1172
- * `enqueue(buildPayload(ctx))`, inline composition. `callee` is the
1173
- * source text of the call target; `args` recurses with bounded depth,
1174
- * so `formatError(e)` reads as `{ callee: "formatError", args: [...] }`
1175
- * rather than an opaque null.
1176
- */
1177
- | {
1641
+ } | {
1178
1642
  kind: "call";
1179
1643
  callee: string;
1180
1644
  args: EffectArg[];
@@ -1188,13 +1652,7 @@ type RawEffect = {
1188
1652
  callee: string;
1189
1653
  args: EffectArg[];
1190
1654
  async: boolean;
1191
- /**
1192
- * Ancestor if/switch/ternary conditions that gate reaching this
1193
- * call. Populated for calls nested inside conditional blocks or
1194
- * loop bodies; empty for top-level (always-fires) calls.
1195
- * Converts to `Effect.preconditions: Predicate[]` in the IR —
1196
- * same RawCondition → Predicate pipeline transitions use.
1197
- */
1655
+ /** Empty means the call always fires, not that nobody looked. */
1198
1656
  preconditions?: RawCondition[];
1199
1657
  } | {
1200
1658
  type: "emission";
@@ -1208,14 +1666,8 @@ interface RawBranch {
1208
1666
  terminal: RawTerminal;
1209
1667
  effects: RawEffect[];
1210
1668
  /**
1211
- * Pre-typed `Effect`s emitted by `PatternPack.invocationRecognizers`.
1212
- * Bypass the `RawEffect → Effect` conversion that `effects` runs
1213
- * through — recognizers have full structural knowledge of what
1214
- * they're emitting (e.g. `interaction(class: "storage-access")` with
1215
- * binding identity + per-class fields/selector)
1216
- * and there's no narrower extractor-side representation worth
1217
- * round-tripping through. Concatenated with converted `effects` at
1218
- * `assembleSummary` time.
1669
+ * Effects a recognizer built itself, already in IR form, so these skip the
1670
+ * `RawEffect` conversion that `effects` goes through.
1219
1671
  */
1220
1672
  extraEffects?: Effect[];
1221
1673
  location: {
@@ -1223,14 +1675,20 @@ interface RawBranch {
1223
1675
  end: number;
1224
1676
  };
1225
1677
  isDefault: boolean;
1678
+ /** The fields a consumer reads off a response inside this branch. */
1679
+ expectedInput?: TypeShape | null;
1226
1680
  /**
1227
- * Shape of upstream data the code unit reads within this branch.
1228
- * Populated by the adapter for client/consumer code units: after
1229
- * branching on a response status, the consumer accesses fields on
1230
- * the response body — those accesses are collected into a TypeShape.
1231
- * The extractor copies this through to Transition.expectedInput.
1681
+ * An adapter that passes its reading along uncollapsed sets this instead of
1682
+ * `terminal.statusCode`. A status nobody wrote then becomes a claim only
1683
+ * where a pack declared the default.
1232
1684
  */
1233
- expectedInput?: TypeShape | null;
1685
+ statusCodeReading?: DefaultedReading<number>;
1686
+ /**
1687
+ * Set instead of `terminal.body.shape`, on the same terms.
1688
+ * `terminal.body.typeText` is left alone, so a pack that gives the type as
1689
+ * text and also reads its structure can do both.
1690
+ */
1691
+ bodyShapeReading?: DefaultedReading<TypeShape>;
1234
1692
  }
1235
1693
  interface RawDependencyCall {
1236
1694
  name: string;
@@ -1246,12 +1704,8 @@ interface RawDeclaredContract {
1246
1704
  framework: string;
1247
1705
  responses: Array<{
1248
1706
  statusCode: number;
1249
- /**
1250
- * Structured body schema the contract declares for this status code.
1251
- * Populated when the adapter can statically resolve the response schema
1252
- * (e.g. `c.type<T>()`'s type argument). Null when the contract exists
1253
- * but no body was declared or the schema form isn't yet supported.
1254
- */
1707
+ /** Null means either the contract declared no body or suss could not read
1708
+ * the schema form, and nothing here tells the two apart. */
1255
1709
  body?: TypeShape | null;
1256
1710
  }>;
1257
1711
  params?: Record<string, {
@@ -1259,54 +1713,33 @@ interface RawDeclaredContract {
1259
1713
  required: boolean;
1260
1714
  }>;
1261
1715
  /**
1262
- * Relationship between this declared contract and the `transitions[]`
1263
- * on the same summary:
1264
- *
1265
- * - "derived": both are extracted from the same source data, so
1266
- * comparing them against each other is tautological. Example: an
1267
- * OpenAPI stub's contract and its transitions both come from the
1268
- * operation's `responses` block. The cross-boundary checker
1269
- * skips per-summary contract-consistency for these.
1270
- *
1271
- * - "independent": the contract is a separate statement from the
1272
- * transitions. Example: a ts-rest handler whose router declares
1273
- * `responses` and whose implementation is a separate function;
1274
- * a CFN stub whose `MethodResponses` and integration config are
1275
- * independent template fields. Contract-consistency comparison
1276
- * is meaningful.
1277
- *
1278
- * Defaults to "independent" when a pack doesn't say — safer to
1279
- * surface a spurious-but-investigable finding than to silently drop
1280
- * a real one.
1716
+ * "derived" means the contract and the transitions both come from the same
1717
+ * source, so comparing them proves nothing and the checker skips it. A pack
1718
+ * that says nothing gets "independent", which risks a spurious finding
1719
+ * rather than dropping a valid one.
1281
1720
  */
1282
1721
  provenance?: "derived" | "independent";
1283
1722
  }
1284
1723
  /**
1285
- * What sits where a unit's body should be.
1724
+ * What the adapter found where a unit's body should be. Downstream this is the
1725
+ * difference between a summary that says nothing because there was nothing to
1726
+ * say and one that says nothing because nobody could read the body.
1286
1727
  *
1287
- * - "absent": there is no body behind this declaration (an overload
1288
- * signature, an ambient declaration, an abstract method), so the
1289
- * pack read nothing about what the unit does.
1290
- * - "empty": a body with nothing in it. A summary that says nothing
1291
- * about it has described it completely.
1292
- * - "statements": a body with work in it, including a concise
1293
- * arrow's expression. A summary that says nothing about it has
1294
- * described none of it.
1295
- * - "elsewhere": the unit's body is not in what this run read. A
1296
- * route registered with a handler the caller supplies announces a
1297
- * boundary and names no function to look in, and the boundary is
1298
- * worth reporting with nothing behind it.
1728
+ * "absent" is a declaration with no body at all, such as an overload
1729
+ * signature. "empty" is a body with nothing in it, which an empty summary
1730
+ * describes completely. "statements" is a body with work in it, which an empty
1731
+ * summary describes none of. "elsewhere" is a body this run did not read: a
1732
+ * route registered with a handler the caller supplies points at no function to
1733
+ * go look in.
1299
1734
  */
1300
1735
  type BodyContent = "absent" | "empty" | "statements" | "elsewhere";
1301
1736
  interface RawCodeStructure {
1302
- /**
1303
- * The types this unit's shapes name rather than spell out. Carried
1304
- * onto the summary as `definitions`, so a reader following a name has
1305
- * somewhere to look.
1306
- */
1737
+ /** Types the unit's shapes refer to by name instead of spelling out, so a
1738
+ * reader who follows one of those names has somewhere to look. */
1307
1739
  definitions?: Record<string, TypeShape> | null;
1308
1740
  identity: {
1309
1741
  name: string;
1742
+ nameKind?: "binding" | "label";
1310
1743
  kind: CodeUnitKind;
1311
1744
  file: string;
1312
1745
  range: {
@@ -1316,113 +1749,71 @@ interface RawCodeStructure {
1316
1749
  exportName: string | null;
1317
1750
  exportPath: string[] | null;
1318
1751
  };
1319
- /**
1320
- * Three-layer boundary description (`transport`, `semantics`,
1321
- * `recognition`). See `@suss/behavioral-ir`'s `BoundaryBinding`.
1322
- * Null when the unit participates in no cross-unit boundary —
1323
- * helpers, pure utilities, and anything the adapter can't place.
1324
- */
1752
+ /** Null when the unit is not on any cross-unit boundary, which is the
1753
+ * ordinary case for helpers and pure utilities. */
1325
1754
  boundaryBinding: BoundaryBinding | null;
1326
- /** The thing that gets deployed and runs this code, when known. */
1327
1755
  deployableUnit?: DeployableUnit;
1328
1756
  parameters: RawParameter[];
1329
1757
  branches: RawBranch[];
1330
- /**
1331
- * Return statements in the body that none of the pack's terminals
1332
- * matched. A handler shaped in a way the pack does not describe still
1333
- * returns something, and saying nothing about it reads the same as
1334
- * saying it does nothing.
1335
- */
1758
+ /** Return statements no terminal in the pack matched. Leave this at zero and
1759
+ * a handler the pack cannot describe looks like one that returns nothing. */
1336
1760
  unmatchedReturns?: number;
1337
1761
  /**
1338
- * What the adapter found where the unit's body should be. A summary
1339
- * with nothing in it means one of two different things, and only the
1340
- * adapter can say which: a body nobody could read, or a body with
1341
- * nothing in it to read. Adapters that do not say leave this unset,
1342
- * and everything downstream treats the summary as it did before.
1762
+ * Only the adapter can tell a body nobody could read from a body with
1763
+ * nothing in it, and an empty summary looks the same either way.
1343
1764
  */
1344
1765
  bodyContent?: BodyContent;
1345
1766
  dependencyCalls: RawDependencyCall[];
1346
1767
  declaredContract: RawDeclaredContract | null;
1347
- /**
1348
- * Names of pack-declared response properties whose semantics is `body`.
1349
- * For client units this records the accessor a consumer uses to reach the
1350
- * body (e.g. ["data"] for axios, ["body","json","text"] for fetch). Carried
1351
- * through to `summary.metadata.http.bodyAccessors` so the cross-boundary
1352
- * checker can unwrap consumer expectedInput correctly without knowing each
1353
- * pack. Flat here because this is the adapter→extractor plumbing contract;
1354
- * nesting under `http` happens when the summary is assembled.
1355
- */
1768
+ /** The property a consumer goes through to get at the body, `data` for
1769
+ * axios say, so the checker can unwrap it without knowing each pack. */
1356
1770
  bodyAccessors?: string[];
1357
- /**
1358
- * Names of pack-declared response properties whose semantics is
1359
- * `statusCode`. Same shape as `bodyAccessors` but for the status side —
1360
- * lets the checker recognise pack-specific names (e.g. fetch's `status`,
1361
- * axios's `status`, hypothetical `responseStatus`) when matching consumer
1362
- * branch conditions to provider transitions. Carried through to
1363
- * `summary.metadata.http.statusAccessors`.
1364
- */
1771
+ /** The same, for the status: `status` for fetch and for axios. */
1365
1772
  statusAccessors?: string[];
1366
- /**
1367
- * Raw GraphQL document source attached to consumer-side graphql
1368
- * summaries (the inner text of a `useQuery(gql\`...\`)` call).
1369
- * Assembled into `summary.metadata.graphql.document` for the
1370
- * checker's pairing layer to parse. Deliberately untouched here —
1371
- * the extractor has no dependency on graphql-js; parsing happens
1372
- * at check time.
1373
- */
1773
+ /** Left exactly as written, because the extractor does not depend on
1774
+ * graphql-js. The parsing happens at check time. */
1374
1775
  graphqlDocument?: string;
1375
- /**
1376
- * Raw GraphQL schema SDL attached to provider-side resolver
1377
- * summaries. For schema-first stubs (AppSync), the SDL IS the
1378
- * schema. For code-first frameworks (Apollo Server), the SDL
1379
- * comes from the `typeDefs` config option when statically
1380
- * resolvable. Surfaced as `summary.metadata.graphql.schemaSdl`
1381
- * so the checker can use the return-type field set when pairing
1382
- * nested consumer selections.
1383
- */
1384
1776
  graphqlSchemaSdl?: string;
1385
- /**
1386
- * Declared contract derived from the SDL field for this resolver:
1387
- * `{ returnType, args, provenance, framework }`. Surfaced as
1388
- * `summary.metadata.graphql.declaredContract` so
1389
- * `checkGraphqlContractAgreement` can pair it against other
1390
- * sources declaring a contract for the same boundary.
1391
- *
1392
- * The extractor doesn't derive this — adapters that have the SDL
1393
- * AND know the (typeName, fieldName) of the resolver populate it.
1394
- * Today: the TS adapter does this for Apollo-style resolverMap
1395
- * discovery. NestJS GraphQL (decorator-based) is a follow-up.
1396
- */
1397
- graphqlDeclaredContract?: Record<string, unknown>;
1398
- /**
1399
- * Set on a consumer-side GraphQL summary whose document reference was
1400
- * recognized (an imported `TypedDocumentNode` from graphql-codegen,
1401
- * say) but whose body couldn't be read statically. Surfaced as
1402
- * `summary.metadata.graphql.unresolvedDocument` so the unreadable
1403
- * document is accounted for rather than silently dropped. `reference`
1404
- * is the identifier passed to the hook / imperative call; `reason`
1405
- * explains what defeated static resolution.
1406
- */
1777
+ /** The extractor cannot derive this. An adapter that has the SDL and knows
1778
+ * which field the resolver serves fills it in. */
1779
+ graphqlDeclaredContract?: GraphqlDeclaredContract;
1780
+ /** Set when a document reference was recognized but its body could not be
1781
+ * read, so an unreadable document is accounted for instead of dropped. */
1407
1782
  graphqlUnresolvedDocument?: {
1408
1783
  reference: string;
1409
1784
  reason: string;
1410
1785
  };
1411
1786
  /**
1412
- * Set when the source does not state part of the boundary this unit
1413
- * sits on. The binding still goes out with that part left empty, so
1414
- * the unit pairs with nothing rather than with whatever a guess named,
1415
- * and this sentence tells a reader which part is missing and why.
1416
- *
1417
- * A statement about the reading rather than about the code, so it
1418
- * lands as an `unreadOutcome` gap and no checker holds it against the
1419
- * unit.
1787
+ * Which part of the boundary the source does not state, and why. The binding
1788
+ * still goes out with that part empty, so the unit pairs with nothing rather
1789
+ * than with whatever a guess would have supplied. It comes out as an
1790
+ * `unreadOutcome` gap, so no checker counts it against the unit.
1420
1791
  */
1421
1792
  unreadBinding?: string;
1793
+ /**
1794
+ * Readings the adapter passed along without collapsing. This module writes
1795
+ * the reason for any that came back unreadable or ambiguous. Written and
1796
+ * absent readings contribute nothing here, since what they found is already
1797
+ * on the summary.
1798
+ */
1799
+ readings?: readonly Reading<unknown>[];
1422
1800
  }
1423
1801
  interface ExtractorOptions {
1424
1802
  gapHandling: "strict" | "permissive" | "silent";
1425
1803
  }
1804
+ /**
1805
+ * The id for one branch's transition, built so that editing a handler does not
1806
+ * churn it. Reordering branches, or adding an unrelated one, must leave the
1807
+ * existing ids alone, otherwise `diffSummaries` reports "everything changed"
1808
+ * every time somebody shuffles a handler around.
1809
+ *
1810
+ * The id combines the enclosing function name, the terminal kind, the status
1811
+ * code (a literal value, the source text of a dynamic one, or "none"), and a
1812
+ * short hash of the condition chain's source texts. Editing a branch's body
1813
+ * without touching its guards or its status leaves the id alone, so a diff
1814
+ * reports one changed transition instead of an add plus a remove. Change any
1815
+ * of those signals and you get a new id.
1816
+ */
1426
1817
  declare function makeTransitionId(functionName: string, branch: RawBranch): string;
1427
1818
  declare function assembleSummary(raw: RawCodeStructure, options?: ExtractorOptions): BehavioralSummary;
1428
1819
  declare function detectGaps(raw: RawCodeStructure, transitions: Transition[], options: ExtractorOptions): Gap[];
@@ -1431,4 +1822,4 @@ declare function terminalToOutput(terminal: RawTerminal): Output;
1431
1822
  declare function effectToIR(effect: RawEffect): Effect;
1432
1823
  declare function paramToInput(param: RawParameter): Input;
1433
1824
 
1434
- export { type AccessRecognizer, type BindingExtraction, type BodyContent, type ContractPattern, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type ExtractorOptions, type InputMappingPattern, type InvocationRecognizer, type PatternPack, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type ResponsePropertyMapping, type ResponsePropertyMeaning, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type TransparentWrapper, assembleSummary, assessConfidence, detectGaps, effectToIR, httpRouteDiscovery, makeTransitionId, paramToInput, terminalToOutput };
1825
+ export { type AccessRecognizer, type BindingExtraction, type BodyContent, type CaseGroup, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ContractPattern, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type ExitKind, type ExtractorOptions, IdMap, IdSet, type Identified, type InputMappingPattern, type InvocationRecognizer, type LanguageAdapter, PathBudgetExceeded, type PatternPack, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ResponsePropertyMapping, type ResponsePropertyMeaning, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type TransparentWrapper, UnmodeledFlow, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, detectGaps, effectToIR, enumerateOrDegrade, enumerateStructuredPaths, firstWrittenReading, httpRouteDiscovery, makeTransitionId, mapReading, paramToInput, sharedGatingConditions, terminalToOutput, unreadableReading, valueToReadFurtherFrom, writtenReading };