@suss/extractor 0.33.1 → 0.34.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.ts CHANGED
@@ -1,17 +1,17 @@
1
- import { Effect, MessageBusSemantics, DeployableUnit, CodeUnitKind, RequestSpellingMetadata, BehavioralSummary, Predicate, TypeShape, RenderNode, BoundaryBinding, WrapperReference, GraphqlDeclaredContract, ConfidenceInfo, Transition, Gap, Input, Output } from '@suss/behavioral-ir';
1
+ import { Effect, MessageBusSemantics, DeployableUnit, CodeUnitKind, RequestSpellingMetadata, BehavioralSummary, Predicate, ValueRef, TypeShape, RenderNode, ProvenanceSlot, BoundaryBinding, WrapperReference, TestMetadata, GraphqlDeclaredContract, ConfidenceInfo, Transition, Gap, Input, Output } from '@suss/behavioral-ir';
2
2
  import { z } from 'zod';
3
3
 
4
4
  /**
5
- * What a unit reads out of what it was given.
5
+ * What a unit reads out of its inputs.
6
6
  *
7
7
  * A summary already describes this, but only as a chain of `derived` nodes
8
8
  * nested one inside the next, which anyone who wants the list has to walk
9
9
  * themselves. The `path` on an input reference is empty, so querying the
10
10
  * field that looks like the answer gives nothing back.
11
11
  *
12
- * This module flattens that into the list directly: everything a unit reaches
13
- * for through its inputs, once each, in the order somebody would say them out
14
- * loud. It goes on the summary as `inputReads`.
12
+ * This module flattens those chains into a list of everything a unit reads
13
+ * through its inputs, once each, sorted by input and then by path. The list
14
+ * goes on the summary as `inputReads`.
15
15
  */
16
16
 
17
17
  /** One thing a unit read, and the way it reached it. */
@@ -31,10 +31,10 @@ interface InputRead {
31
31
  * is a parameter. The call site has the literals and no library; the
32
32
  * helper body has the library and no literals.
33
33
  *
34
- * A pack asks here for the helper to be read once, over the whole
35
- * project, before any file is walked. What comes back says what the
36
- * body does in terms of the helper's own parameters, and the pack turns
37
- * that into the patterns and recognizers it used to take as config.
34
+ * A pack asks for the helper to be read once, over the whole project,
35
+ * before any file is walked. What comes back says what the body does in
36
+ * terms of the helper's own parameters, and the pack turns that into
37
+ * discovery patterns and invocation recognizers.
38
38
  */
39
39
 
40
40
  /**
@@ -45,8 +45,8 @@ interface InputRead {
45
45
  */
46
46
  type HelperValue =
47
47
  /**
48
- * A string, with `{N}` standing where parameter N was interpolated.
49
- * A literal with no interpolation is the literal itself.
48
+ * A string, with `{N}` wherever parameter N was interpolated. A
49
+ * literal with no interpolation comes back unchanged.
50
50
  */
51
51
  {
52
52
  as: "text";
@@ -83,11 +83,11 @@ interface HelperSink {
83
83
  }
84
84
  /** A function the project declares, as the index read it. */
85
85
  interface ProjectHelper {
86
- /** What the declaration calls it, which is what a call site writes. */
86
+ /** The declared name, which is the name call sites use. */
87
87
  name: string;
88
88
  /** Absolute path of the file declaring it. */
89
89
  file: string;
90
- /** Its parameters, in order, by name. */
90
+ /** Parameter names in declaration order. */
91
91
  parameters: string[];
92
92
  /**
93
93
  * Parameters some caller handed this pack's own value to. Empty when
@@ -100,11 +100,11 @@ interface ProjectHelper {
100
100
  /**
101
101
  * How the index picks out which of the project's functions to read.
102
102
  *
103
- * `subject` starts at a call site: a function the project hands one of
104
- * this pack's own values to is a helper of this pack's, whatever the
103
+ * `subject` starts at a call site: a function the project passes one of
104
+ * this pack's own values to is one of the pack's helpers, whatever the
105
105
  * helper's file imports. `text` starts at the body, for a pack whose
106
106
  * library is reached over the wire and has no import to look for. Both
107
- * come from the library itself, which is the bar `requiresImport` meets.
107
+ * searches use only facts about the library, as `requiresImport` does.
108
108
  */
109
109
  type HelperSearch = {
110
110
  by: "subject";
@@ -117,30 +117,30 @@ interface HelperDeclarations {
117
117
  discovery?: DiscoveryPattern[];
118
118
  invocationRecognizers?: InvocationRecognizer[];
119
119
  }
120
- /** A pack's standing request to have the project's helpers read. */
120
+ /** A pack's request to have the project's helpers read before extraction. */
121
121
  interface ProjectHelpers {
122
122
  find: HelperSearch;
123
123
  /**
124
- * Turned into patterns and recognizers before the first file is
125
- * walked. It is handed data, never an AST, so a pack that declares
126
- * one still runs on any adapter that implements the reading.
124
+ * Called once, before the first file is walked, to turn the helpers
125
+ * into patterns and recognizers. It receives data and never an AST, so
126
+ * a pack that declares it runs on any adapter that reads helpers.
127
127
  */
128
128
  declare(helpers: readonly ProjectHelper[]): HelperDeclarations;
129
129
  }
130
130
 
131
131
  /**
132
- * The `PatternPack` interface, which is what a framework pack gives a language
133
- * adapter. The pack says WHAT to look for (which import, which call, which
134
- * decorator); the adapter knows HOW to find that in its language's AST.
132
+ * The `PatternPack` interface a framework pack hands to a language adapter.
133
+ * The pack describes what to look for (an import, a call, a decorator), and
134
+ * the adapter finds it in its own language's syntax tree.
135
135
  *
136
- * Everything here is data, never code. A pack describes a library once and any
137
- * adapter that understands these patterns can apply it. If you want a pack to
138
- * compute something, the answer is usually another declarative field here.
136
+ * Most of a pack is data, so one pack description works with any adapter
137
+ * that reads these patterns. When a pack needs to compute something, the
138
+ * usual fix is another declarative field here. The function hooks
139
+ * (`subUnits`, `discoverUnits` and the recognizers) cover what data cannot.
139
140
  *
140
- * The sections run in the order an adapter uses them: discovery finds candidate
141
- * code units, terminals describe how a unit finishes, contract reading and
142
- * input mapping describe what it declares and takes in, and response property
143
- * semantics say which property is the body and which is the status.
141
+ * Sections follow the order an adapter uses them. The pack patterns
142
+ * reference at https://suss.sh/packs/patterns shows each variant with the
143
+ * code it matches.
144
144
  */
145
145
 
146
146
  type DiscoveryMatch = {
@@ -165,21 +165,18 @@ type DiscoveryMatch = {
165
165
  * Unset means any method call (or bare call for globals). */
166
166
  methodFilter?: string[];
167
167
  /**
168
- * Method names on the import that produce a client-equivalent instance,
169
- * so variables initialized from those calls also act as discovery
170
- * subjects. axios uses `axios.create({...})` to build a baseURL-bound
171
- * instance; declaring `factoryMethods: ["create"]` lets the adapter
172
- * treat `api.get(...)` (where `api = axios.create(...)`) the same as
173
- * `axios.get(...)`.
168
+ * Methods on the import that build a client instance. A variable
169
+ * initialized from one of these calls is a client too, so with
170
+ * `factoryMethods: ["create"]`, `api.get(...)` after
171
+ * `api = axios.create(...)` matches the same as `axios.get(...)`.
174
172
  */
175
173
  factoryMethods?: string[];
176
174
  /**
177
- * The property of the factory call's config object that every
178
- * request through the instance is sent under, `baseURL` for axios.
179
- * Both sides of a boundary have to read one route the same way,
180
- * and a spec's `servers[0].url` already goes in front of the
181
- * provider's paths, so a base written here goes in front of the
182
- * consumer's.
175
+ * The option on the factory call's config that sets a base path for
176
+ * every request through the instance, `baseURL` for axios. The
177
+ * adapter puts it in front of each call's path, so the consumer's
178
+ * route lines up with a spec whose `servers[0].url` adds the same
179
+ * prefix to the provider's paths.
183
180
  */
184
181
  basePathOption?: string;
185
182
  /**
@@ -190,88 +187,54 @@ type DiscoveryMatch = {
190
187
  callable?: boolean;
191
188
  } | {
192
189
  /**
193
- * A constructor or factory call that takes a configuration object
194
- * containing a resolver map, which is how code-first GraphQL servers
195
- * are usually written. The map is two levels deep: outer keys are
196
- * GraphQL type names (`Query`, `Mutation`, `Subscription`, or
197
- * object-type names like `User`), and inner keys are field names
198
- * whose values are resolver functions.
190
+ * A constructor or factory call whose config object contains a
191
+ * two-level resolver map, the usual way to write a code-first GraphQL
192
+ * server. Outer keys are type names and inner keys are field names:
199
193
  *
200
- * Example (Apollo Server v4):
201
194
  * ```ts
202
- * new ApolloServer({
203
- * typeDefs,
204
- * resolvers: {
205
- * Query: { users: async () => {...} },
206
- * Mutation: { createUser: async (_, {input}) => {...} },
207
- * User: { fullName: (parent) => `${parent.first} ${parent.last}` },
208
- * },
209
- * });
195
+ * new ApolloServer({ typeDefs, resolvers: { Query: { users: async () => {...} } } });
210
196
  * ```
211
197
  *
212
- * Each inner function becomes one discovered unit whose binding
213
- * semantics is `graphql-resolver(typeName, fieldName)`. Both
214
- * `new Ctor(cfg)` and `ctor(cfg)` match, because Apollo's standalone
215
- * server uses `new` and yoga uses a bare call.
198
+ * Each inner function becomes one unit bound as
199
+ * `graphql-resolver(typeName, fieldName)`. Both `new Ctor(cfg)` and
200
+ * `ctor(cfg)` match, since Apollo's standalone server uses `new` and
201
+ * yoga uses a bare call.
216
202
  */
217
203
  type: "resolverMap";
218
204
  importModule: string;
219
205
  importName: string;
220
206
  /**
221
- * The property on the config object that contains the resolver map.
222
- * This is the library's own config key, so the pack has to give it and
223
- * the adapter ships no default. Apollo, yoga, and graphql-tools all
224
- * spell it `"resolvers"`.
207
+ * The config property that contains the resolver map. The key belongs
208
+ * to the library, so the adapter has no default. Apollo, yoga and
209
+ * graphql-tools all use `"resolvers"`.
225
210
  */
226
211
  mapProperty: string;
227
212
  /**
228
- * GraphQL types whose fields we DON'T treat as resolvers. This is the
229
- * opt-out for meta-types like `Subscription` that we may want to handle
230
- * differently later. Leave it unset to discover every type.
213
+ * Types whose fields are not discovered as resolvers, such as
214
+ * `Subscription`. Leave it unset to discover every type.
231
215
  */
232
216
  excludeTypes?: string[];
233
217
  } | {
234
218
  /**
235
219
  * A consumer-side GraphQL hook call, the way Apollo Client and urql
236
- * are normally used. Each call to one of the listed hooks becomes
237
- * a `client`-kind code unit whose binding semantics is
238
- * `graphql-operation(operationType, operationName?)`.
220
+ * are normally used. Each call to a listed hook becomes a `client`
221
+ * unit bound as `graphql-operation(operationType, operationName?)`.
239
222
  *
240
- * The document argument can be written several ways: an inline
241
- * `gql`-tagged template, a const binding (in this module or imported
242
- * from another one), a `.graphql` or `.gql` file import, or a
243
- * generated `TypedDocumentNode` object literal from graphql-codegen
244
- * client-preset. When the document body cannot be read statically,
245
- * the operation header falls back to the `TypedDocumentNode` type
246
- * arguments. A document that still cannot be resolved shows up on the
247
- * summary as `metadata.graphql.unresolvedDocument`, so the boundary is
248
- * kept rather than dropped.
249
- *
250
- * Example:
251
- * ```ts
252
- * import { gql, useQuery } from "@apollo/client";
253
- * const GET_USER = gql`query GetUser($id: ID!) { user(id: $id) { id } }`;
254
- * function UserPage({ id }) {
255
- * const { data } = useQuery(GET_USER, { variables: { id } });
256
- * ...
257
- * }
258
- * ```
259
- *
260
- * The adapter records the operation name and type on the
261
- * DiscoveredUnit's `operationInfo`, and binding construction uses that
262
- * to emit `graphql-operation(...)`. The per-hook `operationType`
263
- * wins when the document header cannot be read, the same way
264
- * `graphqlImperativeCall.methods` does.
223
+ * The document can be an inline `gql` template, a const in this
224
+ * module or an imported one, a `.graphql` or `.gql` file import, or a
225
+ * `TypedDocumentNode` from graphql-codegen. When the body cannot be
226
+ * read, the operation header comes from the `TypedDocumentNode` type
227
+ * arguments. A document that still cannot be resolved is kept on the
228
+ * summary as `metadata.graphql.unresolvedDocument`, so the boundary
229
+ * stays.
265
230
  */
266
231
  type: "graphqlHookCall";
267
232
  importModule: string;
268
233
  /**
269
- * Hooks to match on that import, each mapped to the operation type it
270
- * performs (`useQuery` to query, `useMutation` to mutation,
271
- * `useSubscription` to subscription). That mapping supplies the
272
- * operation type when the document body cannot be read statically.
273
- * Each hook is reported as `kind = "client"` unless a pack overrides
274
- * that through the enclosing `DiscoveryPattern.kind`.
234
+ * Hooks on that import, each with the operation type it performs
235
+ * (`useQuery` performs a query). The type is used when the document's
236
+ * header cannot be read. A matched call is a `client` unit unless the
237
+ * enclosing `DiscoveryPattern.kind` says otherwise.
275
238
  */
276
239
  hooks: Array<{
277
240
  hookName: string;
@@ -279,21 +242,17 @@ type DiscoveryMatch = {
279
242
  }>;
280
243
  } | {
281
244
  /**
282
- * An imperative Apollo-Client-style call: `client.query({ query })`,
283
- * `client.mutate({ mutation })`, `client.subscribe({ query })`. This is
284
- * separate from hook calls because the document is on a config-object
285
- * property rather than the first positional argument.
245
+ * An imperative Apollo Client call: `client.query({ query })`,
246
+ * `client.mutate({ mutation })` or `client.subscribe({ query })`. The
247
+ * document is on a config property instead of the first argument,
248
+ * which is why this is separate from `graphqlHookCall`.
286
249
  *
287
- * Discovery only fires when the named constructor (usually
288
- * `ApolloClient`) is imported, because otherwise any object at all
289
- * with a `query` method would look like a match.
290
- *
291
- * Each entry in `methods` specifies the method called on the client
292
- * (`"query"`, `"mutate"`, or `"subscribe"`) and the config-object
293
- * property that contains the gql document
294
- * (`"query"`, `"mutation"`, and `"query"` respectively). The method
295
- * name decides the operation type when the gql document's header is
296
- * anonymous. When the document has a name, its header wins.
250
+ * Discovery fires only when the named constructor (usually
251
+ * `ApolloClient`) is imported, since otherwise any object with a
252
+ * `query` method would match. Each `methods` entry gives the method,
253
+ * the config property with the document on it, and the operation type
254
+ * to use when the document's header is anonymous. A named header takes
255
+ * precedence.
297
256
  */
298
257
  type: "graphqlImperativeCall";
299
258
  importModule: string;
@@ -305,41 +264,29 @@ type DiscoveryMatch = {
305
264
  }>;
306
265
  } | {
307
266
  /**
308
- * Treats a TypeScript package's public export surface as a
309
- * boundary. The adapter reads `package.json` at `packageJsonPath`,
310
- * resolves each reachable entry point (root `.` and any sub-path
311
- * `exports`), follows barrel re-exports, and emits one discovered
312
- * unit per exported function. Those units are the provider side of
313
- * an in-process `function-call` boundary.
314
- *
315
- * The bindings this produces have the identity
316
- * `{ transport: "in-process",
317
- * semantics: { name: "function-call",
318
- * package: <pkg.name>,
319
- * exportPath: [...] },
320
- * recognition: <pack.name> }`.
321
- *
322
- * A sub-path export is identified as, for example,
323
- * `@suss/behavioral-ir/schemas::BehavioralSummarySchema`, giving
324
- * `exportPath = ["schemas", "BehavioralSummarySchema"]`. A root export
325
- * leaves the sub-path segment out.
267
+ * A TypeScript package's public exports, as the provider side of an
268
+ * in-process `function-call` boundary. The adapter reads
269
+ * `package.json`, resolves each entry point (the root `.` and any
270
+ * sub-path in `exports`), follows barrel re-exports, and emits one
271
+ * unit per exported function.
326
272
  *
327
- * As of v0 this resolves the `types`, `default`, and `import`
328
- * conditions on `exports`, and falls back to `types`, `main`, `module`
329
- * when there is no `exports` field. Pattern exports (`./utils/*`) and
330
- * `development` conditions are not handled yet.
273
+ * Each binding is keyed by package and export path, so
274
+ * `@suss/behavioral-ir/schemas::BehavioralSummarySchema` has
275
+ * `exportPath = ["schemas", "BehavioralSummarySchema"]`. The `types`,
276
+ * `default` and `import` conditions are resolved, with `types`, `main`
277
+ * and `module` as the fallback when there is no `exports` field.
278
+ * Pattern exports (`./utils/*`) and `development` conditions are not.
331
279
  */
332
280
  type: "packageExports";
333
281
  /**
334
282
  * Absolute path to the package's `package.json`. Left out when
335
- * `workspaces` is set, since the workspace decides the list.
283
+ * `workspaces` is set, since the workspace manifest lists the packages.
336
284
  */
337
285
  packageJsonPath?: string;
338
286
  /**
339
- * One pattern for every package the workspace declares. The pack
340
- * cannot list the packages, because they belong to the project
341
- * rather than to any library, so the adapter reads the workspace
342
- * manifest and applies this pattern once per package it finds.
287
+ * Apply the pattern to every package the workspace declares. Those
288
+ * packages belong to the project, so the pack cannot list them; the
289
+ * adapter reads the workspace manifest and applies the pattern to each.
343
290
  */
344
291
  workspaces?: true;
345
292
  /**
@@ -355,113 +302,113 @@ type DiscoveryMatch = {
355
302
  excludeNames?: string[];
356
303
  } | {
357
304
  /**
358
- * Class methods with a particular decorator, on classes with a
359
- * particular class-level decorator. NestJS-style frameworks work this
360
- * way: resolvers, handlers, and controllers are declared by decorator
361
- * rather than by registering a function in an object literal.
362
- *
363
- * Discovery only fires when `classDecorator` and each
364
- * `methodDecorators` entry come from `importModule`, so a user-defined
365
- * decorator that happens to share a name will not match.
366
- *
367
- * For a NestJS GraphQL pack:
368
- * `{ importModule: "@nestjs/graphql",
369
- * classDecorator: "Resolver",
370
- * methodDecorators: ["Query", "Mutation", "ResolveField",
371
- * "Subscription"] }`
305
+ * The public exports of one module the project lists in `suss.json`,
306
+ * read the way `packageExports` reads a package's entry points: one
307
+ * unit per exported function, class methods surfaced, barrels
308
+ * followed. Each binding is keyed `fn:<module>::<exportName>`. The
309
+ * adapter writes these patterns from the module list, so no pack
310
+ * declares one.
311
+ */
312
+ type: "moduleSurface";
313
+ /** The module's name, as `suss.json` gives it. */
314
+ module: string;
315
+ /** Absolute paths of the files whose exports are the surface. */
316
+ publicFiles: string[];
317
+ } | {
318
+ /**
319
+ * Methods with one of `methodDecorators` on a class with one of
320
+ * `classDecorators`, the way NestJS declares resolvers. At least one
321
+ * method decorator has to be imported from `importModule`, so a
322
+ * project decorator that only shares a name does not match. A NestJS
323
+ * GraphQL pack sets `classDecorators: ["Resolver"]` and
324
+ * `methodDecorators: ["Query", "Mutation", "ResolveField", "Subscription"]`.
372
325
  *
373
- * The adapter fills in `DiscoveredUnit.resolverInfo` so the binding
374
- * comes out as `graphql-resolver(typeName, fieldName)`.
375
- * `typeName` resolves from `methodDecoratorTypeMap` when the
376
- * method decorator is in it, and otherwise from the class
377
- * decorator's first argument (`@Resolver(() => User)` gives
378
- * `"User"`). `fieldName` comes from the method decorator's `{ name }`
379
- * option when that is set, and otherwise from the method name.
326
+ * The binding comes out as `graphql-resolver(typeName, fieldName)`.
327
+ * `typeName` comes from `methodDecoratorTypeMap` when the method
328
+ * decorator is in it, and otherwise from the class decorator's first
329
+ * argument (`@Resolver(() => User)` gives `"User"`). `fieldName` comes
330
+ * from the method decorator's `{ name }` option, or the method name.
380
331
  */
381
332
  type: "decoratedMethod";
382
333
  /**
383
- * The module a decorator has to be imported from before discovery will
384
- * fire. Codebases sometimes re-export a framework decorator wrapped
385
- * with extra metadata of their own, and checking one module would miss
386
- * those. Pass an array of acceptable modules and any one of them
387
- * matching is enough.
334
+ * The modules a decorator may be imported from. A project that wraps
335
+ * a framework decorator and re-exports it needs its own module listed
336
+ * too. Any one module in the list matching is enough.
388
337
  */
389
338
  importModule: string | string[];
390
339
  /**
391
- * Class decorators to recognise. The first one that appears on a class
392
- * is the one typeName is read from; the rest are fallbacks for
393
- * codebases with several wrapper styles. A pack ships only what its own
394
- * framework declares here, and takes a project's own wrappers through
395
- * its options instead.
340
+ * Class decorators to match. The first one found on a class supplies
341
+ * `typeName`, and the rest are fallbacks for projects with several
342
+ * wrapper styles. A pack lists only its own framework's decorators and
343
+ * takes a project's wrappers through its options.
396
344
  */
397
345
  classDecorators: string[];
398
346
  methodDecorators: string[];
399
347
  /**
400
- * Maps a method decorator to the type its field belongs to, for the
401
- * decorators that settle it. NestJS puts `@Query` on the root
402
- * `Query` type and `@Mutation` on `Mutation` no matter what the class
403
- * says, so an entry here wins over the class decorator's argument.
348
+ * Maps a method decorator to the type its field belongs to. NestJS
349
+ * puts `@Query` on the root `Query` type whatever the class says, so an
350
+ * entry here takes precedence over the class decorator's argument.
404
351
  *
405
- * When the map leaves a decorator out and the class decorator gives no
406
- * type either, nothing here works out which type owns the field. The
407
- * binding then goes out with no type and pairs with nothing, instead
408
- * of claiming a field the schema does not have.
352
+ * When neither the map nor the class decorator gives a type, the
353
+ * binding goes out with no type and pairs with nothing. That is safer
354
+ * than claiming a field the schema does not have.
409
355
  */
410
356
  methodDecoratorTypeMap: Record<string, string>;
411
357
  } | {
412
358
  /**
413
- * NestJS-style REST controller discovery: a class decorated with
414
- * `@Controller(pathPrefix?)`, and methods decorated with
415
- * `@Get(subpath?)`, `@Post`, `@Put`, `@Delete`, and so on. The
416
- * decorator's NAME is what determines the HTTP method, through
417
- * `methodDecoratorRouteMap`. The route path is the class decorator's
418
- * first argument joined with a slash to the method decorator's first
419
- * argument, and both of those are optional.
359
+ * A NestJS-style REST controller: a class decorated with
360
+ * `@Controller(prefix?)` and methods decorated with `@Get(subpath?)`,
361
+ * `@Post` and so on. The method decorator's name gives the HTTP method
362
+ * through `methodDecoratorRouteMap`. The path is the class decorator's
363
+ * first argument joined to the method decorator's, and both are
364
+ * optional. The binding comes out as `rest(method, path)`.
420
365
  *
421
- * Wrapper decorators are tolerated the same way as in
422
- * `decoratedMethod`: at least one method-route decorator has to come
423
- * from the framework module, but class decorators
424
- * are matched by name alone, so a project's own wrapper around the
425
- * framework's decorator matches once the project lists it in the
426
- * pack's options.
427
- *
428
- * The adapter fills in `DiscoveredUnit.routeInfo` so the binding comes
429
- * out as `rest(method, path)`.
366
+ * At least one route decorator on a method has to be imported from
367
+ * `importModule`. Class decorators match by name alone, so a project's
368
+ * own wrapper matches once the project lists it in the pack's options.
430
369
  */
431
370
  type: "decoratedRoute";
432
371
  importModule: string | string[];
433
372
  classDecorators: string[];
434
373
  /**
435
- * Maps a decorator to an HTTP method. NestJS uses one decorator per
436
- * verb (`@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`, `@Options`,
437
- * `@Head`, `@All`), and other frameworks may do the same. The values
438
- * become the `method` field on the REST binding, and `"*"` is fine for
439
- * a catch-all decorator.
374
+ * Decorator name to HTTP method, one entry per verb decorator. The
375
+ * value becomes the REST binding's `method`; use `"*"` for a
376
+ * catch-all decorator such as NestJS's `@All`.
440
377
  */
441
378
  methodDecoratorRouteMap: Record<string, string>;
379
+ /**
380
+ * The status the framework sends when a handler returns without
381
+ * stating one, keyed by method decorator, for the decorators whose
382
+ * status differs from the response terminals' `defaultStatusCode`.
383
+ * NestJS sends 201 for `@Post`.
384
+ */
385
+ defaultStatusCodes?: Record<string, number>;
386
+ /**
387
+ * A method decorator, imported from `importModule`, whose first
388
+ * argument sets the status of every response the handler returns
389
+ * without stating its own, NestJS's `@HttpCode(204)`. It wins over
390
+ * `defaultStatusCodes`.
391
+ */
392
+ statusCodeDecorator?: string;
393
+ /**
394
+ * The call that puts one path in front of every route the
395
+ * application serves, NestJS's `app.setGlobalPrefix("api")`.
396
+ */
397
+ globalPrefix?: GlobalPrefixCall;
442
398
  } | {
443
399
  /**
444
- * Loop expansion: a `for-of` loop over a literal array of
445
- * route specs is treated as if each element were an inline
446
- * registration. Used for patterns like:
400
+ * A `for-of` loop over a literal array of route specs, read as one
401
+ * inline registration per element:
447
402
  *
448
- * const routes = [
449
- * { method: "get", path: "/users", handler: getUsers },
450
- * ...
451
- * ];
452
- * for (const r of routes) app[r.method](r.path, r.handler);
453
- *
454
- * `elementShape` declares which keys on each element give the method,
455
- * the path, and the handler. The loop body has to contain at least
456
- * one call expression that references the
457
- * loop variable, which filters out unrelated loops. Nothing else
458
- * about that call is checked.
459
- *
460
- * An iterable that resolves to an `ArrayLiteralExpression`, inline or
461
- * bound to a `const` one hop away, gets expanded. Cross-file and
462
- * computed iterables are outside v0.
403
+ * ```ts
404
+ * const routes = [{ method: "get", path: "/users", handler: getUsers }];
405
+ * for (const r of routes) app[r.method](r.path, r.handler);
406
+ * ```
463
407
  *
464
- * Pack-author docs: `design/proposals/dynamic-registration.md`.
408
+ * `elementShape` says which keys give the method, path and handler.
409
+ * The loop body has to contain a call that uses the loop variable,
410
+ * which filters out unrelated loops. The array can be inline or a
411
+ * `const` one hop away. An array from another file is not expanded.
465
412
  */
466
413
  type: "registrationLoop";
467
414
  elementShape: {
@@ -483,33 +430,26 @@ type DiscoveryMatch = {
483
430
  };
484
431
  } | {
485
432
  /**
486
- * Helper-call expansion: one function call at the user's site is
487
- * treated as if it were N inline registrations, with the call's
488
- * arguments substituted into a template per registration. Used
489
- * for calls like `registerCrud(app, 'users', userHandlers)` that
490
- * `registrationCall` discovery cannot see today.
491
- *
492
- * Each entry in `registrations` describes one virtual route
493
- * the helper produces. `pathTemplate` and `handlerArg` use
494
- * `{N}` placeholders that resolve to the call's positional
495
- * arguments. `{N}` substitutes the argument's literal value
496
- * (for string-literal args) or its source text (for
497
- * non-literal args, with the slot marked opaque). `{N}.prop`
498
- * reads `prop` from the argument's resolved object.
499
- *
500
- * `importModule` optionally narrows matches to helpers imported from
501
- * one specific module, which helps when two packages happen to export
502
- * a function with the same name.
433
+ * A helper call read as several inline registrations, with the call's
434
+ * arguments substituted into a template for each. It covers helpers
435
+ * like `registerCrud(app, "users", userHandlers)`, which
436
+ * `registrationCall` cannot see.
503
437
  *
504
- * Pack-author docs: `design/proposals/dynamic-registration.md`.
438
+ * Each `registrations` entry is one route the helper produces.
439
+ * `pathTemplate` and `handlerArg` use `{N}` for the call's Nth
440
+ * argument: its value when it is a string literal, and otherwise its
441
+ * source text with the slot marked opaque. `{N}.prop` reads `prop` from
442
+ * the argument's resolved object. `importModule` narrows matches to a
443
+ * helper imported from that module, for when two packages export the
444
+ * same name.
505
445
  */
506
446
  type: "registrationTemplate";
507
447
  helperName: string;
508
448
  importModule?: string;
509
449
  /**
510
- * Which argument is the routable, so a route the helper writes
511
- * keys on the same app as one written beside it and the
512
- * middleware registered there covers it too.
450
+ * Which argument is the routable. A route the helper registers then
451
+ * belongs to the same app as a route written beside the call, and the
452
+ * middleware registered on that app applies to it too.
513
453
  */
514
454
  subject?: {
515
455
  argument: number;
@@ -523,32 +463,23 @@ type DiscoveryMatch = {
523
463
  }>;
524
464
  } | {
525
465
  /**
526
- * Routes declared as JSX elements, the way client-side routers
527
- * write them: an element imported from the router library whose
528
- * attributes give a URL path pattern and the element it
529
- * renders. Covers the tree form (route elements nested inside
530
- * one another, child paths joining the parent's, index routes
531
- * taking the parent's path) and the object-array form (a
532
- * factory call whose first argument is an array of route
533
- * objects using the same property names).
466
+ * Routes declared as JSX elements, the way client-side routers write
467
+ * them. It reads the tree form, where a child path joins its parent's
468
+ * and an index route takes its parent's path, and the object form,
469
+ * where a factory call takes an array of route objects with the same
470
+ * property names. The pack supplies every name, and the adapter only
471
+ * walks JSX and arrays.
534
472
  *
535
- * The pack says what its library exports: the route element, the
536
- * path, element, and index attributes, and any factories that
537
- * take an array of route objects. The adapter walks JSX and arrays,
538
- * and knows none of those names itself.
539
- *
540
- * Each route with a readable path becomes one unit whose target
541
- * is the component the element attribute references, resolved
542
- * only when the reference is a single identifier. A route whose
543
- * component cannot be read is still reported, as a boundary with
544
- * nothing behind it. A route whose path cannot be read gets no path
545
- * and reports that in a gap instead of guessing at one.
473
+ * Each route with a readable path becomes one unit whose target is the
474
+ * component its element attribute references, when that reference is
475
+ * a single identifier. A route whose component cannot be read is still
476
+ * reported, as a boundary with nothing behind it. A route whose path
477
+ * cannot be read gets no path and a gap saying why.
546
478
  */
547
479
  type: "jsxElementRoute";
548
480
  /**
549
- * Module(s) the route element and factories must be imported
550
- * from. Exact module specifiers, matched against the file's
551
- * import declarations; aliased imports are followed.
481
+ * Modules the route element and factories must be imported from,
482
+ * matched exactly. An aliased import still matches.
552
483
  */
553
484
  importModule: string | string[];
554
485
  /** The route element's exported name. */
@@ -578,12 +509,10 @@ type DiscoveryMatch = {
578
509
  */
579
510
  routeObjectFactories?: string[];
580
511
  /**
581
- * Factory functions that turn JSX route elements into the route
582
- * objects the library consumes. The elements themselves are read
583
- * by the JSX walk wherever they appear, so a route-object factory
584
- * handed one of these calls has nothing more to add. Listing them
585
- * here is what stops that case from being reported as an
586
- * unreadable route array.
512
+ * Factory functions that turn JSX route elements into route objects.
513
+ * The JSX walk already reads those elements wherever they appear.
514
+ * Listing the factory keeps a route-object factory that is handed one
515
+ * of these calls from being reported as an unreadable route array.
587
516
  */
588
517
  elementsFactories?: string[];
589
518
  /**
@@ -594,23 +523,17 @@ type DiscoveryMatch = {
594
523
  method: string;
595
524
  } | {
596
525
  /**
597
- * Consumer side of the package-export boundary. Scans source files for
598
- * imports of the listed packages and records every call site,
599
- * emitting one `caller`-kind unit per enclosing
600
- * function. The bindings this produces are
601
- * `function-call { package, exportPath }`, which match the provider
602
- * summaries `packageExports` produces.
603
- *
604
- * `packages` lists exact package names to track imports of, possibly
605
- * with a sub-path such as `"@suss/behavioral-ir/schemas"`. Pass
606
- * several package names to track a family at
607
- * once. Imports of any other package are ignored.
526
+ * The consumer side of the package-export boundary. The adapter finds
527
+ * imports of the listed packages, records every call site, and emits
528
+ * one `caller` unit per enclosing function, bound as
529
+ * `function-call { package, exportPath }` to pair with the providers
530
+ * `packageExports` finds.
608
531
  *
609
- * As of v0 this covers named and default imports. Namespace imports
610
- * (`import * as X from`) are not tracked yet. Re-imports within
611
- * the consumer repo (consumer A imports from consumer B which
612
- * re-exports from pkg) produce units against the intermediate rather
613
- * than the original, because full symbol resolution is not built yet.
532
+ * `packages` takes exact package names, with a sub-path where needed,
533
+ * such as `"@suss/behavioral-ir/schemas"`. Named and default imports
534
+ * are tracked. Namespace imports (`import * as X`) are not yet. When a
535
+ * file in the consumer's repo re-exports the package and another file
536
+ * imports it from there, the unit binds to that intermediate file.
614
537
  */
615
538
  type: "packageImport";
616
539
  /** Left out when `workspaces` is set. */
@@ -621,7 +544,61 @@ type DiscoveryMatch = {
621
544
  * side of the package-export boundary, whichever two they are.
622
545
  */
623
546
  workspaces?: true;
624
- };
547
+ } | TestCaseMatch;
548
+ /**
549
+ * Test cases written as nested calls, the way vitest, jest and mocha
550
+ * write them: `describe("orders", () => { it("cancels", () => ...) })`.
551
+ * Each case becomes a unit named by its title path, the suite titles
552
+ * then its own, joined with ` > `. A title the adapter cannot read as a
553
+ * string keeps its source text and is recorded as unresolved.
554
+ */
555
+ interface TestCaseMatch {
556
+ type: "testCase";
557
+ style: "block";
558
+ /** The module the runner's functions are imported from, e.g. "vitest". */
559
+ importModule: string;
560
+ /** The functions that open a group of cases, e.g. ["describe", "suite"]. */
561
+ suiteNames: string[];
562
+ /** The functions that declare one case, e.g. ["it", "test"]. */
563
+ caseNames: string[];
564
+ /**
565
+ * Modifiers that mean the case does not run, written after a suite
566
+ * or case name: `it.skip`, `describe.todo`. A case under a skipped
567
+ * suite is skipped too.
568
+ */
569
+ skipModifiers: string[];
570
+ /**
571
+ * Modifiers that take arguments of their own and return the function
572
+ * to call, such as `it.skipIf(condition)`. The case called through one
573
+ * is read like any other.
574
+ */
575
+ argumentModifiers: string[];
576
+ /**
577
+ * Modifiers that take a table and declare one case per row, such as
578
+ * `it.each(rows)`. The runner fills the title in per row, so the case
579
+ * is read once, under the title as written, and that title is
580
+ * recorded as unresolved.
581
+ */
582
+ rowModifiers: string[];
583
+ /**
584
+ * How the runner replaces something before a case runs. `object` is
585
+ * the runner's helper object, imported from `importModule`.
586
+ * `moduleMethods` replace a whole module given its specifier
587
+ * (`vi.mock("./orders.js")`), and `memberMethods` replace one member
588
+ * given the object and the member's name (`vi.spyOn(orders, "cancel")`).
589
+ */
590
+ mocks?: {
591
+ object: string;
592
+ moduleMethods: string[];
593
+ memberMethods: string[];
594
+ };
595
+ /**
596
+ * Walk only these files, matched on whole path segments from the end,
597
+ * so `src/orders.test.ts` matches `/repo/pkg/src/orders.test.ts`. Left
598
+ * out, every file that imports the runner is walked.
599
+ */
600
+ files?: string[];
601
+ }
625
602
  type BindingExtraction = {
626
603
  method: {
627
604
  type: "fromRegistration";
@@ -657,11 +634,9 @@ type BindingExtraction = {
657
634
  property: string;
658
635
  } | {
659
636
  /**
660
- * The route path comes from where the file is on disk, which is how
661
- * Next.js and React Router describe their routes. The pack spells
662
- * out its own convention here, because the adapter knows
663
- * about files and the pack knows what the framework does with their
664
- * names.
637
+ * The route path comes from where the file is on disk, the way
638
+ * Next.js and React Router describe routes. The pack gives the
639
+ * framework's filename convention, and the adapter applies it.
665
640
  *
666
641
  * `app/api/orders/[id]/route.ts` under `{ root: "app",
667
642
  * dropBasenames: ["route"], dynamic: "brackets" }` comes out as
@@ -707,8 +682,8 @@ type BindingExtraction = {
707
682
  * `decoratorArgument` reads the argument off the same decorator the
708
683
  * match selected the handler by, so `@EventPattern("order.placed")`
709
684
  * gives "order.placed". `literal` is for a wire whose channel the
710
- * library fixes. `unstated` says the wire is known and the channel is
711
- * not, which pairs the way a null channel always has.
685
+ * library fixes. `unstated` means the wire is known and the channel is
686
+ * not, and it pairs the same as a null channel.
712
687
  */
713
688
  type ChannelSource = {
714
689
  from: "decoratorArgument";
@@ -721,15 +696,10 @@ type ChannelSource = {
721
696
  };
722
697
  /**
723
698
  * A binding the pattern states outright, for a boundary the match
724
- * cannot read from the source.
725
- *
726
- * `bindingExtraction` speaks REST and nothing else, so a declarative
727
- * pack whose boundary is a queue or a topic had nowhere to say so and
728
- * was pushed into a callback, which pack health then reports as an
729
- * ast-link. This is the same vocabulary `DiscoveredCustomUnit` already
730
- * has, declared instead of returned. Message bus only for now; the
731
- * design note in the proposals directory says what comes next and why
732
- * the rest stays put.
699
+ * cannot read from the source. `bindingExtraction` only covers REST, so
700
+ * a declarative pack for a queue or a topic states its binding here
701
+ * instead of writing a callback. The fields are the ones a
702
+ * `DiscoveredCustomUnit` returns. Only the message bus is supported.
733
703
  */
734
704
  type DeclaredBinding = {
735
705
  semantics: "message-bus";
@@ -789,6 +759,40 @@ interface WrapperMethodRegistration extends WrapperFunctionShape {
789
759
  */
790
760
  arity?: number;
791
761
  }
762
+ /**
763
+ * A call on the application object that puts one path in front of
764
+ * every route, with the path as its first argument. The adapter reads
765
+ * it in whichever file makes it, and applies the prefix to every route
766
+ * the pattern finds when all such calls in the run agree on it.
767
+ */
768
+ interface GlobalPrefixCall {
769
+ /** The method on the application object, NestJS's `setGlobalPrefix`. */
770
+ method: string;
771
+ /**
772
+ * How the application object is made: `NestFactory.create(...)`, where
773
+ * `NestFactory` is imported from `@nestjs/core`. A call on anything
774
+ * else is not read, including a parameter some caller passes anything
775
+ * else to.
776
+ */
777
+ application: {
778
+ importModule: string;
779
+ importName: string;
780
+ factory: string;
781
+ };
782
+ /**
783
+ * The routes the prefix leaves out, listed under `option` on the
784
+ * object the call takes second. An entry is a path, or an object with
785
+ * the path under `pathKey` and the method under `methodKey`, written
786
+ * as a member of the library's method enum. `methods` maps each
787
+ * member's number to its verb, with `*` for every verb.
788
+ */
789
+ exclude?: {
790
+ option: string;
791
+ pathKey: string;
792
+ methodKey: string;
793
+ methods: Record<number, string>;
794
+ };
795
+ }
792
796
  /** A wrapper handed to the routable's constructor as an option. */
793
797
  interface WrapperOptionRegistration extends WrapperFunctionShape {
794
798
  /** The option the wrapper is under, e.g. "defaultHook". */
@@ -804,22 +808,17 @@ interface DiscoveryPattern {
804
808
  /** A binding the pattern states outright. See `DeclaredBinding`. */
805
809
  binding?: DeclaredBinding;
806
810
  /**
807
- * How the routable this pattern discovers (Express's `Router()`,
808
- * Hono's `new Hono()`, and similar) can itself be mounted onto
809
- * another one under a path prefix, as in Express's
810
- * `app.use(prefix, router)` or Hono's `app.route(prefix, sub)`.
811
- * This only means anything when `match.type` is `"registrationCall"`,
812
- * because mount discovery reuses that match's `importModule` and
813
- * `importName` to work out which variables in a file are the routable
814
- * that a mount call is being made on.
811
+ * How the routable this pattern discovers (Express's `Router()`, Hono's
812
+ * `new Hono()`) is mounted on another under a path prefix, as in
813
+ * `app.use(prefix, router)` or `app.route(prefix, sub)`. It only applies
814
+ * when `match.type` is `"registrationCall"`, since mount discovery uses
815
+ * that match's `importModule` and `importName` to find the routables.
815
816
  *
816
- * When set, the adapter composes the mount's prefix into the path
817
- * of every route discovered on the mounted value, whether it is
818
- * declared in the mounting file or, by following the mounted value
819
- * through an import, in whichever file declares it. A mount whose
817
+ * The adapter adds the prefix to every route on the mounted value, in
818
+ * the mounting file or in the file an import leads to. A mount whose
820
819
  * prefix is not a string literal, or whose target the resolution store
821
- * cannot follow to a concrete value, contributes nothing, and the routes
822
- * under it keep the path they were written with.
820
+ * cannot follow, adds nothing, and its routes keep the path they were
821
+ * written with.
823
822
  */
824
823
  mount?: {
825
824
  /** Method name that registers a sub-router at a prefix, e.g. "use" or "route". */
@@ -844,22 +843,14 @@ interface DiscoveryPattern {
844
843
  */
845
844
  wraps?: WrapperRegistration;
846
845
  /**
847
- * This pattern only runs against files that import one of these module
848
- * specifiers, or a sub-path of one. An empty array means no gate at all
849
- * (the pattern is dispatched against
850
- * every file). Leaving it undefined does the same, but pack authors
851
- * SHOULD write it out, because `[]` is the deliberate
852
- * "match every file" choice, usually because the pattern keys on
853
- * something other than imports. The fetch runtime does that, since it
854
- * matches global `fetch(...)` calls.
855
- *
856
- * Matching is by prefix on the import module specifier. An entry of
857
- * `"@nestjs/graphql"` matches `from "@nestjs/graphql"` and
858
- * `from "@nestjs/graphql/dist/foo"` and any other sub-path.
846
+ * Run this pattern only on files that import one of these module
847
+ * specifiers or a sub-path of one, so `"@nestjs/graphql"` also matches
848
+ * `"@nestjs/graphql/dist/foo"`. An empty array runs it on every file, the
849
+ * same as leaving it unset. Write `[]` out when that is deliberate, as
850
+ * the fetch runtime does for global `fetch(...)` calls.
859
851
  *
860
- * This pre-filter is only there for speed. The closure walk and the other
861
- * post-passes can still reach every loaded file through symbol
862
- * resolution.
852
+ * The gate only saves time. The closure walk and the passes after
853
+ * discovery can still reach every loaded file through symbol resolution.
863
854
  */
864
855
  requiresImport?: string[];
865
856
  }
@@ -869,13 +860,10 @@ type TerminalMatch = {
869
860
  } | {
870
861
  type: "returnStatement";
871
862
  /**
872
- * Skip ReturnStatements whose returned expression is a CallExpression
873
- * (or NewExpression). For frameworks where `return reply.send(...)`
874
- * also lands as a `parameterMethodCall` match on the inner call,
875
- * this stops the same `return reply.send(...)` producing two
876
- * terminals, one from the wrapping returnStatement and one from
877
- * the inner method-call chain. Bare returns (`return user`,
878
- * `return { id }`, `return await fn()`) still match.
863
+ * Skip a return whose value is a call or `new` expression. Set it when
864
+ * a `parameterMethodCall` terminal already matches the inner call, so
865
+ * `return reply.send(...)` produces one terminal instead of two.
866
+ * Returns like `return user` and `return await fn()` still match.
879
867
  */
880
868
  excludeCallReturns?: boolean;
881
869
  } | {
@@ -896,22 +884,16 @@ type TerminalMatch = {
896
884
  type: "functionCall";
897
885
  functionName: string;
898
886
  /**
899
- * Only match when the name was imported from one of these modules.
900
- * The field works exactly like a DiscoveryPattern's gate, matching by
901
- * prefix: "react-router" also matches "react-router/server".
887
+ * Only match when the name was imported from one of these modules,
888
+ * by prefix, the same as `DiscoveryPattern.requiresImport`.
902
889
  *
903
- * Set it whenever the function belongs to a library, because matching
904
- * on a bare name picks up every function with that name in the
905
- * user's project too. `json` is a common name for a project's own
906
- * response helper, and reading a library's argument order into one
907
- * of those gives you a confident wrong answer.
908
- *
909
- * Leave it unset only when the function belongs to no library at all.
910
- * A pack should generally not target a project's own helper. Declare
911
- * the envelope structure instead, with a `returnShape` terminal, and
912
- * the adapter follows a returned call into the project and reads
913
- * the helper's parameters. That covers a helper whatever it is called
914
- * and whatever order its arguments come in.
890
+ * Set it whenever the function belongs to a library. A bare name also
891
+ * matches the project's own functions, and `json` is a common name
892
+ * for a project's response helper. Reading a library's argument order
893
+ * into one of those gives a confident wrong answer. To cover a project
894
+ * helper, declare the envelope with a `returnShape` terminal instead:
895
+ * the adapter follows a returned call into the project and reads the
896
+ * helper's parameters, whatever the helper is called.
915
897
  */
916
898
  requiresImport?: string[];
917
899
  } | {
@@ -924,26 +906,21 @@ type TerminalMatch = {
924
906
  type: "jsxReturn";
925
907
  } | {
926
908
  /**
927
- * A synthetic terminal for the implicit fall-through at the end of a
928
- * function body. It fires when the function's last statement is
929
- * neither a `ReturnStatement` nor a `ThrowStatement`, which
930
- * covers the common case of handler and effect bodies that run
931
- * side effects and return `undefined` implicitly. Without this,
932
- * handler summaries come out with `transitions: []` because
933
- * `findTerminals` has nothing to match. A pack that always expects
934
- * explicit returns (HTTP handlers) should leave this out of
935
- * its terminals. A pack for callback bodies (React handlers,
936
- * `useEffect` bodies, Node `.on(...)` callbacks) should include it.
909
+ * The implicit fall-through at the end of a function body. It fires
910
+ * when the last statement is neither a return nor a throw, which is
911
+ * how most event handlers and effect bodies end. Without it, such a
912
+ * summary comes out with no transitions. Packs for callback bodies
913
+ * (React handlers, `useEffect`, Node `.on(...)`) include it, and HTTP
914
+ * handler packs that expect explicit returns leave it out.
937
915
  */
938
916
  type: "functionFallthrough";
939
917
  } | {
940
918
  /**
941
919
  * A call to the parameter at this position, `next()` inside a
942
- * middleware. Nothing declares this in a pack: the adapter builds
943
- * it from `DiscoveryPattern.wraps.continuationParam`, so a
944
- * wrapper's path that hands control on ends in a `delegate`
945
- * output and a path that responds first does not. That is how
946
- * composition tells the two apart.
920
+ * middleware. Packs do not declare it; the adapter builds it from
921
+ * `DiscoveryPattern.wraps.continuationParam`. A wrapper path that
922
+ * hands control on then ends in a `delegate` output, and composition
923
+ * uses that to tell it from a path that responds first.
947
924
  */
948
925
  type: "parameterCall";
949
926
  parameterPosition: number;
@@ -981,10 +958,21 @@ interface TerminalExtraction {
981
958
  defaultStatusCode?: number;
982
959
  }
983
960
  interface TerminalPattern {
984
- /** What kind of output this terminal produces: "response", "throw", "return", "render", "delegate" */
985
- kind: "response" | "throw" | "return" | "render" | "delegate";
961
+ /**
962
+ * What kind of output this terminal produces. An `exit` terminal ends
963
+ * the process, so a path that reaches one goes no further, and the
964
+ * status code it extracts is the exit code.
965
+ */
966
+ kind: "response" | "throw" | "return" | "render" | "delegate" | "exit";
986
967
  match: TerminalMatch;
987
968
  extraction: TerminalExtraction;
969
+ /**
970
+ * Read in every unit, whichever pack discovered it, the way an
971
+ * invocation recognizer is. A runtime's own way of ending the process,
972
+ * `process.exit(n)`, ends a route handler and a helper alike, and the
973
+ * pack that declares it discovers no units of its own.
974
+ */
975
+ inEveryUnit?: boolean;
988
976
  /**
989
977
  * On a throw terminal: the framework turns the thrown status into
990
978
  * the wire response, so a resolved status makes the output a
@@ -1007,9 +995,8 @@ interface ContractPattern {
1007
995
  };
1008
996
  /**
1009
997
  * The properties an endpoint states its HTTP method and path under.
1010
- * Both ts-rest and zod-openapi happen to spell them `method` and
1011
- * `path`, but they are the library's words, so the pack says them
1012
- * and the adapter reads whatever it is told.
998
+ * The names belong to the library, so the pack supplies them. ts-rest
999
+ * and zod-openapi both use `method` and `path`.
1013
1000
  */
1014
1001
  methodProperty: string;
1015
1002
  pathProperty: string;
@@ -1073,33 +1060,23 @@ type InputMappingPattern = {
1073
1060
  wholeParamRole?: string;
1074
1061
  } | {
1075
1062
  /**
1076
- * Emit one `Input` per declared parameter, in source order, using the
1077
- * parameter's name as its role, or `defaultRole` when set. Used by the
1078
- * reachable-closure pass for internal library functions, where no
1079
- * framework declares a set of roles, so the name a caller sees IS
1080
- * the role. Destructured parameters are captured the
1081
- * same way `objectParam` captures them, so `(ctx, { userId })`
1082
- * gives two inputs, `ctx` and `userId`.
1063
+ * One input per declared parameter, in source order, with the
1064
+ * parameter's name as its role unless `defaultRole` is set. The
1065
+ * reachable-closure pass uses it for internal library functions,
1066
+ * where no framework assigns roles. Destructured parameters work as
1067
+ * in `objectParam`, so `(ctx, { userId })` gives `ctx` and `userId`.
1083
1068
  */
1084
1069
  type: "allPositional";
1085
1070
  defaultRole?: string;
1086
1071
  } | {
1087
1072
  /**
1088
- * Decorator-driven parameter mapping, NestJS-style. For each declared
1089
- * parameter, the adapter reads the parameter's first decorator and
1090
- * looks its name up in `decoratorRoleMap`.
1091
- * A decorator that matches gives the parameter that role. One that
1092
- * matches nothing falls back to `defaultRole`, or is skipped when
1093
- * `defaultRole` is unset.
1094
- *
1095
- * For `@nestjs/graphql` resolvers:
1096
- * `{ "Args": "args", "Parent": "parent",
1097
- * "Context": "context", "Info": "info" }`.
1098
- *
1099
- * Decorators are matched by name alone, so if several frameworks
1100
- * define `@Args`, all of them map. Packs that need to
1101
- * tell them apart by import module can add that later, once there
1102
- * is a use case worth the cost.
1073
+ * NestJS-style parameter decorators. The adapter looks up each
1074
+ * parameter's first decorator in `decoratorRoleMap`. A match gives the
1075
+ * role, and a parameter that matches nothing gets `defaultRole` or is
1076
+ * skipped. For `@nestjs/graphql` resolvers the map is
1077
+ * `{ Args: "args", Parent: "parent", Context: "context", Info: "info" }`.
1078
+ * Decorators match by name alone, so every framework's `@Args` maps
1079
+ * the same way.
1103
1080
  */
1104
1081
  type: "decoratedParams";
1105
1082
  decoratorRoleMap: Record<string, string>;
@@ -1138,31 +1115,57 @@ interface ResponsePropertyMapping {
1138
1115
  * say so.
1139
1116
  */
1140
1117
  interface TransparentWrapper {
1141
- /** Callee text as written, e.g. "Sentry.wrapHandler". */
1142
- callee: string;
1143
- /** Which argument the wrapped function is passed as. */
1144
- argument: number;
1145
1118
  /**
1146
- * The module the callee has to have been imported from. Without it a
1147
- * local object spelled the same way would be taken for the library.
1119
+ * The module that exports the wrapper, e.g. "@sentry/aws-serverless".
1120
+ * A call matches whatever the project calls the import, and a local
1121
+ * function spelled the same way does not match.
1148
1122
  */
1149
1123
  module: string;
1124
+ /** The name the module exports the wrapper under, e.g. "wrapHandler". */
1125
+ name: string;
1126
+ /** Which argument the wrapped function is passed as. */
1127
+ argument: number;
1128
+ }
1129
+ /**
1130
+ * A library decorator that fills the parameter it decorates. The library
1131
+ * builds the value, so nothing in the project describes it, and the
1132
+ * decorator call is the only thing that says what the parameter is.
1133
+ */
1134
+ interface ParameterSupplier {
1135
+ /** The module that exports the decorator, e.g. "@nestjs/typeorm". */
1136
+ module: string;
1137
+ /** The name the module exports the decorator under. */
1138
+ name: string;
1139
+ }
1140
+ /**
1141
+ * A command-line parser a runtime or library provides, and where its
1142
+ * settings say which flags a command takes. `parseArgs({ args, options })`
1143
+ * from `node:util` is one: its options object lists every flag, and
1144
+ * nothing else in the command's body ever spells them.
1145
+ */
1146
+ interface ArgumentParser {
1147
+ /** The module that exports the parser, e.g. `"node:util"`. */
1148
+ module: string;
1149
+ /** The name the module exports it under, e.g. `"parseArgs"`. */
1150
+ name: string;
1151
+ /** Which argument the settings object is passed as. */
1152
+ argument: number;
1153
+ /** The settings key the command-line arguments are passed under, e.g. `"args"`. */
1154
+ argsKey: string;
1155
+ /** The settings key whose object has one key per flag, e.g. `"options"`. */
1156
+ optionsKey: string;
1150
1157
  }
1151
1158
  interface PatternPack {
1152
1159
  name: string;
1153
1160
  /**
1154
- * Pack version stamp, which feeds the cache invalidation key. Bump on
1155
- * any change that affects discovered units / extracted summaries.
1156
- * Format is opaque to the adapter, so semver or a content hash both
1157
- * work.
1161
+ * The pack's version, part of the cache key. Change it whenever a change
1162
+ * affects discovered units or summaries; any string works.
1158
1163
  *
1159
- * Optional, because whoever loads the pack knows more about it than
1160
- * the pack does. The CLI folds a hash of the file it loaded and of
1161
- * the config it passed into this stamp, so a pack run through the CLI
1162
- * invalidates on an edit whether or not it declares a version. A host
1163
- * that builds packs some other way takes on that responsibility itself.
1164
- * A pack with nothing to stamp comes out as `"unset"`, and a warm cache
1165
- * will then serve results for code that has since changed.
1164
+ * It is optional because the CLI hashes the pack file it loaded and the
1165
+ * config it passed into the key, so an edit invalidates the cache
1166
+ * whether or not the version moved. A host that loads packs another way
1167
+ * has to do that itself. A pack with nothing to stamp is keyed as
1168
+ * `"unset"`, and a warm cache then serves stale results after an edit.
1166
1169
  */
1167
1170
  version?: string;
1168
1171
  /**
@@ -1196,18 +1199,12 @@ interface PatternPack {
1196
1199
  */
1197
1200
  requestSpelling?: RequestSpellingMetadata;
1198
1201
  /**
1199
- * Transport (wire protocol) used in the `BoundaryBinding.transport`
1200
- * of discovered units. Every pack has to say what its transport is
1201
- * rather than falling back on a hardcoded HTTP default. "What transport
1202
- * does this pack cover?" is a question every pack should have to
1203
- * answer, and requiring the field stops a later pack (React, GraphQL,
1204
- * Lambda-invoke, queues) from quietly inheriting an HTTP-shaped default
1205
- * that does not fit it.
1206
- *
1207
- * The pack's `name` separately fills in `BoundaryBinding.recognition`
1208
- * on the summaries, so `{ transport, recognition }` come from
1209
- * the pack directly and the adapter derives `semantics` from the
1210
- * discovery pattern's binding-extraction rules.
1202
+ * The transport written into `BoundaryBinding.transport` on discovered
1203
+ * units, such as `"http"` or `"in-process"`. It is required so a pack
1204
+ * for React, queues or Lambda invocation never inherits an HTTP default
1205
+ * that does not fit it. The pack's `name` fills in
1206
+ * `BoundaryBinding.recognition`, and the adapter derives `semantics`
1207
+ * from the discovery pattern's binding extraction.
1211
1208
  */
1212
1209
  protocol: string;
1213
1210
  /**
@@ -1225,137 +1222,57 @@ interface PatternPack {
1225
1222
  */
1226
1223
  failureDelivery?: FailureDelivery;
1227
1224
  /**
1228
- * Synthesize extra code units out of a parent unit's body, for when one
1229
- * construct the user wrote implicitly spawns several units the runtime
1230
- * schedules. Used when a framework's runtime
1231
- * schedules callbacks that aren't visible as top-level declarations:
1232
- * React event handlers on JSX elements, React `useEffect` bodies,
1233
- * Node `emitter.on("event", handler)`, class-component lifecycle
1234
- * methods, and similar.
1235
- *
1236
- * `ctx` is typed `unknown` here because the extractor has no
1237
- * knowledge of which adapter is driving it; each language adapter
1238
- * defines its own context type (`TsSubUnitContext` in
1239
- * `@suss/adapter-typescript`, say) with the primitives a pack needs to
1240
- * walk the parent's AST. Packs import and cast to the adapter
1241
- * context they were written against, and that cast is how a pack says
1242
- * out loud that it requires the TypeScript adapter.
1225
+ * Build extra code units from a parent unit's body, for callbacks the
1226
+ * runtime schedules that are not top-level declarations: React event
1227
+ * handlers on JSX elements, `useEffect` bodies, Node `emitter.on(...)`
1228
+ * handlers. Each returned unit goes through the same extraction as a
1229
+ * discovered one and gets its own summary. Set `terminals` or
1230
+ * `inputMapping` on a sub-unit when it differs from the pack's defaults.
1243
1231
  *
1244
- * Returned units are fed through the adapter's extraction pipeline
1245
- * the same way top-level discovered units are, so each becomes its
1246
- * own `BehavioralSummary`. Put per-unit `terminals` and `inputMapping`
1247
- * on the `DiscoveredUnit` when a sub-unit is written differently from
1248
- * the parent pack's defaults.
1232
+ * `ctx` is `unknown` because each adapter defines its own context type
1233
+ * (`TsSubUnitContext` in `@suss/adapter-typescript`) with the primitives
1234
+ * for walking the parent's syntax tree. A pack casts to the context it
1235
+ * was written against, which ties the pack to that adapter.
1249
1236
  */
1250
1237
  subUnits?: (parent: DiscoveredSubUnitParent, ctx: unknown) => DiscoveredSubUnit[];
1251
1238
  /**
1252
- * A top-level discovery callback the pack supplies. It is to discovery
1253
- * what `subUnits` is to sub-units: when a framework's convention does
1254
- * not fit one of the data-driven `DiscoveryMatch` variants (REST
1255
- * registration, decorator-based controllers, named-export shapes,
1256
- * etc.), the pack ships its own walker here. The adapter calls it
1257
- * once per source file alongside the data-driven dispatch.
1239
+ * The pack's own top-level discovery, for a convention none of the
1240
+ * `DiscoveryMatch` variants fits, such as React's component-export
1241
+ * heuristic or Storybook's `.stories.tsx` files. Keeping those in the
1242
+ * pack leaves the `DiscoveryMatch` union to patterns any pack can share.
1243
+ * The adapter calls it once per source file beside the data-driven
1244
+ * discovery, and the units it returns go through the normal pipeline
1245
+ * with the same per-unit overrides.
1258
1246
  *
1259
- * Use this for framework-specific patterns that do not generalize:
1260
- * React's component-export heuristic (PascalCase plus a JSX return),
1261
- * Vue's `.vue` SFC slots, Solid's component conventions, Storybook's
1262
- * `.stories.tsx` file convention. Those are all legitimate conventions,
1263
- * but baking each one into the central `DiscoveryMatch` union forces
1264
- * every unrelated pack to know about them. Callbacks leave the central
1265
- * union for the generic primitives and let each pack own its own
1266
- * conventions.
1267
- *
1268
- * `ctx` is typed `unknown` for the same reason as in `subUnits`: each
1269
- * adapter ships its own context primitive (`TsDiscoveryContext` in
1270
- * `@suss/adapter-typescript`) and a pack casts to whichever one it was
1271
- * written against. That cast is how the pack says it requires the TS
1272
- * adapter.
1273
- *
1274
- * The units you return go through the adapter's normal pipeline. They
1275
- * get their terminals and effects extracted, sub-units synthesized, and
1276
- * summaries assembled exactly as units from data-driven discovery
1277
- * do. Per-unit `terminals` and `inputMapping` overrides on
1278
- * `DiscoveredUnit` work the same way too.
1279
- *
1280
- * **Cross-pack dedup.** When this callback discovers a unit at the
1281
- * same `(func, kind)` as a unit from another pack's data-driven
1282
- * discovery, the adapter's cross-pack claim dedup keeps whichever
1283
- * claimed it first. The order packs appear in the framework list is
1284
- * what decides precedence.
1247
+ * `ctx` is `unknown` for the same reason as in `subUnits`
1248
+ * (`TsDiscoveryContext` in `@suss/adapter-typescript`). When this finds
1249
+ * a unit at the same function and kind as another pack's discovery, the
1250
+ * pack that comes first in the framework list claims it.
1285
1251
  */
1286
1252
  discoverUnits?: (sourceFile: unknown, ctx: unknown) => DiscoveredCustomUnit[];
1287
1253
  /**
1288
- * Per-call-site recognizers that emit typed `Effect`s alongside the
1289
- * generic `invocation` effect the adapter already captures.
1290
- *
1291
- * **Scope contract.** The adapter walks every CallExpression in
1292
- * the function body and dispatches to every registered recognizer
1293
- * for each call. Walking skips nested function bodies (those are
1294
- * their own units with their own recognizer dispatch). The walk is
1295
- * INDEPENDENT of the existing invocation-effect walker, which is
1296
- * deliberately narrow (it only captures
1297
- * `invocation` effects from bare expression statements and container
1298
- * composition, to avoid double-counting calls that already become
1299
- * terminals). Recognizers do not have that problem, so they fire on
1300
- * every call regardless of position, including
1301
- * `const x = await fn(...)` initializers and nested call args
1302
- * (which the invocation walker skips). This independence means
1303
- * recognizer authors can rely on seeing every call in scope.
1304
- *
1305
- * **Cross-pack visibility.** Recognizers fire regardless of which pack
1306
- * discovered the enclosing function, so
1307
- * `@suss/framework-prisma`'s recognizer can fire on Prisma calls
1308
- * inside an `@suss/framework-express` handler. Pack authors don't
1309
- * need to coordinate.
1310
- *
1311
- * **Emission contract.** Returning effects ADDS them to the enclosing
1312
- * default-branch transition, and the generic `invocation` effect is
1313
- * kept either way (typed effects live alongside the raw
1314
- * call capture, so inspect can still render the callee text and
1315
- * arguments while the checker pairs on the typed form). Return `null`
1316
- * or `[]` for no match.
1254
+ * Per-call recognizers that emit typed `Effect`s beside the generic
1255
+ * `invocation` effect. The adapter calls each one on every call in the
1256
+ * function body, whichever pack discovered the function, and skips
1257
+ * nested functions, which are units of their own. Calls the invocation
1258
+ * walk skips, such as `const x = await fn(...)` initializers, are included.
1317
1259
  *
1318
- * **Dedup is the recognizer's responsibility.** The dispatcher does
1319
- * not dedupe across calls. A recognizer that wants to fire
1320
- * once per identifier, to collapse reads bound to a const used N
1321
- * times, has to track that state itself across invocations.
1322
- *
1323
- * **Exceptions are caught and logged.** A recognizer that throws gets
1324
- * logged to stderr with the file path and line number, and is skipped
1325
- * for that one call while the extraction carries on. A buggy
1326
- * recognizer will not crash the run.
1327
- *
1328
- * `call` is the language adapter's call-expression handle (opaque
1329
- * here; ts-morph `CallExpression` in `@suss/adapter-typescript`).
1330
- * `ctx` is the adapter's recognizer context (source file, an
1331
- * `extractArgs()` helper that reuses the adapter's own EffectArg
1332
- * builder). A recognizer casts both to the adapter context it was
1333
- * written against, which is the same way `subUnits` says a pack
1334
- * requires the TypeScript adapter.
1260
+ * Returned effects are added to the enclosing default transition, and
1261
+ * the `invocation` effect stays. Return `null` or `[]` for no match.
1262
+ * Nothing dedupes across calls, so a recognizer that should fire once
1263
+ * per identifier tracks that itself. One that throws is logged with its
1264
+ * file and line and skipped for that call. `call` and `ctx` are the
1265
+ * adapter's own types, which a recognizer casts to as `subUnits` does.
1335
1266
  */
1336
1267
  invocationRecognizers?: InvocationRecognizer[];
1337
1268
  /**
1338
- * Optional pack-level import gate. When set, the adapter's
1339
- * pre-filter only considers this pack applicable to source files
1340
- * whose imports include at least one of the listed modules
1341
- * (matched by prefix, so `"@aws-sdk/client-sqs"` matches that module
1342
- * and any `"@aws-sdk/client-sqs/sub-path"`).
1343
- *
1344
- * Useful for recognizer-only packs that target a specific library:
1345
- * `@suss/framework-aws-sqs` declares `["@aws-sdk/client-sqs"]`,
1346
- * `@suss/framework-prisma` declares `["@prisma/client"]`. Without
1347
- * a gate, a recognizer-only pack walks every file in the project. That
1348
- * is correct but wasteful in a large monorepo where most files never
1349
- * import the library.
1350
- *
1351
- * A discovery-pattern pack already has a per-pattern `requiresImport`
1352
- * on `DiscoveryPattern`. This is the pack-level version of that, for a
1353
- * pack whose ONLY mechanism is recognizers and which has no discovery.
1354
- *
1355
- * Empty or undefined means no gate, so the pack walks every file (the
1356
- * default for universal recognizers like `@suss/runtime-node`'s
1357
- * process-surface and env-var recognizers, since `process.*` is
1358
- * available without importing anything).
1269
+ * A pack-level import gate: the pack applies only to files that import
1270
+ * one of these modules, matched by prefix. A pack with only recognizers
1271
+ * uses it the way a discovery pattern uses its own `requiresImport`;
1272
+ * `@suss/framework-prisma` declares `["@prisma/client"]`. Without a gate
1273
+ * the pack walks every file, which gives the same result and is slow in
1274
+ * a large monorepo. Leave it empty for recognizers that apply
1275
+ * everywhere, such as `process.env` reads, which need no import.
1359
1276
  */
1360
1277
  requiresImport?: string[];
1361
1278
  /**
@@ -1368,6 +1285,16 @@ interface PatternPack {
1368
1285
  * ordinary import would.
1369
1286
  */
1370
1287
  generatedModuleMarkers?: string[];
1288
+ /**
1289
+ * The directories this library's code generator writes to, as the
1290
+ * project's own configuration says, for a generator that leaves no
1291
+ * marker file. Given the directory of a file that imports by relative
1292
+ * path, it returns the output directories the configuration governing
1293
+ * that file gives. An import into one counts as the gated package, the
1294
+ * same as a directory with a marker in it, even before the generator
1295
+ * has run.
1296
+ */
1297
+ generatedModuleDirs?: (fromDir: string) => readonly string[];
1371
1298
  /**
1372
1299
  * Functions the project itself wrote in front of this library, read
1373
1300
  * once across the whole project before any file is walked. What the
@@ -1376,14 +1303,13 @@ interface PatternPack {
1376
1303
  */
1377
1304
  projectHelpers?: ProjectHelpers;
1378
1305
  /**
1379
- * Environment variables the pack's library reads from inside
1380
- * node_modules, where no walk ever looks. Declaring them keeps the
1381
- * checker from telling a template that a variable is unused when the
1382
- * library reads it on every invocation. The adapter emits one marker
1383
- * summary per entry whose `module` some project file imports, and
1384
- * the runtime-config pairing consults the markers before it accuses.
1385
- * The module match is a specifier prefix, so one entry covers a
1386
- * scoped family like `@aws-lambda-powertools/`.
1306
+ * Environment variables the library reads from inside node_modules,
1307
+ * where no walk looks. Without them the checker would report a variable
1308
+ * in a template as unused when the library reads it on every invocation.
1309
+ * The adapter emits one marker summary per entry whose `module` some
1310
+ * project file imports, and runtime-config pairing checks the markers
1311
+ * before reporting. `module` matches as a prefix, so one entry covers a
1312
+ * family like `@aws-lambda-powertools/`.
1387
1313
  */
1388
1314
  libraryEnvVars?: Array<{
1389
1315
  /** Module-specifier prefix the library's imports start with. */
@@ -1394,16 +1320,18 @@ interface PatternPack {
1394
1320
  names?: string[];
1395
1321
  }>;
1396
1322
  /**
1397
- * Library wrappers that return the function they wrapped. The adapter
1398
- * works this out on its own for a factory inside the project by reading
1399
- * its body. A library wrapper's body is not there to read, so the
1400
- * pack has to say it: a call to `callee` resolves to its
1401
- * `argument`-th argument.
1402
- *
1403
- * `callee` matches the call expression text as written, e.g.
1404
- * `"Sentry.wrapHandler"`.
1323
+ * Library wrappers that return the function they were handed. A call to
1324
+ * the `name` that `module` exports resolves to its `argument`-th
1325
+ * argument. See `TransparentWrapper`.
1405
1326
  */
1406
1327
  transparentWrappers?: TransparentWrapper[];
1328
+ /**
1329
+ * Parameter decorators whose call is the value a container passes
1330
+ * in. A constructor parameter written `@InjectRepository(User) repo`
1331
+ * then reads as that call, and a pack can match `this.repo.find()` by
1332
+ * the decorator it came from.
1333
+ */
1334
+ parameterSuppliers?: ParameterSupplier[];
1407
1335
  /**
1408
1336
  * Objects whose properties are the process environment, written as
1409
1337
  * the dotted path the code spells, e.g. `"process.env"`. The adapter
@@ -1412,6 +1340,27 @@ interface PatternPack {
1412
1340
  * parameter gets its reads reported at the calls that named them.
1413
1341
  */
1414
1342
  environmentObjects?: string[];
1343
+ /**
1344
+ * The process's output streams, written the same way, e.g.
1345
+ * `"process.stdout"`. The adapter follows each one into the helpers
1346
+ * it is passed to, so a write through a stream parameter can say
1347
+ * which stream it goes to.
1348
+ */
1349
+ streamObjects?: string[];
1350
+ /**
1351
+ * Dotted paths a program assigns its exit code to without ending
1352
+ * there, e.g. `"process.exitCode"`. The value assigned, and the
1353
+ * argument of a call an `exit` terminal matches, are where the adapter
1354
+ * starts when it works out which functions' returns become the code.
1355
+ */
1356
+ exitCodeWrites?: string[];
1357
+ /**
1358
+ * Command-line parsers, read in every unit whichever pack found it.
1359
+ * Each flag a call to one declares becomes an input read of `--flag`
1360
+ * off the parameter the call hands its arguments from, so a command's
1361
+ * intent can list its flags under `receives`.
1362
+ */
1363
+ argumentParsers?: ArgumentParser[];
1415
1364
  /**
1416
1365
  * How this library's client object is constructed, so an operation
1417
1366
  * summary can say which endpoint its calls go to. Each entry is a
@@ -1466,41 +1415,26 @@ interface PatternPack {
1466
1415
  workspace: string;
1467
1416
  }>;
1468
1417
  /**
1469
- * Per-property-access recognizers, the counterpart to
1470
- * `invocationRecognizers`. Use these for patterns that read a value
1471
- * through property access without invoking it: `process.env.X`
1472
- * env-var reads, `Date.now()`-style time reads (which is actually a
1473
- * call, see invocationRecognizers), bare `module.constant` reads.
1474
- *
1475
- * A recognizer here is handed a property access, a call, or a tagged
1476
- * template, and guards its own shapes. The tagged template is there
1477
- * for a library that takes its whole argument as one, the way
1478
- * `prisma.$queryRaw` and `gql` do.
1479
- *
1480
- * The scope rules are the same as for invocationRecognizers: it fires
1481
- * on every such node in the function body and skips nested function
1482
- * bodies. The emission contract is the same, so effects land on the
1483
- * enclosing default-branch transition.
1484
- *
1485
- * The arguments are opaque here and narrowed by the adapter, for the
1486
- * same reason as in invocationRecognizers.
1418
+ * Per-access recognizers, the counterpart to `invocationRecognizers` for
1419
+ * values read without a call, such as `process.env.X`. A recognizer is
1420
+ * handed a property access, a call or a tagged template, and checks the
1421
+ * node itself. The tagged template covers libraries that take a whole
1422
+ * template as one argument, like `prisma.$queryRaw` and `gql`. Scope,
1423
+ * emission and argument types follow the `invocationRecognizers` rules.
1487
1424
  */
1488
1425
  accessRecognizers?: AccessRecognizer[];
1489
1426
  /**
1490
- * What the pack wrote as data rather than as code, for the health
1491
- * report. Absent for a pack written as a hand-rolled walk, which is
1492
- * itself the thing the report says.
1427
+ * What the pack wrote as data, for the pack health report. A pack
1428
+ * written as a hand-rolled walk leaves it out, and the report says so.
1493
1429
  */
1494
1430
  declarations?: PackDeclarations;
1495
1431
  }
1496
1432
  /**
1497
- * The price a pack paid for what it matches, so the migration onto the
1498
- * declared surface can be measured rather than asserted.
1499
- *
1500
- * Expressiveness is bought link by link: a link answered with data is
1501
- * inspectable, serializable and runs on any adapter, while a link
1502
- * answered with a function is code that only its own language runs.
1503
- * Both are allowed, and the report says which is which.
1433
+ * How much of what a pack matches is declared as data and how much is
1434
+ * code, counted for the pack health report. A link given as data can be
1435
+ * inspected, serialized and run by any adapter. A link given as a
1436
+ * function only runs in its own language. Both are allowed, and the
1437
+ * report counts each kind.
1504
1438
  */
1505
1439
  interface PackDeclarations {
1506
1440
  declarations: DeclaredMatch[];
@@ -1523,22 +1457,19 @@ interface DeclaredMatch {
1523
1457
  example: string | null;
1524
1458
  }
1525
1459
  /**
1526
- * Per-call-site recognizer hook. See `PatternPack.invocationRecognizers`
1527
- * for the contract and threading model.
1460
+ * Per-call recognizer hook. `PatternPack.invocationRecognizers` says when
1461
+ * it is called and what it returns.
1528
1462
  */
1529
1463
  type InvocationRecognizer<TCtx = unknown> = (call: unknown, ctx: TCtx) => Effect[] | null;
1530
1464
  /**
1531
- * Per-property-access recognizer hook. See
1532
- * `PatternPack.accessRecognizers` for the contract and threading model.
1465
+ * Per-access recognizer hook. `PatternPack.accessRecognizers` says when
1466
+ * it is called and what it returns.
1533
1467
  */
1534
1468
  type AccessRecognizer<TCtx = unknown> = (access: unknown, ctx: TCtx) => Effect[] | null;
1535
1469
  /**
1536
- * The bare minimum a `subUnits` hook needs to know about the parent code
1537
- * unit it is working inside. `func` is left opaque here because each
1538
- * language adapter brands its own FunctionRoot type. This interface lives in
1539
- * the extractor only so `PatternPack` can refer to it, and the adapter-level
1540
- * context types like `TsSubUnitContext` narrow `func` to a concrete AST
1541
- * handle.
1470
+ * The parent unit a `subUnits` hook works inside. `func` is opaque because
1471
+ * each adapter has its own function-root type; adapter context types such
1472
+ * as `TsSubUnitContext` narrow it.
1542
1473
  */
1543
1474
  interface DiscoveredSubUnitParent {
1544
1475
  /** Handle to the parent's function body. Opaque at extractor level. */
@@ -1549,15 +1480,11 @@ interface DiscoveredSubUnitParent {
1549
1480
  kind: string;
1550
1481
  }
1551
1482
  /**
1552
- * What a pack's `discoverUnits` hook returns for each top-level unit it
1553
- * finds. It is to discovery what `DiscoveredSubUnit` is to sub-units. The
1554
- * adapter widens these into its own internal `DiscoveredUnit` type, which has
1555
- * adapter-specific fields like `routeInfo` and `packageExportInfo` on it, and
1556
- * then runs them through the normal extraction pipeline.
1557
- *
1558
- * Pack authors only ever see opaque handles: `func` is whatever the adapter's
1559
- * primitive returned, and the adapter narrows it to its concrete
1560
- * function-root type (`FunctionRoot` in `@suss/adapter-typescript`).
1483
+ * One top-level unit a pack's `discoverUnits` hook found. The adapter
1484
+ * widens it into its own internal unit type and runs it through the
1485
+ * normal extraction pipeline. `func` is whatever the adapter's primitive
1486
+ * returned, and the adapter narrows it to its function-root type
1487
+ * (`FunctionRoot` in `@suss/adapter-typescript`).
1561
1488
  */
1562
1489
  interface DiscoveredCustomUnit {
1563
1490
  /** Function body handle, opaque here. */
@@ -1567,10 +1494,9 @@ interface DiscoveredCustomUnit {
1567
1494
  /** Discovered name (e.g. "UserCard"). */
1568
1495
  name: string;
1569
1496
  /**
1570
- * The unit's callable identity, when the pack states one: the module
1571
- * it lives in and the name it goes by there. A server action is the
1572
- * case, so intent and the keyed pairing pass can refer to it. The
1573
- * adapter puts both on the unit's function-call binding.
1497
+ * The module the unit is exported from and its export name, when the
1498
+ * pack states them. Server actions use this so intent and keyed pairing
1499
+ * can refer to them. The adapter puts both on the function-call binding.
1574
1500
  */
1575
1501
  functionCallInfo?: {
1576
1502
  module: string;
@@ -1587,19 +1513,15 @@ interface DiscoveredCustomUnit {
1587
1513
  */
1588
1514
  inputMapping?: InputMappingPattern;
1589
1515
  /**
1590
- * REST route identity for units a callback discovers against an
1591
- * external manifest (a SAM/CFN template's `Events` block, an infra
1592
- * routing declaration, and so on) rather than an in-code registration.
1593
- * When set, the adapter builds a `rest` binding from `(method, path)`,
1594
- * the same binding a NestJS controller gets from decorator-derived
1595
- * `routeInfo`, and the discoverUnits callback never has to reach into
1596
- * the adapter's binding machinery. Either half is null when the
1597
- * source does not state it, and a binding missing one pairs with
1598
- * nothing.
1516
+ * The REST route for a unit found through an external manifest, such as
1517
+ * a SAM template's `Events` block, instead of an in-code registration.
1518
+ * The adapter builds a `rest` binding from it, the same one a NestJS
1519
+ * controller gets. Leave either half null when the source does not
1520
+ * state it; a binding missing one pairs with nothing.
1599
1521
  *
1600
- * One function bound to several routes emits one DiscoveredCustomUnit
1601
- * per route. The adapter's per-file claim dedup keys on
1602
- * `(func, kind, method, path)`, so all of those variants survive.
1522
+ * A function bound to several routes returns one unit per route. The
1523
+ * per-file claim dedup keys on function, kind, method and path, so all
1524
+ * of them are kept.
1603
1525
  */
1604
1526
  routeInfo?: {
1605
1527
  method: string | null;
@@ -1645,9 +1567,8 @@ interface DiscoveredCustomUnit {
1645
1567
  metadata?: Record<string, unknown>;
1646
1568
  }
1647
1569
  /**
1648
- * What a pack's `subUnits` hook returns per synthesized child. The
1649
- * adapter pipes each of these through the same extraction + assembly
1650
- * pipeline used for top-level-discovered units.
1570
+ * One child unit a pack's `subUnits` hook built. The adapter extracts and
1571
+ * assembles it the same way as a top-level unit.
1651
1572
  */
1652
1573
  interface DiscoveredSubUnit {
1653
1574
  /** Function body handle, opaque here. */
@@ -1681,8 +1602,8 @@ interface DiscoveredSubUnit {
1681
1602
  * again.
1682
1603
  *
1683
1604
  * The `Cond` type parameter is the language's own handle for a condition
1684
- * expression (a ts-morph Expression for TypeScript, a tree-sitter node for a
1685
- * language added later). The engine passes it through untouched for a caller
1605
+ * expression (a ts-morph Expression in TypeScript, a tree-sitter node in
1606
+ * Python and Ruby). The engine passes it through untouched for a caller
1686
1607
  * to parse afterwards, and never looks inside it.
1687
1608
  */
1688
1609
  /** Where a condition came from, in terms of what that branch's body does. */
@@ -1723,18 +1644,18 @@ interface CaseGroup<Cond> {
1723
1644
  readonly body: StatementBlock<Cond>;
1724
1645
  }
1725
1646
  /**
1726
- * What every lowered statement has on it, whichever construct it is.
1647
+ * The fields every lowered statement has, whichever construct it is.
1727
1648
  *
1728
1649
  * The lowering works `exitKind` out by scanning the statement's own
1729
1650
  * subtree for a return or a throw, skipping every nested function body.
1730
1651
  *
1731
1652
  * `callbacks` is the bodies of the functions this statement passes to
1732
- * calls it makes, as far as the language counts those as running for the
1653
+ * calls it makes, when the language treats those as running for the
1733
1654
  * enclosing unit. The engine walks them on the same path as the
1734
1655
  * statement, so their branches are the unit's branches. Their `return`
1735
- * is not the unit's, so a path that ends inside one continues past the
1736
- * statement. A lowering that leaves the field out behaves as it did
1737
- * before there was one.
1656
+ * does not leave the unit, so a path that ends inside one continues past
1657
+ * the statement. When a lowering leaves the field out, no callback bodies
1658
+ * are walked.
1738
1659
  */
1739
1660
  interface LoweredStatementParts<Cond> {
1740
1661
  readonly exitKind: ExitKind;
@@ -1765,7 +1686,7 @@ type StructuredStatement<Cond = unknown> = (LoweredStatementParts<Cond> & {
1765
1686
  readonly kind: "try";
1766
1687
  readonly tryBody: StatementBlock<Cond>;
1767
1688
  readonly catchBody: StatementBlock<Cond> | null;
1768
- /** 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. */
1689
+ /** Kept only for validation. The engine does not model a finally that exits or contains one of the caller's terminals, and never enumerates its conditions. */
1769
1690
  readonly finallyBody: StatementBlock<Cond> | null;
1770
1691
  }) | (LoweredStatementParts<Cond> & {
1771
1692
  readonly kind: "exit";
@@ -1780,14 +1701,14 @@ type StructuredStatement<Cond = unknown> = (LoweredStatementParts<Cond> & {
1780
1701
  * value in.
1781
1702
  *
1782
1703
  * A reader that returns `T | null` makes null mean three things at once: the
1783
- * source left the value out, the source states it and the reader could not
1704
+ * source left the value out, the source wrote it and the reader could not
1784
1705
  * evaluate it, or several candidates matched and the reader picked none.
1785
- * Each needs something different said about it on the summary, and the type
1786
- * does not show which the reader meant, so every new reader decides it again
1787
- * and some of them decide wrong.
1706
+ * Each needs a different note on the summary, and `T | null` does not show
1707
+ * which case happened, so every new reader has to choose again and some
1708
+ * choose wrong.
1788
1709
  *
1789
1710
  * The rule that turns a reading into a claim, a library default, or a gap
1790
- * lives in the summary builder in this package and is not exported.
1711
+ * is in the summary builder in this package and is not exported.
1791
1712
  */
1792
1713
  /** Where in a file a value is written, as byte offsets into that file. */
1793
1714
  interface SourceRange {
@@ -1830,9 +1751,9 @@ declare const absentReading: Reading<never>;
1830
1751
  */
1831
1752
  declare function unreadableReading<T>(reason: string, range: SourceRange): Reading<T>;
1832
1753
  /**
1833
- * Several values could be right and the reader picked none. Keeping the
1834
- * candidates leaves what was found available to whoever later teaches the
1835
- * reader how to choose, and every step afterwards keeps them too.
1754
+ * Several values could be right and the reader picked none. The candidates
1755
+ * are kept through every later step, so code that can choose among them
1756
+ * still has them.
1836
1757
  *
1837
1758
  * The range is where somebody should look to see the ambiguity. An ambiguity
1838
1759
  * often spans more than one place (two mounts of one router, in two files),
@@ -1843,8 +1764,7 @@ declare function ambiguousReading<T>(candidates: readonly T[], reason: string, r
1843
1764
  /**
1844
1765
  * A reading paired with what the library does when the source says nothing.
1845
1766
  * Only a pack may supply that default, so the value a summary claims for an
1846
- * unstated field is library-defined and lives in the pack alongside
1847
- * everything else the pack already declares.
1767
+ * unstated field always comes from a pack's declaration about the library.
1848
1768
  */
1849
1769
  interface DefaultedReading<T> {
1850
1770
  /** What the source said. */
@@ -1871,9 +1791,8 @@ declare function mapReading<T, U>(reading: Reading<T>, f: (value: T) => U): Read
1871
1791
  *
1872
1792
  * `f` runs on each of an ambiguous reading's candidates too, and the ones
1873
1793
  * that do read come back as the candidates of an ambiguous reading with the
1874
- * same reason. That way the alternatives stay next to the chosen value all
1875
- * the way to the summary, instead of being dropped at the first step
1876
- * that reads further.
1794
+ * same reason. The alternatives reach the summary instead of being dropped
1795
+ * at the first step that reads further.
1877
1796
  */
1878
1797
  declare function andThenReading<T, U>(reading: Reading<T>, f: (value: T, range: SourceRange) => Reading<U>): Reading<U>;
1879
1798
  /**
@@ -1888,10 +1807,10 @@ interface ChosenReading<T> {
1888
1807
  */
1889
1808
  reading: Reading<T>;
1890
1809
  /**
1891
- * The readings the choice passed over that nobody could resolve. A later
1892
- * reading supplying the value does not settle what an earlier one said
1893
- * and could not be read, so these still go to the
1894
- * builder and their reasons still reach the summary.
1810
+ * The unreadable or ambiguous readings the choice passed over. A later
1811
+ * reading that supplies the value does not settle an earlier one that
1812
+ * could not be read, so these still go to the builder and their reasons
1813
+ * reach the summary.
1895
1814
  */
1896
1815
  passedOver: readonly Reading<T>[];
1897
1816
  }
@@ -1909,32 +1828,29 @@ declare function firstWrittenReading<T>(readings: readonly Reading<T>[]): Chosen
1909
1828
  * parameters that decide what each of the handler's parameters is, and
1910
1829
  * that has to be settled before there is a summary field to fill in.
1911
1830
  *
1912
- * This applies no default and gives no reason, so most of what a summary
1913
- * says must not be written from it. Hand the reading to the
1914
- * builder instead, and the fixed rule gets applied to it once, somewhere
1915
- * review can see it.
1831
+ * This applies no default and gives no reason, so most summary fields must
1832
+ * not be written from it. Pass the reading to the builder instead, which
1833
+ * applies the one rule for readings in a place a reviewer can find.
1916
1834
  *
1917
1835
  * The identity fields of a boundary binding are the exception, and the path
1918
- * this reads is one of them. A binding either says where a unit is or says
1919
- * nothing and pairs with nothing, and no pack declares a
1920
- * default for what a boundary is called, so the value the builder would
1921
- * put there is the value this gives back. Hand the reading over as well and
1922
- * the reason still becomes a gap.
1836
+ * is one of them. No pack declares a default for what a boundary is called,
1837
+ * so the builder would write the same value this returns. Pass the reading
1838
+ * to the builder as well, so its reason still becomes a gap.
1923
1839
  */
1924
1840
  declare function valueToReadFurtherFrom<T>(reading: Reading<T>): T | null;
1925
1841
 
1926
1842
  /**
1927
- * composeWrappers.ts: what a unit does once the code registered around
1928
- * it is folded in.
1843
+ * Folds the code registered around a unit, such as middleware, into what
1844
+ * the unit does.
1929
1845
  *
1930
1846
  * A wrapper is a meta-function: it takes a unit and returns a unit. The
1931
1847
  * call to its continuation comes through as a `delegate` transition, so
1932
1848
  * a path that ends before that call is a response the caller gets
1933
1849
  * instead of the unit's own, and a path that reaches it hands the
1934
- * request on without responding. The responses go beside the unit's own
1935
- * outcomes and the pass-throughs go nowhere, because the unit's
1936
- * outcomes already say what happens on them. The package README works
1937
- * the example through and says what composition does not read.
1850
+ * request on without responding. The responses are added beside the
1851
+ * unit's own outcomes. The pass-throughs are dropped, because the unit's
1852
+ * outcomes already say what happens on them. This package's DESIGN.md
1853
+ * works through an example and lists what composition does not read.
1938
1854
  */
1939
1855
 
1940
1856
  /**
@@ -1951,6 +1867,57 @@ interface ComposeOptions {
1951
1867
  */
1952
1868
  declare function composeWrappers(summaries: readonly BehavioralSummary[], options?: ComposeOptions): BehavioralSummary[];
1953
1869
 
1870
+ /**
1871
+ * The modules a project lists in `suss.json`, as every adapter reads them.
1872
+ *
1873
+ * A module is a name, a root folder, and the files whose exports other
1874
+ * modules are meant to call. The CLI reads the list and hands it to the
1875
+ * adapter with absolute paths. Which file is public when the list does not
1876
+ * say is a fact about the language, so each adapter settles that first.
1877
+ * The rest is the same in all three adapters: which module a file belongs
1878
+ * to, the module stamped on each summary, and the part of the cache key
1879
+ * that makes an edit to the list run discovery again.
1880
+ */
1881
+
1882
+ /** The recognition on every module export's binding, in each language. */
1883
+ declare const MODULE_SURFACE_RECOGNITION = "modules";
1884
+ interface DeclaredModule {
1885
+ readonly name: string;
1886
+ /** Absolute. Every file under it belongs to the module. */
1887
+ readonly root: string;
1888
+ /**
1889
+ * Absolute. The files whose exports are the module's public surface.
1890
+ * Left out when `suss.json` does not say, and then the adapter's
1891
+ * convention for its language decides.
1892
+ */
1893
+ readonly public?: readonly string[];
1894
+ }
1895
+ /** A module whose public files are settled. An empty list means it has none. */
1896
+ interface SettledModule extends DeclaredModule {
1897
+ readonly public: readonly string[];
1898
+ }
1899
+ /**
1900
+ * Each module with its public files. A module `suss.json` gave files for
1901
+ * keeps them. Otherwise the language's candidates are tried, and only the
1902
+ * ones on disk are kept. A candidate is a path relative to the module's
1903
+ * root, with `{name}` standing for the module's name, as in `index.ts`
1904
+ * or `../{name}.rb`.
1905
+ */
1906
+ declare function settleModules(modules: readonly DeclaredModule[] | undefined, candidates: readonly string[]): SettledModule[];
1907
+ /**
1908
+ * The module a file belongs to. A public file belongs to its module even
1909
+ * when it is outside the root, as a Ruby `lib/billing.rb` is next to
1910
+ * `lib/billing/`. Otherwise the deepest root that contains the file
1911
+ * wins, so a module nested inside another's folder keeps its own files.
1912
+ */
1913
+ declare function moduleOfFile(modules: readonly SettledModule[], file: string): SettledModule | undefined;
1914
+ /**
1915
+ * Writes each summary's module onto its location. `root` is the directory
1916
+ * a shortened `location.file` is relative to, when the adapter has
1917
+ * already shortened it.
1918
+ */
1919
+ declare function stampModules(summaries: readonly BehavioralSummary[], modules: readonly SettledModule[], root: string | undefined): void;
1920
+
1954
1921
  /**
1955
1922
  * Which branch an effect belongs to.
1956
1923
  *
@@ -1961,8 +1928,8 @@ declare function composeWrappers(summaries: readonly BehavioralSummary[], option
1961
1928
  * attributes effects to branches reads from here, so they all decide
1962
1929
  * it the same way.
1963
1930
  *
1964
- * Neither test is enough alone, and the README beside this file says
1965
- * why with the two shapes that go wrong.
1931
+ * Neither test is enough alone. This package's DESIGN.md shows the two
1932
+ * cases where one test on its own gets the branch wrong.
1966
1933
  */
1967
1934
 
1968
1935
  /**
@@ -1982,7 +1949,26 @@ declare function guardsHoldOn(preconditions: readonly RawCondition[] | undefined
1982
1949
  declare function runsBefore(effectLine: number, terminalEndLine: number): boolean;
1983
1950
 
1984
1951
  /**
1985
- * moduleImports.ts: writing `metadata.moduleImports` on a summary set.
1952
+ * Marks the summaries whose return becomes the process's exit code, as
1953
+ * `metadata.process.exitCodeFrom: "return"`, and clears the mark from
1954
+ * every other one.
1955
+ *
1956
+ * Every adapter works the functions out once per run and calls this over
1957
+ * every summary the run hands back, reused ones included. The chain runs
1958
+ * from the file that sets the code into files that never record that
1959
+ * file as a dependency, so a mark a cache kept could otherwise outlive an
1960
+ * edit to the entry file.
1961
+ */
1962
+
1963
+ /**
1964
+ * `returnsTheCode` contains function keys, the absolute file and the
1965
+ * function's span as `declarationKey` writes them. A summary whose file
1966
+ * was shortened is resolved against `workspaceRoot` first.
1967
+ */
1968
+ declare function markReturnsAsExitCode(summaries: readonly BehavioralSummary[], returnsTheCode: ReadonlySet<string>, workspaceRoot?: string): void;
1969
+
1970
+ /**
1971
+ * Writes `metadata.moduleImports` on a set of summaries.
1986
1972
  *
1987
1973
  * Every adapter records the project files a summary's own file depends
1988
1974
  * on, and a checker rebuilds the module graph from that field to find
@@ -1999,15 +1985,14 @@ declare function runsBefore(effectLine: number, terminalEndLine: number): boolea
1999
1985
  declare function stampModuleImports(summaries: BehavioralSummary[], importsOf: (file: string) => Iterable<string> | undefined): void;
2000
1986
 
2001
1987
  /**
2002
- * moduleInit.ts: the raw structure for what a module does when it loads.
1988
+ * Builds the raw structure for what a module does when it loads.
2003
1989
  *
2004
1990
  * A module's top-level statements run once, when it is first imported,
2005
- * and a service that reads its configuration there reads it there for
2006
- * good. Walking unit bodies alone never sees that read, so the read gets
2007
- * a unit of its own, one per file, named after the file, with no
2008
- * boundary. Attributing it to each handler would report one read as
2009
- * several. The calls those statements make go on the same unit, so the
2010
- * closure can reach the functions behind them.
1991
+ * and a service often reads its configuration there. Walking unit bodies
1992
+ * never sees that read, so the read gets a unit of its own: one per file,
1993
+ * named after the file, with no boundary. Attributing it to each handler
1994
+ * would report one read as several. The calls those statements make go on
1995
+ * the same unit, so the closure can reach the functions behind them.
2011
1996
  *
2012
1997
  * The one default branch has a `void` terminal, because module
2013
1998
  * initialization returns to nobody. Every adapter builds it the same way.
@@ -2028,12 +2013,9 @@ interface ModuleInitOptions {
2028
2013
  declare function moduleInitStructure(options: ModuleInitOptions): RawCodeStructure;
2029
2014
 
2030
2015
  /**
2031
- * Small helpers for pattern packs that are built the same way.
2032
- *
2033
- * The pack interface is deliberately declarative: a PatternPack is a data
2034
- * object the adapter interprets, and most differences between frameworks are
2035
- * best expressed that way. A few patterns, though, repeat word for word
2036
- * across packs, and this module collects those so they are written once.
2016
+ * Builders for the pack entries that several packs would otherwise copy
2017
+ * word for word. A pack is still a data object the adapter interprets;
2018
+ * these functions only write that data out.
2037
2019
  */
2038
2020
 
2039
2021
  /**
@@ -2042,15 +2024,14 @@ declare function moduleInitStructure(options: ModuleInitOptions): RawCodeStructu
2042
2024
  * like.
2043
2025
  *
2044
2026
  * Each `importNames` entry produces one DiscoveryPattern, because a library
2045
- * usually exposes both a default export and a named export that each produce
2046
- * the routable instance (Express has `express()` and
2047
- * `Router()`, Fastify has `fastify()` and `Fastify`). The binding
2048
- * extraction, method from the registration and path from position 0, is
2049
- * the same for every HTTP server framework we support.
2027
+ * usually exports more than one function that makes the routable instance
2028
+ * (Express has `express()` and `Router()`, Fastify has `fastify()` and
2029
+ * `Fastify`). The binding extraction, method from the registration and
2030
+ * path from position 0, is the same for every supported HTTP framework.
2050
2031
  *
2051
- * Callers still pass the `methods` list themselves, because frameworks
2052
- * support different HTTP verbs. Fastify includes `.head` and `.options`;
2053
- * Express historically does not by default.
2032
+ * Callers pass the `methods` list themselves, because frameworks accept
2033
+ * different verbs: Fastify has `.head` and `.options`, and Express does
2034
+ * not by default.
2054
2035
  *
2055
2036
  * @example
2056
2037
  * discovery: httpRouteDiscovery({
@@ -2077,7 +2058,7 @@ declare function httpRouteDiscovery(opts: {
2077
2058
  * handlers: middleware, error handlers, validation hooks.
2078
2059
  *
2079
2060
  * A wrapper becomes a unit of its own, summarized like any other, so
2080
- * these entries carry no `bindingExtraction` and no registration chain.
2061
+ * these entries have no `bindingExtraction` and no registration chain.
2081
2062
  * Their `match` is there for the import and the imported name, which is
2082
2063
  * how wrapper discovery works out which variables in a file are the
2083
2064
  * routable. One entry per (import name, wrapper shape) pair, since a
@@ -2095,7 +2076,7 @@ declare function wrapperDiscovery(opts: {
2095
2076
  * placeholders for the call's positional arguments.
2096
2077
  */
2097
2078
  interface RegistrationHelper {
2098
- /** What the helper is called, as the project's code writes it. */
2079
+ /** The helper's name as the project's code writes it. */
2099
2080
  helperName: string;
2100
2081
  /** The file declaring it, so a same-named function elsewhere is left alone. */
2101
2082
  importModule?: string;
@@ -2122,9 +2103,8 @@ interface RegistrationHelper {
2122
2103
  */
2123
2104
  declare function registrationHelperDiscovery(helpers: readonly RegistrationHelper[], kind?: string): DiscoveryPattern[];
2124
2105
  /**
2125
- * The standing request an HTTP pack makes to have the project's own
2126
- * route helpers read, so what each one registers is a fact about the
2127
- * code rather than something the project restates in config.
2106
+ * Asks for the project's own route helpers to be read, so what each one
2107
+ * registers comes from the code instead of being restated in config.
2128
2108
  */
2129
2109
  declare function routeHelperIndex(opts: {
2130
2110
  importModule: string;
@@ -2136,8 +2116,8 @@ declare function routeHelperIndex(opts: {
2136
2116
  /**
2137
2117
  * The payload behind a `JSON.stringify(...)` call, or the argument
2138
2118
  * unchanged when it is anything else. A producer serializes its message
2139
- * before sending it, and the shape worth comparing across the boundary
2140
- * is what went in, not the string that came out.
2119
+ * before sending it, so the value compared across the boundary is the
2120
+ * object that went into the string.
2141
2121
  */
2142
2122
  declare function unwrapJsonStringify(body: EffectArg | null): EffectArg | null;
2143
2123
 
@@ -2145,13 +2125,12 @@ declare function unwrapJsonStringify(body: EffectArg | null): EffectArg | null;
2145
2125
  * Option declarations more than one pack takes.
2146
2126
  *
2147
2127
  * A pack exports `optionsSchema` beside its factory, and the CLI parses
2148
- * a `-f pack=config.json` file against it before the factory runs, so a
2149
- * key nobody declared is refused by name instead of read as nothing.
2128
+ * a `-f pack=config.json` file against it before the factory runs, so an
2129
+ * undeclared key is refused by name instead of being silently ignored.
2150
2130
  *
2151
- * Four packs accept `storageSystem` and three accept `scope`. A copy
2152
- * per pack is how one of them ends up allowing a value the others
2153
- * reject, which is what happened when two doc comments named
2154
- * `"postgres"` and the union allowed only `"postgresql"`.
2131
+ * Four packs accept `storageSystem` and three accept `scope`, so each
2132
+ * option is declared once here. With a copy per pack, one pack can end up
2133
+ * accepting a value the others reject.
2155
2134
  */
2156
2135
 
2157
2136
  /**
@@ -2175,9 +2154,9 @@ declare const scopeOption: z.ZodString;
2175
2154
  * A call the project has declared to be its own dispatcher, which both
2176
2155
  * message-bus packs take under `producers`.
2177
2156
  *
2178
- * `receiver` is the type rather than the variable: a service keeps its
2179
- * dispatcher in a field, a closure or a constructor parameter, and the
2180
- * type is the only thing stable across all three.
2157
+ * `receiver` is a type name because a service may keep its dispatcher in
2158
+ * a field, a closure or a constructor parameter, and the type is the same
2159
+ * in all three.
2181
2160
  */
2182
2161
  declare const configuredCallOption: z.ZodObject<{
2183
2162
  module: z.ZodString;
@@ -2197,14 +2176,14 @@ declare const configuredCallOption: z.ZodObject<{
2197
2176
  * Nothing here touches a language-specific AST node. A language adapter
2198
2177
  * lowers its own tree into `StructuredStatement` and hands that over.
2199
2178
  *
2200
- * Constructs a lowering declines to model come back as thrown
2201
- * `PathBudgetExceeded` or `UnmodeledFlow`, and the caller falls back to
2179
+ * On a construct it does not model, or on too many paths, the engine throws
2180
+ * `UnmodeledFlow` or `PathBudgetExceeded`, and the caller falls back to
2202
2181
  * enclosure conditions plus an opaque conjunct instead of guessing.
2203
2182
  */
2204
2183
 
2205
2184
  /** The cap on how many paths to enumerate. Past it the caller falls back to degraded conditions. */
2206
2185
  declare const MAX_PATHS = 256;
2207
- /** 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. */
2186
+ /** A construct the engine does not model, such as a stray break in a switch clause or a finally that exits. Callers catch this and degrade. */
2208
2187
  declare class UnmodeledFlow extends Error {
2209
2188
  }
2210
2189
  /** The path budget was exceeded. Callers catch this and degrade. */
@@ -2215,11 +2194,11 @@ interface StructuredPathConditionsInput<Cond, Terminal> {
2215
2194
  statements: StatementBlock<Cond>;
2216
2195
  /**
2217
2196
  * Every StructuredStatement reachable from `statements`, mapped to the
2218
- * terminals the caller gave us that sit directly at that node: its own
2219
- * position (a return, throw, or opaque leaf) or, for a branching
2220
- * construct, its header or test expression. A lowering builds this at the
2221
- * same time as the tree, so the engine never walks a raw source AST to
2222
- * find where a terminal lives.
2197
+ * caller's terminals found directly at that node: at its own position
2198
+ * (a return, throw, or opaque leaf) or, for a branching construct, in
2199
+ * its header or test expression. A lowering builds this at the same
2200
+ * time as the tree, so the engine never walks a raw source AST to find
2201
+ * where a terminal is.
2223
2202
  */
2224
2203
  terminalsByStmt: ReadonlyMap<StructuredStatement<Cond>, readonly Terminal[]>;
2225
2204
  }
@@ -2239,11 +2218,10 @@ interface StructuredPathConditionsResult<Cond, Terminal> {
2239
2218
  */
2240
2219
  declare function enumerateStructuredPaths<Cond, Terminal>(input: StructuredPathConditionsInput<Cond, Terminal>): StructuredPathConditionsResult<Cond, Terminal>;
2241
2220
  /**
2242
- * Enumerate, and when the engine gives up say so instead of throwing. Each
2243
- * terminal comes back reachable under one condition nobody can read, which
2244
- * keeps everything the caller already knew about it. A caller that can do
2245
- * better, by walking the terminal's own ancestors for the conditions that
2246
- * enclose it, should.
2221
+ * Enumerate, and when the engine gives up, report why instead of throwing.
2222
+ * Each terminal comes back reachable under one opaque marker condition, so
2223
+ * no terminal is lost. A caller that can walk a terminal's own ancestors
2224
+ * for the conditions that enclose it should use those instead.
2247
2225
  */
2248
2226
  declare function enumerateOrDegrade<Cond, Terminal>(input: StructuredPathConditionsInput<Cond, Terminal>, terminals: Iterable<Terminal>): StructuredPathConditionsResult<Cond, Terminal> & {
2249
2227
  /** Why the engine gave up, or null when it read the whole body. */
@@ -2256,12 +2234,10 @@ declare function enumerateOrDegrade<Cond, Terminal>(input: StructuredPathConditi
2256
2234
  * for a path without it.
2257
2235
  *
2258
2236
  * `readPredicate` turns the language's own expression handle into a
2259
- * predicate. Without one every condition stays opaque, which is what a
2260
- * caller that has no reader gets.
2237
+ * predicate. Without it every condition comes back with `structured: null`.
2261
2238
  */
2262
2239
  declare function sharedGatingConditions<Cond>(paths: readonly (readonly ConditionInfo<Cond>[])[] | undefined, readPredicate?: (expression: Cond) => Predicate): RawCondition[];
2263
2240
 
2264
- /** Anything with a stable id, which is what these key on. */
2265
2241
  interface Identified {
2266
2242
  readonly id: number;
2267
2243
  }
@@ -2271,7 +2247,7 @@ declare class IdSet<T extends Identified> implements Iterable<T> {
2271
2247
  constructor(nodes?: Iterable<T>);
2272
2248
  add(node: T): this;
2273
2249
  has(node: T): boolean;
2274
- /** The node this set was built with, which is the caller's own handle for it. */
2250
+ /** The wrapper that was added, which may be a different object from `node`. */
2275
2251
  get(node: T): T | undefined;
2276
2252
  get size(): number;
2277
2253
  [Symbol.iterator](): Iterator<T>;
@@ -2287,23 +2263,59 @@ declare class IdMap<T extends Identified, V> implements Iterable<[T, V]> {
2287
2263
  }
2288
2264
 
2289
2265
  /**
2290
- * nodeWalk.ts: the one recursion the adapters use to read a declaration
2291
- * wherever the language allows it to be written.
2266
+ * The enclosing definition of a tree-sitter node, kept for as long as
2267
+ * its parse is.
2268
+ *
2269
+ * A fact key includes the function a node is written in, so an adapter
2270
+ * looks it up for every read it keys, and each step up the tree is a call
2271
+ * into tree-sitter's wasm module that builds a fresh wrapper. A parse tree
2272
+ * does not change once it is built, so each result is kept in a WeakMap
2273
+ * keyed on the tree. The results go when the tree does, and a parse made
2274
+ * in a later run starts with nothing kept.
2292
2275
  *
2293
- * A declaration goes where a statement goes, and every language has more
2294
- * places for a statement than a reader remembers to list. So an adapter
2295
- * does not list the containers it descends into. It walks every named
2296
- * child and says only which nodes keep their own body, because that set
2297
- * is short and a grammar's own vocabulary already names it.
2276
+ * Only this read is kept. Keeping each node's children and fields too
2277
+ * cost more memory than it saved time.
2298
2278
  */
2299
- /** A parse tree, however a grammar spells one. */
2279
+ /** What the reader needs from a parse tree node. */
2280
+ interface ParsedNode<T> {
2281
+ readonly id: number;
2282
+ readonly type: string;
2283
+ readonly tree: object;
2284
+ readonly parent: T | null;
2285
+ }
2286
+ /** The nearest ancestor of each node whose type is one of `types`, read once per parse. */
2287
+ declare class EnclosingNodes<T extends ParsedNode<T>> {
2288
+ private readonly types;
2289
+ private readonly byTree;
2290
+ constructor(types: ReadonlySet<string>);
2291
+ /**
2292
+ * The nearest ancestor of one of the types, or null when there is none.
2293
+ * Every node passed on the way up has the same result, so each one is
2294
+ * kept, and a later read from a sibling stops one step up.
2295
+ */
2296
+ of(node: T): T | null;
2297
+ private keptFor;
2298
+ }
2299
+
2300
+ /**
2301
+ * The recursion the adapters use to find a declaration wherever the
2302
+ * language allows one to be written.
2303
+ *
2304
+ * A declaration can go anywhere a statement can, and every language has
2305
+ * more places for a statement than anyone remembers to list. So an adapter
2306
+ * does not list the containers to descend into. The walk visits every
2307
+ * named child, and the adapter only lists the nodes whose body belongs to
2308
+ * what they declare, because that list is short and the grammar already
2309
+ * has a node type for each.
2310
+ */
2311
+ /** The one thing the walk needs from a parse tree node. */
2300
2312
  interface WalkableNode<T> {
2301
2313
  readonly namedChildren: ReadonlyArray<T | null>;
2302
2314
  }
2303
2315
  /**
2304
2316
  * Returned by `into` for a node whose children the walk should leave
2305
- * unread, such as one whose body belongs to what it declares. A symbol,
2306
- * so it cannot collide with whatever a caller carries.
2317
+ * unread, such as one whose body belongs to what it declares. It is a
2318
+ * symbol so it cannot collide with any value a caller passes down.
2307
2319
  */
2308
2320
  declare const SKIP_CHILDREN: unique symbol;
2309
2321
  /** What a walk does at each node it reaches. */
@@ -2330,6 +2342,60 @@ declare function walkDescendants<T extends WalkableNode<T>, C>(root: T, carried:
2330
2342
  /** The deepest directory containing every file, or undefined when they share no absolute root. */
2331
2343
  declare function commonDirectoryOf(files: ReadonlyArray<string>): string | undefined;
2332
2344
 
2345
+ /**
2346
+ * Which files a test pack reads. `suss extract --intent` hands a test
2347
+ * pack the files the PRDs list under `coveredBy`, written the way the
2348
+ * author wrote them, usually from the repository root. An adapter walks
2349
+ * absolute paths, so an entry matches a file on whole path segments from
2350
+ * the end: `tests/test_orders.py` matches `/repo/api/tests/test_orders.py`
2351
+ * and not `/repo/api/tests/other_test_orders.py`. Every adapter's test
2352
+ * discovery goes through this, so the three languages agree on it.
2353
+ */
2354
+ /** Whether the file is on the list. Every file is when there is no list. */
2355
+ declare function isListedTestFile(file: string, listed: readonly string[] | undefined): boolean;
2356
+ /**
2357
+ * Whether a file's base name matches one of the runner's patterns, where
2358
+ * `*` matches any run of characters: `test_*.py` or `*_spec.rb`.
2359
+ */
2360
+ declare function matchesTestFileName(file: string, patterns: readonly string[]): boolean;
2361
+
2362
+ /**
2363
+ * Turns the places a value's walk ended into the value references a
2364
+ * transition records, the same way for every adapter.
2365
+ *
2366
+ * The walk itself is in `@suss/resolution`. What an input is depends on
2367
+ * the language and the framework, and so does reading a literal or
2368
+ * quoting the source, so the adapter says those three things. The order
2369
+ * they are tried in, and what a place that is none of them becomes, is
2370
+ * decided here.
2371
+ */
2372
+
2373
+ /** One place a walk ended, as much of it as this needs. */
2374
+ interface WalkEnd {
2375
+ /** The key of the member the walk ended at. */
2376
+ readonly key: string;
2377
+ /** The properties read off it on the way to the value, outermost first. */
2378
+ readonly path: readonly string[];
2379
+ /** A read at a key the source computes on the way, or null for none. */
2380
+ readonly computedAt: string | null;
2381
+ /** The language's own conversions on the way to the value, outermost first. */
2382
+ readonly conversions: readonly string[];
2383
+ readonly end: {
2384
+ readonly is: string;
2385
+ };
2386
+ }
2387
+ /** What only the adapter can say about a place a walk ended. */
2388
+ interface SourceSpelling<L extends WalkEnd> {
2389
+ /** The input the place is, or null when it is not one. */
2390
+ inputOf(leaf: L): ValueRef | null;
2391
+ /** The literal the written value at a key is, or undefined for anything else. */
2392
+ literalAt(key: string): string | number | boolean | null | undefined;
2393
+ /** The source written at a key, or null when the key is not a node. */
2394
+ textAt(key: string): string | null;
2395
+ }
2396
+ /** Every place a value's walk ended, as distinct value references. */
2397
+ declare function sourceRefsOf<L extends WalkEnd>(leaves: readonly L[], spelling: SourceSpelling<L>): ValueRef[];
2398
+
2333
2399
  /**
2334
2400
  * The small surface every language adapter implements, whatever that
2335
2401
  * language's own tooling looks like underneath.
@@ -2342,9 +2408,8 @@ declare function commonDirectoryOf(files: ReadonlyArray<string>): string | undef
2342
2408
 
2343
2409
  interface LanguageAdapter {
2344
2410
  /**
2345
- * Extract summaries from a specific list of files. Returns a
2346
- * Promise so an implementation can do concurrent I/O during
2347
- * discovery without bottlenecking on synchronous reads.
2411
+ * Summaries for the listed files. It is async so an implementation can
2412
+ * read files concurrently during discovery.
2348
2413
  */
2349
2414
  extractFromFiles(filePaths: string[]): Promise<BehavioralSummary[]>;
2350
2415
  /** Extract summaries from every source file the adapter's project knows about. */
@@ -2352,23 +2417,22 @@ interface LanguageAdapter {
2352
2417
  }
2353
2418
 
2354
2419
  /**
2355
- * adapterStamp.ts: the language-neutral half of an adapter's cache key.
2420
+ * The language-neutral half of an adapter's cache key.
2356
2421
  *
2357
- * An adapter's own version.ts calls `createAdapterStamp` with its own
2422
+ * Each adapter calls `createAdapterStamp` once, with its own
2358
2423
  * `import.meta.url` and its hand-bumped version, and keeps the result for
2359
- * the life of the process. The stamp hashes the adapter's own dist file
2360
- * plus every analysis package it ships beside (this package,
2361
- * `@suss/resolution`, `@suss/datalog`, `@suss/behavioral-ir`), so a
2362
- * release that changes any of them invalidates an older cache, and a
2363
- * dev rebuild invalidates on its own with no version bump by hand.
2364
- * Running from source has no dist file to hash, and that mode declines
2365
- * to cache. The extraction cache's design is in this package's README.
2424
+ * the life of the process. The stamp hashes the adapter's dist file and
2425
+ * every analysis package shipped with it (this package, `@suss/resolution`,
2426
+ * `@suss/datalog`, `@suss/behavioral-ir`). A release that changes any of
2427
+ * them invalidates an older cache, and so does a local rebuild, with no
2428
+ * version bump. Running from source leaves no dist file to hash, so that
2429
+ * mode turns the cache off. This package's DESIGN.md describes the cache.
2366
2430
  */
2431
+
2367
2432
  /**
2368
2433
  * Whether this process can see the adapter's own code. `bundle` includes
2369
- * a hash that changes whenever the code does. `source` means nothing
2370
- * here could find it, so a cache key built from this stamp says nothing
2371
- * about the code that will produce the results.
2434
+ * a hash that changes whenever the code does. `source` means no bundle was
2435
+ * found, so a cache key built from this stamp cannot tell two builds apart.
2372
2436
  */
2373
2437
  type AdapterCodeStamp = {
2374
2438
  kind: "bundle";
@@ -2376,24 +2440,22 @@ type AdapterCodeStamp = {
2376
2440
  } | {
2377
2441
  kind: "source";
2378
2442
  };
2379
- /** What `createAdapterStamp` hands back to an adapter's own version.ts. */
2380
2443
  interface AdapterStamp {
2381
2444
  /** The stamp for the running adapter, computed once per process. */
2382
2445
  codeStamp(): AdapterCodeStamp;
2383
2446
  /**
2384
- * Cache-friendly identity for this adapter plus its packs. Stable
2385
- * across processes given the same inputs; bumps when any pack arrives
2386
- * with a new version stamp, the adapter version changes, or the
2387
- * loaded adapter dist file changes (dev-mode rebuild auto-invalidation).
2447
+ * A cache key for this adapter and its packs. It is the same across
2448
+ * processes for the same inputs, and changes when a pack's version, the
2449
+ * adapter's version, or the adapter's dist file changes.
2388
2450
  */
2389
2451
  packsDigest(packVersions: ReadonlyArray<{
2390
2452
  name: string;
2391
2453
  version?: string;
2392
2454
  }>): string;
2393
2455
  /**
2394
- * `cacheDir` unless this process loaded the adapter from source, where
2395
- * nothing can tell one build of it from another; then this returns
2396
- * null and, once per process, says why on stderr.
2456
+ * Returns `cacheDir`, or null when this process loaded the adapter from
2457
+ * source, since then one build cannot be told from another. The first
2458
+ * null in a process also prints the reason to stderr.
2397
2459
  */
2398
2460
  declineWhenRunFromSource(cacheDir: string | null): string | null;
2399
2461
  }
@@ -2410,7 +2472,7 @@ declare function createAdapterStamp(config: {
2410
2472
  /**
2411
2473
  * The hash for a bundle directory: the bundle itself plus every analysis
2412
2474
  * package that can be located. Empty when the directory has no bundle in
2413
- * it, which is what running from source looks like.
2475
+ * it, as happens when running from source.
2414
2476
  */
2415
2477
  declare function computeDistHashFrom(dir: string): string;
2416
2478
  /**
@@ -2429,19 +2491,40 @@ declare function computeContentHash(paths: readonly string[]): string;
2429
2491
  * content go in, so a file that moves counts as a change even when
2430
2492
  * every byte in it stayed the same.
2431
2493
  *
2432
- * A file that cannot be read stamps as absent rather than voiding the
2433
- * whole stamp, which is what `computeContentHash` does. These files
2434
- * belong to the project rather than to the installed tool: one of them
2435
- * being gone is a fact about the project the next run should notice,
2436
- * not a reason to stop telling runs apart.
2494
+ * A file that cannot be read is hashed as absent, where
2495
+ * `computeContentHash` would return an empty stamp. These files belong
2496
+ * to the project, so a missing one is a change the next run should see.
2437
2497
  */
2438
2498
  declare function projectFileStamp(paths: readonly string[]): string;
2499
+ /**
2500
+ * The run options that change what an extraction writes. An adapter passes
2501
+ * every one it takes, and leaves out the ones it does not have.
2502
+ */
2503
+ interface ExtractionConfig {
2504
+ gapHandling?: "strict" | "permissive" | "silent" | undefined;
2505
+ /** Whether a summary is written for each function a unit reaches. */
2506
+ includeReachable?: boolean | undefined;
2507
+ /** The directory a stored summary's `location.file` is relative to. */
2508
+ workspaceRoot?: string | undefined;
2509
+ /** The directory a stored summary's id measures its file from. */
2510
+ projectRoot?: string | undefined;
2511
+ /** The directories an absolute import was resolved against. */
2512
+ importRoots?: readonly string[] | undefined;
2513
+ /** The modules the project lists, which decide which units discovery finds. */
2514
+ modules?: readonly SettledModule[] | undefined;
2515
+ }
2516
+ /**
2517
+ * The run options as one part of the cache key, so a run never reads an
2518
+ * entry that a run with other options wrote. Every adapter builds this part
2519
+ * here, which keeps the three keys from drifting apart.
2520
+ */
2521
+ declare function extractionConfigStamp(config: ExtractionConfig): string;
2439
2522
  /**
2440
2523
  * The digest a run looks its cache entry up under. A pack may read
2441
2524
  * project files that are not among the ones a run walks, a SAM template
2442
2525
  * that decides which handlers exist for instance, so those belong in
2443
2526
  * the key next to the pack's own config. Which files they are depends
2444
- * on the files this run walks, so the digest is settled per run rather
2527
+ * on the files this run walks, so the digest is computed per run rather
2445
2528
  * than once per adapter.
2446
2529
  */
2447
2530
  declare function runDigest(packsDigest: string, packs: ReadonlyArray<{
@@ -2451,36 +2534,33 @@ declare function runDigest(packsDigest: string, packs: ReadonlyArray<{
2451
2534
  /**
2452
2535
  * The on-disk extraction cache, shared by every language adapter.
2453
2536
  *
2454
- * A run reuses the previous one's summaries whole when nothing
2455
- * changed, and per file when some files did. The entry directory is
2456
- * named after everything that has to agree before any reuse is sound:
2457
- * schema version, adapter version and code hash, pack versions, the
2458
- * extraction config stamp and the config file path. The manifest
2459
- * inside records a stamp and content hash per file, each summary's
2460
- * owning files, and per owning file the other files its walk read.
2461
- * An adapter attaches whatever it needs to a cached file's record
2462
- * through `meta`, which the cache stores and returns opaquely. The
2463
- * full design is in this package's README.
2537
+ * A run reuses the previous run's summaries whole when nothing changed,
2538
+ * and per file when some files did. The entry directory's name is a hash
2539
+ * of the schema version, the adapter and packs digest, and the config
2540
+ * path, so two builds that disagree on any of them never read each
2541
+ * other's entry. The manifest inside records a stamp and content hash per
2542
+ * file, the files each summary belongs to, and for each of those files the
2543
+ * other files its walk read. An adapter can store its own data on a file's
2544
+ * record through `meta`, which the cache never reads. The package's
2545
+ * design notes describe the cache in full.
2464
2546
  */
2465
2547
 
2466
2548
  /**
2467
- * How many keys' worth of entries a cache directory keeps. Two lets a
2468
- * pair of builds alternate (a branch switch, an adapter rebuild and a
2469
- * revert) without either losing its entry, and bounds a directory at
2470
- * twice one manifest.
2549
+ * How many entries a cache directory keeps. Two lets a pair of builds
2550
+ * alternate, as on a branch switch or an adapter rebuild and revert,
2551
+ * without either losing its entry, and keeps a directory to two manifests.
2471
2552
  */
2472
2553
  declare const MAX_ENTRIES = 2;
2473
2554
  /**
2474
2555
  * What one walked file contributed to the run, and what its walk read.
2475
2556
  * `deps` are the other files whose content went into this file's
2476
- * summaries; a change to any of them re-extracts this file. `claims`
2477
- * are the units this file's walk claimed, replayed before a partial
2478
- * run walks anything so precedence comes out the same. `meta` is
2479
- * whatever else the adapter needs to revalidate this file on a
2480
- * partial run (a route's mount prefixes, for instance); the cache
2481
- * never reads it. A file marked `cacheable: false` recorded a
2482
- * dependency the cache cannot pin to files, and is re-extracted on
2483
- * every partial run.
2557
+ * summaries, and a change to any of them re-extracts this file. `claims`
2558
+ * are the units this file's walk claimed. A partial run replays them
2559
+ * before walking anything, so the same pack wins each unit as before.
2560
+ * `meta` is whatever else the adapter needs to check this file again on
2561
+ * a partial run, such as a route's mount prefixes. A file marked
2562
+ * `cacheable: false` depends on something the cache cannot tie to files,
2563
+ * and is re-extracted on every partial run.
2484
2564
  */
2485
2565
  interface RootRecord<Meta = unknown> {
2486
2566
  path: string;
@@ -2493,15 +2573,31 @@ interface RootRecord<Meta = unknown> {
2493
2573
  meta: Meta;
2494
2574
  /** Packs that applied to the file, re-checked on a partial run. */
2495
2575
  packs: string[];
2576
+ /** The files this file imports, in the order the adapter's loader found them. */
2577
+ imports?: string[];
2496
2578
  }
2497
2579
  /**
2498
- * Which files each summary belongs to. `owners[i]` lists the walked
2499
- * files whose reuse keeps `summaries[i]` alive; an empty list marks a
2500
- * run-level summary that a partial run always recomputes.
2580
+ * What an adapter worked out about one unit of its own, such as the calls
2581
+ * one function body makes. `file` is the file the unit is in and `deps`
2582
+ * the other files that went into `data`. A partial run hands the record
2583
+ * back while `file` and every one of `deps` hash the same, so the adapter
2584
+ * can skip the work that produced it. The cache never reads `data`.
2501
2585
  */
2502
- interface CacheAttribution<Meta = unknown> {
2586
+ interface UnitRecord<Data = unknown> {
2587
+ key: string;
2588
+ file: string;
2589
+ deps: string[];
2590
+ data: Data;
2591
+ }
2592
+ /**
2593
+ * Which files each summary belongs to. `summaries[i]` is reused while at
2594
+ * least one file in `owners[i]` is. An empty list marks a summary built
2595
+ * over the whole run, which a partial run always recomputes.
2596
+ */
2597
+ interface CacheAttribution<Meta = unknown, UnitData = unknown> {
2503
2598
  roots: RootRecord<Meta>[];
2504
2599
  owners: string[][];
2600
+ units?: UnitRecord<UnitData>[];
2505
2601
  }
2506
2602
  /** Reported by `lookup`: what the cache decided, and why. */
2507
2603
  interface CacheDiagnostic {
@@ -2537,34 +2633,42 @@ type CacheLookup = {
2537
2633
  diagnostic: CacheDiagnostic;
2538
2634
  };
2539
2635
  /**
2540
- * What a `files-changed` miss can still reuse. `validRoots` is the
2541
- * cache's own verdict from hashes and recorded dependencies; the
2542
- * caller may demote further (a mount prefix that no longer matches)
2543
- * before calling `reuse`. `reuse` returns the summaries owned by at
2544
- * least one surviving root, in stored order, with their owners, so
2545
- * the caller can merge them and write the result back.
2636
+ * What a `files-changed` miss can still reuse. `validRoots` are the files
2637
+ * whose hashes and recorded dependencies are unchanged. The caller can
2638
+ * drop more of them, such as a file whose mount prefix no longer matches,
2639
+ * before calling `reuse`. `reuse` returns the summaries that belong to at
2640
+ * least one remaining file, in stored order, with their owners, so the
2641
+ * caller can merge them and write the result back.
2546
2642
  */
2547
- interface PartialPlan<Meta = unknown> {
2643
+ interface PartialPlan<Meta = unknown, UnitData = unknown> {
2548
2644
  /** Paths whose content hash differs, plus paths new to the set. */
2549
2645
  changed: Set<string>;
2550
2646
  removed: Set<string>;
2647
+ /**
2648
+ * The stored import list of every file whose content is unchanged, when
2649
+ * no file joined or left the set, since either can move where an import
2650
+ * resolves. Empty otherwise.
2651
+ */
2652
+ resolvedImports: Map<string, string[]>;
2551
2653
  roots: Map<string, RootRecord<Meta>>;
2552
2654
  validRoots: Set<string>;
2553
2655
  rootsDeclined: number;
2656
+ /** Unit records whose file and deps are all unchanged, by key. */
2657
+ validUnits: Map<string, UnitRecord<UnitData>>;
2554
2658
  reuse(valid: ReadonlySet<string>): {
2555
2659
  summaries: BehavioralSummary[];
2556
2660
  owners: string[][];
2557
2661
  };
2558
2662
  allSummaries(): BehavioralSummary[];
2559
2663
  /** The stored attribution decoded, for a write that changes nothing. */
2560
- attribution(): CacheAttribution<Meta>;
2664
+ attribution(): CacheAttribution<Meta, UnitData>;
2561
2665
  }
2562
- interface CacheLayer<Meta = unknown> {
2666
+ interface CacheLayer<Meta = unknown, UnitData = unknown> {
2563
2667
  /** The summary list on a hit, null on a miss. */
2564
2668
  tryHit(input: CacheInput): Promise<BehavioralSummary[] | null>;
2565
2669
  /**
2566
- * The lookup behind `tryHit`, with the reason a miss missed. Costs
2567
- * stats alone: no file reads, no AST work.
2670
+ * The lookup behind `tryHit`, with the reason for a miss. It stats the
2671
+ * files, and reads only those changed shortly before, to hash them.
2568
2672
  */
2569
2673
  lookup(input: CacheInput): Promise<CacheLookup>;
2570
2674
  /**
@@ -2572,33 +2676,39 @@ interface CacheLayer<Meta = unknown> {
2572
2676
  * work out which files' summaries survive. Null when the entry has
2573
2677
  * no per-file layer to reuse, or no entry matches the key at all.
2574
2678
  */
2575
- plan(input: CacheInput): Promise<PartialPlan<Meta> | null>;
2679
+ plan(input: CacheInput): Promise<PartialPlan<Meta, UnitData> | null>;
2576
2680
  /**
2577
- * Persist a fresh extraction's summaries to the cache, keyed
2578
- * against the same file list. Subsequent `lookup` calls with the
2579
- * same files return them. Without `attribution` the entry can only
2580
- * ever be reused whole.
2681
+ * Save a fresh extraction's summaries against this file list, so a later
2682
+ * `lookup` with the same files returns them. Without `attribution` the
2683
+ * entry can only be reused whole.
2581
2684
  */
2582
- write(input: CacheInput, summaries: BehavioralSummary[], attribution?: CacheAttribution<Meta>): Promise<void>;
2685
+ write(input: CacheInput, summaries: BehavioralSummary[], attribution?: CacheAttribution<Meta, UnitData>): Promise<void>;
2583
2686
  }
2584
2687
  interface CacheInput {
2585
2688
  /** Absolute paths of every file the run walks. */
2586
2689
  files: ReadonlyArray<string>;
2587
2690
  adapterPacksDigest: string;
2588
2691
  /**
2589
- * One file whose stamp guards the whole entry alongside the file
2590
- * list, such as a tsconfig or a project manifest. Optional because
2591
- * not every adapter has one.
2692
+ * A file whose change invalidates the whole entry, such as a tsconfig or
2693
+ * a project manifest. Not every adapter has one.
2592
2694
  */
2593
2695
  configPath?: string;
2594
2696
  }
2595
2697
  /**
2596
- * Construct a cache layer rooted at `cacheDir`. Pass `null` to
2597
- * opt out of caching entirely, the returned layer's `tryHit`
2598
- * always misses and `write` is a no-op. Useful for one-shot
2599
- * extracts where caching adds latency without payoff.
2698
+ * A cache layer rooted at `cacheDir`. With `null`, every lookup misses and
2699
+ * `write` does nothing, for a one-shot extract where the cache would only
2700
+ * add time.
2701
+ */
2702
+ declare function createCacheLayer<Meta = unknown, UnitData = unknown>(cacheDir: string | null): CacheLayer<Meta, UnitData>;
2703
+
2704
+ /**
2705
+ * Whether a write after `statAt` could leave this file's mtime and size
2706
+ * where the stat saw them, because its last change was that recent.
2600
2707
  */
2601
- declare function createCacheLayer<Meta = unknown>(cacheDir: string | null): CacheLayer<Meta>;
2708
+ declare function stampMayMissAWrite(stat: {
2709
+ mtimeMs: number;
2710
+ ctimeMs: number;
2711
+ }, statAt: number): boolean;
2602
2712
 
2603
2713
  /**
2604
2714
  * The extraction funnel, shared across languages.
@@ -2606,10 +2716,9 @@ declare function createCacheLayer<Meta = unknown>(cacheDir: string | null): Cach
2606
2716
  * "Why did this run produce nothing" is always "at which stage did the
2607
2717
  * count reach zero", so the report is a funnel: files in the tsconfig,
2608
2718
  * files the import gates selected, units discovered, summaries built.
2609
- * Each row is recorded by the stage that owns it, because the
2610
- * alternative is the CLI re-deriving the pre-filter's decisions from a
2611
- * second copy of its logic, and a second copy drifting from the first
2612
- * is what made an entire pack family extract nothing in silence.
2719
+ * Each stage records its own row, so the CLI never works the counts out
2720
+ * from a second copy of the pre-filter's logic that could drift from the
2721
+ * first.
2613
2722
  *
2614
2723
  * TypeScript, Python and Ruby all build one of these, though only
2615
2724
  * TypeScript gates files by import specifier today. A pack with no gate
@@ -2620,9 +2729,9 @@ declare function createCacheLayer<Meta = unknown>(cacheDir: string | null): Cach
2620
2729
  interface PackFunnel {
2621
2730
  pack: string;
2622
2731
  /**
2623
- * What the pack calls this build of itself, or null when it declares
2624
- * nothing. The cache keys on it, so a pack that never changes its
2625
- * stamp can serve a later run with an earlier build's results.
2732
+ * The version stamp the pack declares, or null when it declares none.
2733
+ * The cache keys on it, so a pack that never changes its stamp can
2734
+ * serve a later run with an earlier build's results.
2626
2735
  */
2627
2736
  version: string | null;
2628
2737
  /**
@@ -2644,11 +2753,10 @@ interface PackFunnel {
2644
2753
  */
2645
2754
  gates: string[];
2646
2755
  /**
2647
- * Gate specifiers that do not resolve from the project. Non-empty
2648
- * here usually means the target project's dependencies are not
2649
- * installed, which stops symbol-resolution packs while leaving
2650
- * textual-gate packs working, so the run half-succeeds in a way
2651
- * nothing else surfaces.
2756
+ * Gate specifiers that do not resolve from the project. A non-empty
2757
+ * list usually means the target project's dependencies are not
2758
+ * installed. Packs that resolve symbols then stop working while packs
2759
+ * with a textual gate still run, and nothing else in the report shows it.
2652
2760
  */
2653
2761
  unresolvedGates: string[];
2654
2762
  /** Files the pre-filter selected for this pack. */
@@ -2707,9 +2815,9 @@ interface PackFunnel {
2707
2815
  /**
2708
2816
  * What the pack wrote as data rather than as code, or null for a pack
2709
2817
  * written as a hand-rolled walk, or for a language whose packs have
2710
- * no declared-pattern system at all. This is the one thing in the
2711
- * funnel that no run produces: it is the pack's own shape, and it is
2712
- * here so the migration onto the declared surface can be measured.
2818
+ * no declared-pattern system at all. Unlike the counts, this comes from
2819
+ * the pack's definition and not from the run. It is reported so the
2820
+ * move of packs onto declared patterns can be measured.
2713
2821
  */
2714
2822
  declarations: PackDeclarations | null;
2715
2823
  }
@@ -2724,11 +2832,10 @@ interface ExtractionReport {
2724
2832
  * Files whose exports the checker could not follow, so the run read
2725
2833
  * them as exporting nothing.
2726
2834
  *
2727
- * Without this the artifact cannot tell the two apart: a module whose
2728
- * barrel chain outran the call stack and a module that really does
2729
- * export nothing both produce no summaries and exit 0.
2730
- * Anything reachable only through these files is missing, and every
2731
- * count below is a floor while this is non-empty.
2835
+ * Without this list, a module whose barrel chain overflowed the call
2836
+ * stack looks the same as a module that exports nothing: both produce
2837
+ * no summaries and exit 0. Anything reachable only through these files
2838
+ * is missing, and every count below is a floor while this is non-empty.
2732
2839
  */
2733
2840
  filesWithUnreadableExports: string[];
2734
2841
  /**
@@ -2737,10 +2844,9 @@ interface ExtractionReport {
2737
2844
  */
2738
2845
  emptyStage: EmptyStage | null;
2739
2846
  /**
2740
- * Reassigned names the run stated nothing for, because control flow
2741
- * decides which write a reader sees. Each is a value resolution the
2742
- * facts decline; the count across a large corpus says whether scoped
2743
- * reaching definitions is worth writing.
2847
+ * Reassigned names the run stated no value for, because control flow
2848
+ * decides which write a reader sees. Across a large corpus, the count
2849
+ * shows how much a scoped reaching-definitions analysis would recover.
2744
2850
  */
2745
2851
  reassignedNamesUnstated: number;
2746
2852
  }
@@ -2754,7 +2860,7 @@ type EmptyStage = "tsconfig" | "gateResolution" | "candidateFiles" | "discovery"
2754
2860
  * that looked and found nothing.
2755
2861
  */
2756
2862
  interface PackFailure {
2757
- /** The hook that threw, called what a pack author would call it. */
2863
+ /** The hook that threw, by the name a pack author uses for it. */
2758
2864
  hook: string;
2759
2865
  /** The file the pack was reading. */
2760
2866
  file: string;
@@ -2774,9 +2880,9 @@ interface PackTally {
2774
2880
  }
2775
2881
  declare const emptyTally: () => PackTally;
2776
2882
  /**
2777
- * Record that a pack's hook threw, and phrase it in one sentence a caller
2778
- * can print. Both callers want the same wording, and a failure that only
2779
- * reached stderr left the counts looking like an empty pack.
2883
+ * Record that a pack's hook threw, and return one line a caller can print.
2884
+ * The failure also goes on the tally, so the pack's counts do not look
2885
+ * like those of a pack that found nothing.
2780
2886
  */
2781
2887
  declare function recordPackFailure(tally: PackTally | undefined, failure: {
2782
2888
  pack: string;
@@ -2796,8 +2902,8 @@ declare function tallyUnit(tallies: Map<string, PackTally>, recognition: string
2796
2902
  * it, because the summaries a pack is responsible for are not all built
2797
2903
  * where the pack is in scope: wrapper expansion and sub-unit synthesis
2798
2904
  * both add summaries after discovery has moved on. Every summary
2799
- * records what recognised it, so grouping on that gets each one back to
2800
- * the pack that owns it however late it arrived.
2905
+ * records the pack that recognised it, so grouping on that credits each
2906
+ * summary to its pack however late it was built.
2801
2907
  */
2802
2908
  interface SummaryCounts {
2803
2909
  bound: number;
@@ -2822,6 +2928,25 @@ declare function buildUngatedExtractionReport(args: {
2822
2928
  summaries: ReadonlyArray<BehavioralSummary>;
2823
2929
  }): ExtractionReport;
2824
2930
 
2931
+ /**
2932
+ * Parses a process keeps between runs of one project, by file.
2933
+ *
2934
+ * The Python and Ruby adapters parse every file on every run, because
2935
+ * facts are emitted over the whole project. A process that reads the same
2936
+ * project after every edit hands a run one of these, and a file whose text
2937
+ * is the same as last time gets last time's tree back. The trees are
2938
+ * WASM memory the garbage collector cannot see, so a tree that is replaced
2939
+ * or whose file went away is handed to `release`.
2940
+ */
2941
+ declare class KeptParses<T> {
2942
+ private readonly release;
2943
+ private readonly byFile;
2944
+ constructor(release?: (value: T) => void);
2945
+ parse(file: string, source: string, make: (source: string) => Promise<T>): Promise<T>;
2946
+ /** Lets go of every file not in this run. */
2947
+ keepOnly(files: Iterable<string>): void;
2948
+ }
2949
+
2825
2950
  /**
2826
2951
  * Checks that tell you a pack is probably not working.
2827
2952
  *
@@ -2834,8 +2959,8 @@ declare function buildUngatedExtractionReport(args: {
2834
2959
  * signal. What makes zero a signal is the count before it. A pack whose
2835
2960
  * import gate picked forty files and whose discovery then found no unit
2836
2961
  * in any of them said "look here" and failed to look. Every later stage
2837
- * works the same way, which is why these checks are one comparison run
2838
- * over a list of pairs rather than one check written per stage.
2962
+ * works the same way, so the check is one comparison run over a list of
2963
+ * stage pairs instead of one check written per stage.
2839
2964
  */
2840
2965
 
2841
2966
  /** One thing that looks wrong, reported the way the dogfood invariants are. */
@@ -2869,13 +2994,13 @@ interface HealthCheck {
2869
2994
  violations: HealthViolation[];
2870
2995
  }
2871
2996
  /**
2872
- * What one pack paid for what it matches.
2997
+ * How much of one pack is data and how much is code (#542).
2873
2998
  *
2874
- * Expressiveness is bought link by link, and #542 asks for the price to
2875
- * be printed. A pack with every link written as data runs on any
2876
- * adapter with the executor ops. A link written as a function runs only
2877
- * where its own language does, and one that reads the syntax tree is
2878
- * the floor. All three are allowed, and this says which is which.
2999
+ * A pack with every link written as data runs on any adapter with the
3000
+ * executor ops. A link written as a function runs only where its own
3001
+ * language does, and one that reads the syntax tree runs only on the
3002
+ * adapter whose tree it reads. All three are allowed, and this counts
3003
+ * each kind.
2879
3004
  */
2880
3005
  interface PackGradient {
2881
3006
  pack: string;
@@ -2906,16 +3031,13 @@ declare function evaluatePackHealth(report: ExtractionReport): HealthCheck[];
2906
3031
  declare function formatPackHealth(checks: ReadonlyArray<HealthCheck>, audiences: ReadonlyArray<HealthCheck["audience"]>): string;
2907
3032
 
2908
3033
  /**
2909
- * Lightweight phase-timing instrumentation.
2910
- *
2911
- * Measures wall time spent in named phases of an adapter run so the
2912
- * CLI can surface "extract took 17s, of which 11s was in the
2913
- * reachable closure" without engaging a profiler. Cheap enough to
2914
- * always be on; a phase that isn't entered contributes zero.
3034
+ * Wall time per named phase of an adapter run, so the CLI can print
3035
+ * "extract took 17s, of which 11s was in the reachable closure" without
3036
+ * a profiler. It is cheap enough to leave on, and a phase that never
3037
+ * runs adds nothing.
2915
3038
  *
2916
- * Not OpenTelemetry: this is single-process, stdout-bound, and should
2917
- * not ship transitively to consumers. If suss ever becomes a
2918
- * long-running daemon (LSP / file-watcher), revisit then.
3039
+ * Everything stays in one process and goes to stdout, so there is no
3040
+ * OpenTelemetry dependency for consumers to inherit.
2919
3041
  */
2920
3042
  interface TimingReport {
2921
3043
  totalMs: number;
@@ -2928,48 +3050,34 @@ interface TimingReport {
2928
3050
  interface Timer {
2929
3051
  /** Run `fn`, accumulate wall time under `label`, return its result. */
2930
3052
  time<T>(label: string, fn: () => T): T;
2931
- /** Async variant: same accumulation rule. */
3053
+ /** The same as `time`, for an async `fn`. */
2932
3054
  timeAsync<T>(label: string, fn: () => Promise<T>): Promise<T>;
2933
3055
  /** Snapshot of all accumulated phases, ordered by total time descending. */
2934
3056
  report(): TimingReport;
2935
3057
  }
2936
- /**
2937
- * Build a fresh timer. Each adapter run gets its own, keeps
2938
- * concurrent extracts independent (irrelevant today, will matter if
2939
- * we add a server mode).
2940
- */
3058
+ /** Each adapter run gets its own timer, so two runs at once never mix their phases. */
2941
3059
  declare function createTimer(): Timer;
2942
- /** Null implementation for callers that opt out of timing entirely. */
3060
+ /** A timer that runs `fn` and records nothing, for callers that do not want timing. */
2943
3061
  declare function noopTimer(): Timer;
2944
3062
 
2945
3063
  /**
2946
- * The vocabulary a language adapter implements so that any declared
2947
- * pack runs on it.
3064
+ * The vocabulary a language adapter implements so that any declared pack
3065
+ * runs on it. A pack is data, so the adapter has to turn "did this module
3066
+ * declare the method" into a yes or no about a particular program, and
3067
+ * these interfaces are everything a pack can ask of it.
2948
3068
  *
2949
- * A pack is data, so something has to turn "did this module declare the
2950
- * method" into a yes or no about a particular program. That is the
2951
- * adapter's job, and these interfaces are the whole of what a pack can
2952
- * ask for. An adapter implements them once and every pack runs there,
2953
- * which is what lets one pack drive several languages.
2954
- *
2955
- * They live here rather than beside the builders that produce them,
2956
- * because an adapter needs this and none of the rest: the TypeScript
2957
- * adapter imports these types and no runtime code from
2958
- * `@suss/recognize`. Python and Ruby will want the same.
3069
+ * They are in this package rather than beside the builders in
3070
+ * `@suss/recognize`, so an adapter imports the types without that
3071
+ * package's runtime code. Only the TypeScript adapter implements them.
2959
3072
  *
2960
3073
  * Nothing here mentions a syntax tree. A pack that needs the tree goes
2961
3074
  * through `@suss/recognize/ast`, a separate import so that reaching for
2962
3075
  * it shows up in a diff and in the pack health report.
2963
3076
  */
2964
3077
  /**
2965
- * How a pack pins down the receiver a call is on.
2966
- *
2967
- * The migration plan for #542 lists four more: `factoryMade` (`app =
2968
- * express()`), `imported`, `anchored` (a chain read from its anchor
2969
- * call), `inherits` (a Ruby or Python receiver matched by ancestry) and
2970
- * `global` (`process.env`, bare `fetch`). Each arrives as a member here
2971
- * with an entry in every adapter's dispatch table. The README beside
2972
- * this file says which starts are not receiver-shaped at all.
3078
+ * How a pack pins down the receiver a call is on. The recognize package's
3079
+ * DESIGN.md lists the origins not built yet (#542) and the places a match
3080
+ * can start that are not receivers.
2973
3081
  */
2974
3082
  type ReceiverOrigin = DeclaredBy | ConstructedFrom;
2975
3083
  /**
@@ -3003,29 +3111,41 @@ interface ConstructedFrom {
3003
3111
  readonly named?: readonly string[];
3004
3112
  }
3005
3113
 
3114
+ /**
3115
+ * What a reader returns for a name nothing in the source settles.
3116
+ * `"nothing"` returns null, and `"reference"` returns the name of the
3117
+ * value to look up instead.
3118
+ */
3006
3119
  type UnsettledName = "nothing" | "reference";
3007
3120
  /**
3008
3121
  * One value a call states, as the questions a pack can ask about it.
3009
3122
  *
3010
- * `CallOps` reaches a call beside the one in hand, and this reaches the
3011
- * values that are not calls. A library that takes one request object
3012
- * puts everything the call is doing inside it, sometimes as a list and
3013
- * sometimes as a string in a little language of the library's own, and
3014
- * a pack that reads those is handed this rather than the adapter's own
3015
- * node. So the rule it writes runs wherever the ops do.
3123
+ * `CallOps` reaches the calls around the one in hand, and this reaches the
3124
+ * values that are not calls. A library that takes one request object puts
3125
+ * everything the call does inside it, as a list or as a string in the
3126
+ * library's own query language. A pack that reads those gets this instead
3127
+ * of the adapter's node, so its rule runs on any adapter with the ops.
3016
3128
  */
3017
3129
  interface ValueOps {
3018
3130
  /** The text of the string the source wrote, or null for anything else. */
3019
3131
  text(): string | null;
3020
3132
  /**
3021
- * What this value is called, when the source names it rather than
3022
- * writing it out. A queue URL is nearly always
3133
+ * The name of this value, when the source refers to it by name instead
3134
+ * of writing it out. A queue URL is nearly always
3023
3135
  * `process.env.ORDERS_QUEUE_URL`, so the value only exists at deploy
3024
- * time and the env var's name is what both sides of the boundary
3025
- * agree on. `"reference"` gives that name back, `"nothing"` gives
3026
- * null for anything the source does not settle.
3136
+ * time and both sides of the boundary use the env var's name.
3137
+ * `"reference"` returns that name, and `"nothing"` returns null for
3138
+ * anything the source does not settle.
3027
3139
  */
3028
3140
  name(unsettled: UnsettledName): string | null;
3141
+ /**
3142
+ * Every string this value can be, when the source limits it to a
3143
+ * few. `` `record.${op}` `` with `op` typed `"a" | "b"` is
3144
+ * `record.a` and `record.b`. Null when part of it could be anything,
3145
+ * or when there are more than `cap`. An adapter that has not
3146
+ * implemented this leaves it out, and a reader has only `name`.
3147
+ */
3148
+ names?(cap: number): readonly string[] | null;
3029
3149
  /**
3030
3150
  * The yes or no the source wrote here, or null for anything else. A
3031
3151
  * library that asks which fields a call wants states them as a map of
@@ -3061,16 +3181,17 @@ interface ValueOps {
3061
3181
  * a call is null rather than being dropped, so the two lists stay in
3062
3182
  * step.
3063
3183
  *
3064
- * What a hole comes to is the pack's to say. This says what was
3065
- * there, so a query that hands over a table object can still be read.
3184
+ * What a hole means is up to the pack. This only reports what was
3185
+ * written there, so a query that interpolates a table object can still
3186
+ * be read.
3066
3187
  */
3067
3188
  holes(): readonly (CallOps | null)[];
3068
3189
  /**
3069
3190
  * The same holes as values, in the same order, for a reader that
3070
3191
  * wants what the source settled a hole to rather than what call was
3071
- * written there. A table kept in a module constant is the case: the
3072
- * hole is a name, not a call, so `holes` gives null for it and this
3073
- * gives the constant.
3192
+ * written there. A table name kept in a module constant needs this:
3193
+ * the hole is a name, so `holes` gives null for it and this gives the
3194
+ * constant.
3074
3195
  *
3075
3196
  * An adapter that has not implemented this leaves it out, and a
3076
3197
  * reader then has only the calls.
@@ -3097,7 +3218,7 @@ interface ValueEntry {
3097
3218
  *
3098
3219
  * Two members give back another `CallOps`, which is how these questions
3099
3220
  * reach a call next to this one. A chain of calls and a command object
3100
- * are both these same questions asked one step along, so neither needed
3221
+ * are both these same questions asked one step along, so neither needs
3101
3222
  * a question of its own.
3102
3223
  */
3103
3224
  interface CallOps {
@@ -3106,11 +3227,9 @@ interface CallOps {
3106
3227
  /** Whether the receiver came from where the origin says. */
3107
3228
  receiverIsFrom(origin: ReceiverOrigin): boolean;
3108
3229
  /**
3109
- * Whether the call itself came from where the origin says, which is
3110
- * the same question asked of the callee rather than of what it was
3111
- * called on. `new GetCommand(...)` is the case: a pack that steps to
3112
- * an argument asks this of it before reading anything, so it never
3113
- * reads an argument that was never the one.
3230
+ * Whether the callee itself came from where the origin says. A pack
3231
+ * that steps to an argument such as `new GetCommand(...)` asks this
3232
+ * first, so it never reads a command from some other library.
3114
3233
  */
3115
3234
  isFrom(origin: ReceiverOrigin): boolean;
3116
3235
  /** How many arguments the call passes. */
@@ -3132,6 +3251,23 @@ interface CallOps {
3132
3251
  * class, the same as any other call.
3133
3252
  */
3134
3253
  argument(index: number): CallOps | null;
3254
+ /**
3255
+ * Said by a chain that steps to the arguments of every call it sees,
3256
+ * before it reads the first one: at every call the walk visits, it
3257
+ * will read the arguments from `first` through `last`, or to the end
3258
+ * when `last` is left out. An adapter that asks a separate question
3259
+ * for each read can ask about all of them at once. Leaving it out
3260
+ * changes how fast a run is and nothing else.
3261
+ */
3262
+ readingArguments?(first: number, last?: number): void;
3263
+ /**
3264
+ * Said by a chain before it asks what made a receiver: at every call
3265
+ * the walk visits whose method is one of `methods`, spelled as the
3266
+ * source spells it, it will ask about the receiver. An adapter can
3267
+ * then ask about all of them at once, and leaving it out changes only
3268
+ * how fast a run is.
3269
+ */
3270
+ readingReceivers?(methods: ReadonlySet<string>): void;
3135
3271
  /**
3136
3272
  * The call the callee itself was written as, or null when nothing
3137
3273
  * wrote it as one. A class a factory made is the case: `new
@@ -3154,7 +3290,7 @@ interface CallOps {
3154
3290
  */
3155
3291
  parameterReadsAt?(index: number): readonly string[] | null;
3156
3292
  /**
3157
- * What a named property of the object an argument states says. A
3293
+ * One property of the object literal passed at this position. A
3158
3294
  * property bag is not a call, so nothing else here reaches into one.
3159
3295
  */
3160
3296
  propertyAt(index: number, property: string, unsettled: UnsettledName): string | null;
@@ -3167,13 +3303,42 @@ interface CallOps {
3167
3303
  valueAt(index: number): ValueOps | null;
3168
3304
  /**
3169
3305
  * The one call behind the receiver the origin accepts, however many
3170
- * name, construction, or query hops separate them. Mongoose's
3171
- * `model(...)` behind a document is the picture. Null when nothing
3306
+ * name, construction, or query hops separate them, such as Mongoose's
3307
+ * `model(...)` behind a document. Null when nothing
3172
3308
  * behind the receiver matches, when two distinct calls do (picking
3173
3309
  * one would depend on the order facts arrived), and for an origin
3174
3310
  * kind with no construction to hand back.
3175
3311
  */
3176
3312
  anchorCall?(origin: ReceiverOrigin): CallOps | null;
3313
+ /**
3314
+ * The class the argument in this position refers to, or null when it
3315
+ * refers to none the run declares. An ORM that takes an entity class,
3316
+ * `InjectRepository(Order)`, keeps the table on the class itself.
3317
+ */
3318
+ classAt?(index: number): ClassOps | null;
3319
+ /**
3320
+ * Said by a recognizer about an effect it built from this call: the
3321
+ * value the call states for each column the effect writes or picks
3322
+ * rows by. The adapter asks where each value came from and records it
3323
+ * on the transition. Leaving it out changes only that record.
3324
+ */
3325
+ statesSlots?(effect: Effect, slots: readonly StatedSlot[]): void;
3326
+ }
3327
+ /** A column an effect writes or picks rows by, and the value stated for it. */
3328
+ interface StatedSlot {
3329
+ readonly slot: "field" | "selector";
3330
+ readonly name: string;
3331
+ readonly value: ValueOps;
3332
+ }
3333
+ /** One class a call refers to, as the questions a pack can ask about it. */
3334
+ interface ClassOps {
3335
+ /** The name the class is declared under, or null when it has none. */
3336
+ name(): string | null;
3337
+ /**
3338
+ * The one decorator call on the class that the origin accepts, such
3339
+ * as `@Entity("orders")`. Null when none does, or when two do.
3340
+ */
3341
+ decorator(origin: ReceiverOrigin): CallOps | null;
3177
3342
  }
3178
3343
  /**
3179
3344
  * The property an adapter puts its ops on, in the context it hands a
@@ -3194,26 +3359,27 @@ interface AstCapableOps extends CallOps {
3194
3359
  }
3195
3360
 
3196
3361
  /**
3197
- * The assembly engine: a `RawCodeStructure` from a language adapter goes in,
3198
- * a `BehavioralSummary` comes out. The package README explains where that step
3199
- * fits in the pipeline.
3362
+ * The assembly engine. A language adapter hands over a `RawCodeStructure`,
3363
+ * and `assembleSummary` turns it into a `BehavioralSummary`. The package
3364
+ * README explains where that step fits in the pipeline.
3200
3365
  *
3201
- * Two things surprise people reading this file. First, `RawCodeStructure` and
3202
- * the raw types around it are the contract every adapter and pack implements,
3203
- * so a field added here is a change to that contract. Second, this is the only
3204
- * module allowed to turn a `Reading` into a claim on a summary; adapters hand
3205
- * readings over uncollapsed and the rule for collapsing them lives here.
3366
+ * `RawCodeStructure` and the raw types around it are the contract every
3367
+ * adapter and pack implements, so adding a field here changes that
3368
+ * contract. This is also the only module that turns a `Reading` into a
3369
+ * claim on a summary. Adapters pass readings over uncollapsed, and the rule
3370
+ * for collapsing them is below.
3206
3371
  */
3207
3372
 
3208
3373
  interface RawParameter {
3209
3374
  name: string;
3210
3375
  position: number;
3211
3376
  /**
3212
- * What the parameter is for, in the library's own vocabulary. When the
3213
- * adapter could not tell, this is null rather than a guess, and the reason
3214
- * goes in `readings`.
3377
+ * What the parameter is for, in the library's own vocabulary. Null when
3378
+ * the adapter could not tell, with the reason in `readings`.
3215
3379
  */
3216
3380
  role: string | null;
3381
+ /** Which field of the role's value the parameter is, when the library hands over one field. */
3382
+ field?: string;
3217
3383
  typeText: string | null;
3218
3384
  }
3219
3385
  interface RawCondition {
@@ -3223,7 +3389,7 @@ interface RawCondition {
3223
3389
  source: ConditionSource;
3224
3390
  }
3225
3391
  interface RawTerminal {
3226
- kind: "response" | "throw" | "return" | "render" | "delegate" | "emit" | "void";
3392
+ kind: "response" | "throw" | "return" | "render" | "delegate" | "emit" | "void" | "exit";
3227
3393
  statusCode: {
3228
3394
  type: "literal";
3229
3395
  value: number;
@@ -3302,7 +3468,7 @@ type RawEffect = {
3302
3468
  callee: string;
3303
3469
  args: EffectArg[];
3304
3470
  async: boolean;
3305
- /** Empty means the call always fires, not that nobody looked. */
3471
+ /** An empty list means the call is not under any guard, so it always fires. */
3306
3472
  preconditions?: RawCondition[];
3307
3473
  } | {
3308
3474
  type: "emission";
@@ -3320,6 +3486,8 @@ interface RawBranch {
3320
3486
  * `RawEffect` conversion that `effects` goes through.
3321
3487
  */
3322
3488
  extraEffects?: Effect[];
3489
+ /** Where the values in the slots of `extraEffects` came from. */
3490
+ provenance?: RawProvenance[];
3323
3491
  location: {
3324
3492
  start: number;
3325
3493
  end: number;
@@ -3340,6 +3508,17 @@ interface RawBranch {
3340
3508
  */
3341
3509
  bodyShapeReading?: DefaultedReading<TypeShape>;
3342
3510
  }
3511
+ /**
3512
+ * Where the value in one slot came from, keyed by the effect object an
3513
+ * adapter put in `extraEffects`. Assembly folds repeated effects, so it
3514
+ * works out each effect's position only once the list is final.
3515
+ */
3516
+ interface RawProvenance {
3517
+ effect: Effect;
3518
+ slot: ProvenanceSlot["slot"];
3519
+ name: string;
3520
+ from: ValueRef[];
3521
+ }
3343
3522
  interface RawDependencyCall {
3344
3523
  name: string;
3345
3524
  assignedTo: string | null;
@@ -3420,14 +3599,14 @@ interface RawCodeStructure {
3420
3599
  bodyContent?: BodyContent;
3421
3600
  dependencyCalls: RawDependencyCall[];
3422
3601
  /**
3423
- * Reads the adapter saw in the body that never flow into a condition
3424
- * or an output value, a render tree's `props.title` say. Merged into
3425
- * `inputReads` beside the derived ones.
3602
+ * Reads the adapter saw in the body that never flow into a condition or
3603
+ * an output value, such as a render tree's `props.title`. They are merged
3604
+ * into `inputReads` with the ones derived from conditions and outputs.
3426
3605
  */
3427
3606
  extraInputReads?: InputRead[];
3428
3607
  declaredContract: RawDeclaredContract | null;
3429
- /** The property a consumer goes through to get at the body, `data` for
3430
- * axios say, so the checker can unwrap it without knowing each pack. */
3608
+ /** The property a consumer reads the body through, such as `data` for
3609
+ * axios, so the checker can unwrap it without a rule per pack. */
3431
3610
  bodyAccessors?: string[];
3432
3611
  /** The same, for the status: `status` for fetch and for axios. */
3433
3612
  statusAccessors?: string[];
@@ -3451,6 +3630,8 @@ interface RawCodeStructure {
3451
3630
  wrappers?: WrapperReference[];
3452
3631
  /** Where a REST pack's handlers read each part of the request, from the pack. */
3453
3632
  requestSpelling?: RequestSpellingMetadata;
3633
+ /** What a test pack found about a `test` unit: whether it is skipped, and what it replaces with a mock. */
3634
+ test?: TestMetadata;
3454
3635
  /** The extractor cannot derive this. An adapter that has the SDL and knows
3455
3636
  * which field the resolver serves fills it in. */
3456
3637
  graphqlDeclaredContract?: GraphqlDeclaredContract;
@@ -3508,4 +3689,4 @@ declare function rawConditionToPredicate(c: RawCondition): Predicate;
3508
3689
  declare function effectToIR(effect: RawEffect): Effect;
3509
3690
  declare function paramToInput(param: RawParameter): Input;
3510
3691
 
3511
- export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, type ExitKind, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SourceRange, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type Timer, type TimingReport, type TransparentWrapper, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkableNode, type WrapperMethodRegistration, type WrapperOptionRegistration, type WrapperRegistration, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, commonDirectoryOf, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, makeTransitionId, mapReading, moduleInitStructure, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, sharedGatingConditions, stampModuleImports, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };
3692
+ export { type AccessRecognizer, type AdapterCodeStamp, type AdapterStamp, type ArgumentParser, type AstCapableOps, type BindingExtraction, type BodyContent, type CacheAttribution, type CacheDiagnostic, type CacheInput, type CacheLayer, type CacheLookup, type CallOps, type CaseGroup, type ChannelSource, type ChosenReading, type ClassOps, type ConditionHandle, type ConditionInfo, type ConditionSource, type ConstructedFrom, type ContractPattern, type DeclaredBinding, type DeclaredBy, type DeclaredMatch, type DeclaredModule, type DefaultedReading, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type EmptyStage, EnclosingNodes, type ExitKind, type ExtractionConfig, type ExtractionReport, type ExtractorOptions, type FailureDelivery, type GlobalPrefixCall, type HealthCheck, type HealthViolation, type HelperDeclarations, type HelperSearch, type HelperSink, type HelperValue, IdMap, IdSet, type Identified, type InputMappingPattern, type InputRead, type InvocationRecognizer, KeptParses, type LanguageAdapter, type LoweredStatementParts, MAX_ENTRIES, MAX_PATHS, MODULE_SURFACE_RECOGNITION, type ModuleInitOptions, type NodeVisitor, type OpsCarrier, type PackDeclarations, type PackFailure, type PackFunnel, type PackGradient, type PackTally, type ParameterSupplier, type ParsedNode, type PartialPlan, PathBudgetExceeded, type PatternPack, type ProjectHelper, type ProjectHelpers, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawProvenance, type RawTerminal, type Reading, type ReceiverOrigin, type RegistrationHelper, type ResponsePropertyMapping, type ResponsePropertyMeaning, type RootRecord, SKIP_CHILDREN, type SettledModule, type SourceRange, type SourceSpelling, type StatedSlot, type StatementBlock, type StructuredPathConditionsInput, type StructuredPathConditionsResult, type StructuredStatement, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type TestCaseMatch, type Timer, type TimingReport, type TransparentWrapper, type UnitRecord, UnmodeledFlow, type UnsettledName, type ValueEntry, type ValueOps, type WalkEnd, type WalkableNode, type WrapperMethodRegistration, type WrapperOptionRegistration, type WrapperRegistration, absentReading, ambiguousReading, andThenReading, assembleSummary, assessConfidence, buildUngatedExtractionReport, commonDirectoryOf, composeWrappers, computeContentHash, computeDistHashFrom, configuredCallOption, createAdapterStamp, createCacheLayer, createPackTallies, createTimer, detectGaps, effectToIR, emptyTally, enumerateOrDegrade, enumerateStructuredPaths, evaluatePackHealth, extractionConfigStamp, firstWrittenReading, formatPackHealth, guardsHoldOn, httpRouteDiscovery, isListedTestFile, makeTransitionId, mapReading, markReturnsAsExitCode, matchesTestFileName, moduleInitStructure, moduleOfFile, noopTimer, packGradients, paramToInput, projectFileStamp, rawConditionToPredicate, recordPackFailure, registrationHelperDiscovery, routeHelperIndex, runDigest, runsBefore, scopeOption, settleModules, sharedGatingConditions, sourceRefsOf, stampMayMissAWrite, stampModuleImports, stampModules, storageSystemOption, summaryCountsByPack, tallyUnit, terminalToOutput, unreadableReading, unwrapJsonStringify, valueToReadFurtherFrom, walkDescendants, wrapperDiscovery, writtenReading };