@suss/extractor 0.0.1

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.
@@ -0,0 +1,1269 @@
1
+ import { Effect, Predicate, TypeShape, RenderNode, CodeUnitKind, BoundaryBinding, BehavioralSummary, ConfidenceInfo, Transition, Gap, Input, Output } from '@suss/behavioral-ir';
2
+
3
+ type DiscoveryMatch = {
4
+ type: "namedExport";
5
+ names: string[];
6
+ } | {
7
+ type: "registrationCall";
8
+ importModule: string;
9
+ importName: string;
10
+ registrationChain: string[];
11
+ } | {
12
+ type: "decorator";
13
+ decoratorModule: string;
14
+ decoratorName: string;
15
+ } | {
16
+ type: "fileConvention";
17
+ filePattern: string;
18
+ exportNames: string[];
19
+ } | {
20
+ type: "clientCall";
21
+ /** Module the client is imported from, or "global" for built-ins like fetch */
22
+ importModule: string;
23
+ /** Named export or identifier — e.g. "initClient", "fetch" */
24
+ importName: string;
25
+ /** If set, only match calls to these methods on the client (e.g. ["getUser"]).
26
+ * Unset means any method call (or bare call for globals). */
27
+ methodFilter?: string[];
28
+ /**
29
+ * Method names on the import that produce a client-equivalent instance,
30
+ * so variables initialized from those calls also act as discovery
31
+ * subjects. axios uses `axios.create({...})` to build a baseURL-bound
32
+ * instance; declaring `factoryMethods: ["create"]` lets the adapter
33
+ * treat `api.get(...)` (where `api = axios.create(...)`) the same as
34
+ * `axios.get(...)`.
35
+ */
36
+ factoryMethods?: string[];
37
+ } | {
38
+ /**
39
+ * A constructor or factory call that takes a configuration object
40
+ * containing a resolver map — the idiomatic GraphQL code-first
41
+ * shape. The map is two levels deep: outer keys are GraphQL type
42
+ * names (`Query`, `Mutation`, `Subscription`, or object-type
43
+ * names like `User`); inner keys are field names whose values
44
+ * are resolver functions.
45
+ *
46
+ * Example (Apollo Server v4):
47
+ * ```ts
48
+ * new ApolloServer({
49
+ * typeDefs,
50
+ * resolvers: {
51
+ * Query: { users: async () => {...} },
52
+ * Mutation: { createUser: async (_, {input}) => {...} },
53
+ * User: { fullName: (parent) => `${parent.first} ${parent.last}` },
54
+ * },
55
+ * });
56
+ * ```
57
+ *
58
+ * Each inner function becomes one discovered unit whose binding
59
+ * semantics is `graphql-resolver(typeName, fieldName)`. Matches
60
+ * both `new Ctor(cfg)` and `ctor(cfg)` — Apollo's standalone
61
+ * server uses `new`, yoga uses a bare call.
62
+ */
63
+ type: "resolverMap";
64
+ importModule: string;
65
+ importName: string;
66
+ /**
67
+ * Name of the property on the config object that holds the
68
+ * resolver map. Defaults to `"resolvers"` when unset, matching
69
+ * Apollo / yoga / graphql-tools convention.
70
+ */
71
+ mapProperty?: string;
72
+ /**
73
+ * GraphQL type names whose fields we DON'T treat as resolvers —
74
+ * opt-out for meta-types like `Subscription` that we may want
75
+ * to handle differently later. Unset means discover every type.
76
+ */
77
+ excludeTypes?: string[];
78
+ } | {
79
+ /**
80
+ * Consumer-side GraphQL hook call — the canonical Apollo Client
81
+ * and urql shape. Each call to one of the listed hooks becomes
82
+ * a `client`-kind code unit whose binding semantics is
83
+ * `graphql-operation(operationType, operationName?)`.
84
+ *
85
+ * The document argument resolves through several shapes: an inline
86
+ * `gql`-tagged template, a const binding (same module or imported
87
+ * from another module), a `.graphql` / `.gql` file import, and a
88
+ * generated `TypedDocumentNode` object literal from graphql-codegen
89
+ * client-preset. When the document body isn't statically readable,
90
+ * the operation header falls back to the `TypedDocumentNode` type
91
+ * arguments; a still-unresolvable document surfaces on the summary
92
+ * as `metadata.graphql.unresolvedDocument` rather than dropping the
93
+ * boundary.
94
+ *
95
+ * Example:
96
+ * ```ts
97
+ * import { gql, useQuery } from "@apollo/client";
98
+ * const GET_USER = gql`query GetUser($id: ID!) { user(id: $id) { id } }`;
99
+ * function UserPage({ id }) {
100
+ * const { data } = useQuery(GET_USER, { variables: { id } });
101
+ * ...
102
+ * }
103
+ * ```
104
+ *
105
+ * The adapter records the operation name / type on the
106
+ * DiscoveredUnit's `operationInfo`; binding construction uses that
107
+ * to emit `graphql-operation(...)`. The per-hook `operationType`
108
+ * is authoritative when the document header can't be read (mirrors
109
+ * `graphqlImperativeCall.methods`).
110
+ */
111
+ type: "graphqlHookCall";
112
+ importModule: string;
113
+ /**
114
+ * Hooks to match on that import, each mapped to the operation
115
+ * type it performs (`useQuery` → query, `useMutation` → mutation,
116
+ * `useSubscription` → subscription). The mapping supplies the
117
+ * operation type when the document body isn't statically readable.
118
+ * Each hook is reported as `kind = "client"` by default; packs can
119
+ * override via the enclosing `DiscoveryPattern.kind`.
120
+ */
121
+ hooks: Array<{
122
+ hookName: string;
123
+ operationType: "query" | "mutation" | "subscription";
124
+ }>;
125
+ } | {
126
+ /**
127
+ * Imperative Apollo-Client-style call — `client.query({ query })`,
128
+ * `client.mutate({ mutation })`, `client.subscribe({ query })`.
129
+ * Distinct from hook calls because the document lives on a
130
+ * config-object property rather than the first positional arg.
131
+ * Discovery is gated on an import of the named constructor
132
+ * (typically `ApolloClient`) to reduce false positives — any
133
+ * method-named "query" on a random object could look like this
134
+ * shape.
135
+ *
136
+ * Each entry in `methods` specifies the method name that gets
137
+ * called on the client (`"query"` / `"mutate"` / `"subscribe"`)
138
+ * and the config-object property that holds the gql document
139
+ * (`"query"` / `"mutation"` / `"query"` respectively). The
140
+ * method name drives the operation type (query / mutation /
141
+ * subscription) when the gql document's header is anonymous;
142
+ * a named document's header wins otherwise.
143
+ */
144
+ type: "graphqlImperativeCall";
145
+ importModule: string;
146
+ importName: string;
147
+ methods: Array<{
148
+ methodName: string;
149
+ documentKey: string;
150
+ operationType: "query" | "mutation" | "subscription";
151
+ }>;
152
+ } | {
153
+ /**
154
+ * Treats a TypeScript package's public export surface as a
155
+ * boundary. The adapter reads `package.json` at
156
+ * `packageJsonPath`, resolves each reachable entry point
157
+ * (root `.` and any sub-path `exports`), follows barrel
158
+ * re-exports, and emits one discovered unit per exported
159
+ * function — provider side of an in-process `function-call`
160
+ * boundary.
161
+ *
162
+ * Produced bindings carry identity
163
+ * `{ transport: "in-process",
164
+ * semantics: { name: "function-call",
165
+ * package: <pkg.name>,
166
+ * exportPath: [...] },
167
+ * recognition: <pack.name> }`.
168
+ *
169
+ * Sub-path exports identify as e.g.
170
+ * `@suss/behavioral-ir/schemas::BehavioralSummarySchema` —
171
+ * `exportPath = ["schemas", "BehavioralSummarySchema"]`.
172
+ * Root exports omit the sub-path segment.
173
+ *
174
+ * v0 scope: resolves `types` / `default` / `import` conditions
175
+ * on `exports`, falls back to `types` + `main` + `module`
176
+ * when no `exports` field is set. Pattern exports (`./utils/*`)
177
+ * and `development`-conditional resolution are deferred.
178
+ */
179
+ type: "packageExports";
180
+ /** Absolute path to the package's `package.json`. */
181
+ packageJsonPath: string;
182
+ /**
183
+ * Restrict to these `exports` keys (without leading `./`).
184
+ * The root export is keyed `"."`. Unset means all resolvable
185
+ * sub-paths.
186
+ */
187
+ subPaths?: string[];
188
+ /**
189
+ * Export names to skip — typically `["default"]` when a pack
190
+ * wants to treat default exports separately or not at all.
191
+ */
192
+ excludeNames?: string[];
193
+ } | {
194
+ /**
195
+ * Class methods that carry a specific decorator, on classes that
196
+ * carry a specific class-level decorator. Used by NestJS-style
197
+ * frameworks where resolvers / handlers / controllers are
198
+ * declared by decorator, not by registering a function in an
199
+ * object literal.
200
+ *
201
+ * Discovery gates on an `importModule` of `classDecorator` and
202
+ * each `methodDecorators` entry — random user-defined decorators
203
+ * with the same names won't trip the matcher.
204
+ *
205
+ * For a NestJS GraphQL pack:
206
+ * `{ importModule: "@nestjs/graphql",
207
+ * classDecorator: "Resolver",
208
+ * methodDecorators: ["Query", "Mutation", "ResolveField",
209
+ * "Subscription"] }`
210
+ *
211
+ * The adapter populates `DiscoveredUnit.resolverInfo` so the
212
+ * produced binding carries `graphql-resolver(typeName, fieldName)`.
213
+ * `typeName` resolves from the class decorator's first argument
214
+ * (`@Resolver(() => User)` → `"User"`); when the class decorator
215
+ * has no argument it defaults to the operation kind (`"Query"`,
216
+ * `"Mutation"`, `"Subscription"`). `fieldName` reads the method
217
+ * decorator's `{ name }` option override when present, otherwise
218
+ * the method name.
219
+ */
220
+ type: "decoratedMethod";
221
+ /**
222
+ * The module a pack-recognised decorator must be imported from
223
+ * for discovery to fire. Codebases sometimes wrap framework
224
+ * decorators in their own re-exports (NestJS apps frequently
225
+ * declare `MetadataResolver` / `CoreResolver` factories that
226
+ * compose `@Resolver()` with extra metadata). When that happens,
227
+ * the import-module gate would miss them; pass an array of
228
+ * accepted modules and any one match suffices.
229
+ */
230
+ importModule: string | string[];
231
+ /**
232
+ * Class decorator names to recognise. The first entry that
233
+ * appears on a class wins for typeName extraction (the rest are
234
+ * fallbacks for codebases with multiple wrapper styles).
235
+ */
236
+ classDecorators: string[];
237
+ methodDecorators: string[];
238
+ } | {
239
+ /**
240
+ * NestJS-style REST controller discovery. Class decorated with
241
+ * `@Controller(pathPrefix?)`; methods decorated with
242
+ * `@Get(subpath?)` / `@Post` / `@Put` / `@Delete` / etc. The
243
+ * decorator NAME determines the HTTP method via
244
+ * `methodDecoratorRouteMap`; the route path is the class
245
+ * decorator's first arg + the method decorator's first arg
246
+ * (slash-joined, both optional).
247
+ *
248
+ * Same wrapper-decorator tolerance as `decoratedMethod`: the
249
+ * import-module gate fires on at least one method-route
250
+ * decorator from the framework module, but class decorators
251
+ * match by name only so project-internal wrappers
252
+ * (`@PublicController` / `@AuthedController` factories
253
+ * composing `@Controller()`) work without per-project pack
254
+ * config.
255
+ *
256
+ * The adapter populates `DiscoveredUnit.routeInfo` so the
257
+ * produced binding carries `rest(method, path)`.
258
+ */
259
+ type: "decoratedRoute";
260
+ importModule: string | string[];
261
+ classDecorators: string[];
262
+ /**
263
+ * Decorator name → HTTP method. NestJS uses one decorator per
264
+ * verb (`@Get` / `@Post` / `@Put` / `@Delete` / `@Patch` /
265
+ * `@Options` / `@Head` / `@All`); other frameworks may follow
266
+ * the same convention. The values become the `method` field on
267
+ * the produced REST binding; `"*"` is acceptable for catch-all
268
+ * decorators.
269
+ */
270
+ methodDecoratorRouteMap: Record<string, string>;
271
+ } | {
272
+ /**
273
+ * Loop expansion: a `for-of` loop over a literal array of
274
+ * route specs is treated as if each element were an inline
275
+ * registration. Used for patterns like:
276
+ *
277
+ * const routes = [
278
+ * { method: "get", path: "/users", handler: getUsers },
279
+ * ...
280
+ * ];
281
+ * for (const r of routes) app[r.method](r.path, r.handler);
282
+ *
283
+ * `elementShape` declares which keys on each element carry the
284
+ * method, path, and handler. The loop body is required to
285
+ * contain at least one call expression that references the
286
+ * loop variable (filters out unrelated loops); the call's
287
+ * shape itself is not checked beyond that.
288
+ *
289
+ * Iterables that resolve to an `ArrayLiteralExpression` (inline
290
+ * or single-hop `const`-bound) are expanded; cross-file or
291
+ * computed iterables are out of v0 scope.
292
+ *
293
+ * Pack-author docs: `docs/internal/proposals/dynamic-registration.md`.
294
+ */
295
+ type: "registrationLoop";
296
+ elementShape: {
297
+ methodKey: string;
298
+ pathKey: string;
299
+ handlerKey: string;
300
+ };
301
+ } | {
302
+ /**
303
+ * Helper-call expansion: a single function call at the user
304
+ * site is treated as if it were N inline registrations, with
305
+ * arguments substituted into per-registration templates. Used
306
+ * for patterns like `registerCrud(app, 'users', userHandlers)`
307
+ * that today's `registrationCall` discovery can't see.
308
+ *
309
+ * Each entry in `registrations` describes one virtual route
310
+ * the helper produces. `pathTemplate` and `handlerArg` use
311
+ * `{N}` placeholders that resolve to the call's positional
312
+ * arguments. `{N}` substitutes the argument's literal value
313
+ * (for string-literal args) or its source text (for
314
+ * non-literal args, with the slot marked opaque). `{N}.prop`
315
+ * reads `prop` from the argument's resolved object.
316
+ *
317
+ * `importModule` optionally narrows matches to helpers
318
+ * imported from a specific module — useful when two packages
319
+ * happen to export a function with the same name.
320
+ *
321
+ * Pack-author docs: `docs/internal/proposals/dynamic-registration.md`.
322
+ */
323
+ type: "registrationTemplate";
324
+ helperName: string;
325
+ importModule?: string;
326
+ registrations: Array<{
327
+ method: string;
328
+ pathTemplate: string;
329
+ handlerArg: string;
330
+ }>;
331
+ } | {
332
+ /**
333
+ * Consumer side of the package-export boundary. Scans source
334
+ * files for imports of the named packages and records every
335
+ * call site, emitting one `caller`-kind unit per enclosing
336
+ * function. Produced bindings carry
337
+ * `function-call { package, exportPath }` matching the
338
+ * provider summaries from `packageExports`.
339
+ *
340
+ * `packages` is a list of exact package names (possibly with a
341
+ * sub-path, e.g. `"@suss/behavioral-ir/schemas"`) whose imports
342
+ * to track. Pass multiple package names to track a family at
343
+ * once. Imports of any other package are ignored.
344
+ *
345
+ * v0 scope: named imports + default imports. Namespace imports
346
+ * (`import * as X from`) are not yet tracked. Re-imports within
347
+ * the consumer repo (consumer A imports from consumer B which
348
+ * re-exports from pkg) produce units against the intermediate,
349
+ * not the original — full symbol resolution is deferred.
350
+ */
351
+ type: "packageImport";
352
+ packages: string[];
353
+ };
354
+ type BindingExtraction = {
355
+ method: {
356
+ type: "fromRegistration";
357
+ position: "methodName" | number;
358
+ } | {
359
+ type: "fromExportName";
360
+ } | {
361
+ type: "fromContract";
362
+ } | {
363
+ type: "fromClientMethod";
364
+ } | {
365
+ type: "fromArgumentProperty";
366
+ position: number;
367
+ property: string;
368
+ default?: string;
369
+ } | {
370
+ type: "literal";
371
+ value: string;
372
+ };
373
+ path: {
374
+ type: "fromRegistration";
375
+ position: number;
376
+ } | {
377
+ type: "fromFilename";
378
+ } | {
379
+ type: "fromContract";
380
+ } | {
381
+ type: "fromClientMethod";
382
+ } | {
383
+ type: "fromArgumentLiteral";
384
+ position: number;
385
+ };
386
+ };
387
+ interface DiscoveryPattern {
388
+ /** The kind of code unit this discovers: "handler", "loader", "action", "component", etc. */
389
+ kind: string;
390
+ match: DiscoveryMatch;
391
+ bindingExtraction?: BindingExtraction;
392
+ /**
393
+ * Files whose import declarations include any of these module
394
+ * specifiers (or sub-paths of them) get this pattern's discovery
395
+ * dispatch. Empty array = no gate (pattern is dispatched against
396
+ * every file). Undefined = treated as no gate, but pack authors
397
+ * SHOULD declare it explicitly — the `[]` form is the deliberate
398
+ * "match every file" choice (typically because the pattern keys on
399
+ * something other than imports — e.g. the fetch runtime matching
400
+ * global `fetch(...)` calls).
401
+ *
402
+ * Match semantics: prefix on the import module specifier. An entry
403
+ * `"@nestjs/graphql"` matches `from "@nestjs/graphql"` AND
404
+ * `from "@nestjs/graphql/dist/foo"` AND any other sub-path.
405
+ *
406
+ * Pre-filter is purely a perf optimisation: the closure walk and
407
+ * other post-passes still have access to every loaded file via
408
+ * symbol resolution.
409
+ */
410
+ requiresImport?: string[];
411
+ }
412
+ type TerminalMatch = {
413
+ type: "returnShape";
414
+ requiredProperties?: string[];
415
+ } | {
416
+ type: "returnStatement";
417
+ /**
418
+ * Skip ReturnStatements whose returned expression is a CallExpression
419
+ * (or NewExpression). For frameworks where `return reply.send(...)`
420
+ * also lands as a `parameterMethodCall` match on the inner call,
421
+ * this prevents the same `return reply.send(...)` from producing
422
+ * two terminals — one from the wrapping returnStatement, one from
423
+ * the inner method-call chain. Bare returns (`return user`,
424
+ * `return { id }`, `return await fn()`) still match.
425
+ */
426
+ excludeCallReturns?: boolean;
427
+ } | {
428
+ type: "parameterMethodCall";
429
+ parameterPosition: number;
430
+ methodChain: string[];
431
+ } | {
432
+ type: "throwExpression";
433
+ constructorPattern?: string;
434
+ } | {
435
+ type: "functionCall";
436
+ functionName: string;
437
+ /**
438
+ * Only match when the name was imported from one of these modules.
439
+ * Same name, shape, and prefix semantics as a DiscoveryPattern's
440
+ * gate: "react-router" also matches "react-router/server".
441
+ *
442
+ * Set it whenever the function belongs to a library, because
443
+ * matching a bare name claims every same-named function in the
444
+ * user's project too. `json` is a common name for a project's own
445
+ * response helper, and reading a library's argument order into one
446
+ * of those produces a confident wrong answer.
447
+ *
448
+ * Leave it unset only when the function is not a library's at all.
449
+ * A pack should generally not name a project's own helper: declare
450
+ * the envelope shape instead, through a `returnShape` terminal, and
451
+ * the adapter follows a returned call into the project and reads
452
+ * the helper's parameters. That covers helpers whatever they are
453
+ * named and in whichever argument order they were written.
454
+ */
455
+ requiresImport?: string[];
456
+ } | {
457
+ /**
458
+ * Return statement whose value is a JSX element or fragment. The
459
+ * root element/component name is recorded in RawTerminal.component.
460
+ * Used by React (and any other JSX-based framework pack) to
461
+ * classify component outputs as `render` terminals.
462
+ */
463
+ type: "jsxReturn";
464
+ } | {
465
+ /**
466
+ * Synthetic terminal for the implicit fall-through at the end of a
467
+ * function body. Fires when the function has no explicit
468
+ * `ReturnStatement` / `ThrowStatement` as its last statement —
469
+ * covers the common case of handler / effect bodies that execute
470
+ * side-effects and return `undefined` implicitly. Without this,
471
+ * handler summaries come out with `transitions: []` because
472
+ * `findTerminals` has nothing to match. Packs that always expect
473
+ * explicit returns (HTTP handlers) shouldn't include this in
474
+ * their terminals; packs for callback bodies (React handlers,
475
+ * `useEffect` bodies, Node `.on(...)` callbacks) should.
476
+ */
477
+ type: "functionFallthrough";
478
+ };
479
+ interface TerminalExtraction {
480
+ statusCode?: {
481
+ from: "property";
482
+ name: string;
483
+ } | {
484
+ from: "argument";
485
+ position: number;
486
+ minArgs?: number;
487
+ } | {
488
+ from: "constructor";
489
+ codes: Record<string, number>;
490
+ } | {
491
+ from: "argumentConstructor";
492
+ position: number;
493
+ codes: Record<string, number>;
494
+ };
495
+ body?: {
496
+ from: "property";
497
+ name: string;
498
+ unwrapJsonStringify?: boolean;
499
+ } | {
500
+ from: "argument";
501
+ position: number;
502
+ minArgs?: number;
503
+ };
504
+ /** Fallback status code when none is extracted. e.g. Express res.json() defaults to 200. */
505
+ defaultStatusCode?: number;
506
+ }
507
+ interface TerminalPattern {
508
+ /** What kind of output this terminal produces: "response", "throw", "return", "render" */
509
+ kind: "response" | "throw" | "return" | "render";
510
+ match: TerminalMatch;
511
+ extraction: TerminalExtraction;
512
+ }
513
+ interface ContractPattern {
514
+ /** How to find the contract object. Contracts are data structures, not code units,
515
+ * so this is a simpler shape than DiscoveryPattern. */
516
+ discovery: {
517
+ importModule: string;
518
+ importName: string;
519
+ registrationChain: string[];
520
+ };
521
+ responseExtraction: {
522
+ /** Property on the contract object that holds the responses map */
523
+ property: string;
524
+ };
525
+ paramsExtraction?: {
526
+ property: string;
527
+ };
528
+ }
529
+ type InputMappingPattern = {
530
+ /** Single object parameter, e.g. React Router LoaderFunctionArgs */
531
+ type: "singleObjectParam";
532
+ paramPosition: number;
533
+ /** Property name → role, e.g. { params: "pathParams", request: "request" } */
534
+ knownProperties: Record<string, string>;
535
+ } | {
536
+ /** Positional parameters, e.g. Express (req, res, next) */
537
+ type: "positionalParams";
538
+ params: Array<{
539
+ position: number;
540
+ role: string;
541
+ }>;
542
+ } | {
543
+ /** Destructured from framework call, e.g. ts-rest { params, body, query } */
544
+ type: "destructuredObject";
545
+ /** Property name → role, e.g. { params: "pathParams", body: "requestBody" } */
546
+ knownProperties: Record<string, string>;
547
+ } | {
548
+ /**
549
+ * Component props, React / Vue / Svelte-style: one parameter that
550
+ * the caller destructures at will, with prop names only visible at
551
+ * the call site. When the parameter is destructured, each bound
552
+ * name becomes its own Input with role equal to the name. When
553
+ * it's not destructured (e.g. `function X(props) {...}`), one
554
+ * Input is emitted with `wholeParamRole` (default `"props"`).
555
+ *
556
+ * Differs from `destructuredObject` in that prop names are not
557
+ * declared by the pack up-front — they are whatever the component
558
+ * author wrote. Differs from `singleObjectParam` in that the
559
+ * destructuring pattern is honored when present.
560
+ */
561
+ type: "componentProps";
562
+ paramPosition: number;
563
+ /** Role for the single Input when the param is not destructured. Defaults to "props". */
564
+ wholeParamRole?: string;
565
+ } | {
566
+ /**
567
+ * Emit one `Input` per declared parameter, in source order, with
568
+ * `role = param name` (or `defaultRole` when set). Used by the
569
+ * reachable-closure pass for internal library functions — there
570
+ * is no framework-declared role space, so the caller-visible
571
+ * name IS the role. Captures destructured-parameter bindings the
572
+ * same way `destructuredObject` does, so `(ctx, { userId })`
573
+ * reads as two inputs: `ctx` and `userId`.
574
+ */
575
+ type: "allPositional";
576
+ defaultRole?: string;
577
+ } | {
578
+ /**
579
+ * Decorator-driven parameter mapping (NestJS-style). For each
580
+ * declared parameter, the adapter reads the parameter's first
581
+ * decorator and looks up its name in `decoratorRoleMap` —
582
+ * matched decorators map the parameter to that role; unmatched
583
+ * parameters fall back to `defaultRole` (or skip when unset).
584
+ *
585
+ * For `@nestjs/graphql` resolvers:
586
+ * `{ "Args": "args", "Parent": "parent",
587
+ * "Context": "context", "Info": "info" }`.
588
+ *
589
+ * Decorator gating is by name only — multiple frameworks
590
+ * defining `@Args` would all map. Packs that need to
591
+ * disambiguate by import module add it later when the use case
592
+ * justifies the cost.
593
+ */
594
+ type: "decoratedParams";
595
+ decoratorRoleMap: Record<string, string>;
596
+ defaultRole?: string;
597
+ };
598
+ /**
599
+ * What a property on the API response object semantically represents.
600
+ * Declared in the pack so the adapter can resolve derived properties
601
+ * (e.g. `.ok` → status range 200–299) at extraction time.
602
+ */
603
+ type ResponsePropertyMeaning = {
604
+ type: "statusCode";
605
+ } | {
606
+ type: "statusRange";
607
+ min: number;
608
+ max: number;
609
+ } | {
610
+ type: "body";
611
+ } | {
612
+ type: "headers";
613
+ };
614
+ interface ResponsePropertyMapping {
615
+ /** Property or method name on the response (e.g. "ok", "status", "json") */
616
+ name: string;
617
+ /** How this member is accessed: property read or method call */
618
+ access: "property" | "method";
619
+ /** What the value semantically represents */
620
+ semantics: ResponsePropertyMeaning;
621
+ }
622
+ interface PatternPack {
623
+ name: string;
624
+ /**
625
+ * Pack version stamp — feeds the cache invalidation key. Bump on
626
+ * any change that affects discovered units / extracted summaries.
627
+ * Format is opaque to the adapter; semver is the obvious choice
628
+ * but a content hash, build SHA, or monotonic integer works too.
629
+ * Optional — packs that omit it use the literal string `"unset"`,
630
+ * which means cache entries from one process to the next aren't
631
+ * meaningfully versioned (suitable for development; production
632
+ * packs should declare a version).
633
+ */
634
+ version?: string;
635
+ languages: string[];
636
+ discovery: DiscoveryPattern[];
637
+ terminals: TerminalPattern[];
638
+ contractReading?: ContractPattern;
639
+ inputMapping: InputMappingPattern;
640
+ /**
641
+ * Transport (wire protocol) used in the `BoundaryBinding.transport`
642
+ * of discovered units. Every pack states its transport explicitly
643
+ * rather than leaning on a hardcoded HTTP default — "what transport
644
+ * does this pack cover?" is a question every pack should have to
645
+ * answer, and making the field required keeps future packs (React,
646
+ * GraphQL, Lambda-invoke, queues) from silently inheriting an
647
+ * HTTP-shaped default that doesn't fit.
648
+ *
649
+ * The pack's `name` separately populates `BoundaryBinding.recognition`
650
+ * on produced summaries — so `{ transport, recognition }` come from
651
+ * the pack directly, and `semantics` is derived by the adapter from
652
+ * the discovery pattern's binding-extraction rules.
653
+ */
654
+ protocol: string;
655
+ /**
656
+ * Semantics of properties on the API response object (consumer side).
657
+ * Tells the adapter how to resolve derived properties like `.ok` or
658
+ * `.json()` to structured IR constructs instead of leaving them opaque.
659
+ */
660
+ responseSemantics?: ResponsePropertyMapping[];
661
+ /**
662
+ * Synthesize additional code units from a parent unit's body —
663
+ * "one user-authored construct implicitly spawns multiple
664
+ * runtime-scheduled units." Used when a framework's runtime
665
+ * schedules callbacks that aren't visible as top-level declarations:
666
+ * React event handlers on JSX elements, React `useEffect` bodies,
667
+ * Node `emitter.on("event", handler)`, class-component lifecycle
668
+ * methods, and similar.
669
+ *
670
+ * `ctx` is typed `unknown` here because the extractor has no
671
+ * knowledge of which adapter is driving it; each language adapter
672
+ * defines its own context shape (e.g. `TsSubUnitContext` in
673
+ * `@suss/adapter-typescript`) with the primitives packs need to
674
+ * walk the parent's AST. Packs import and cast to the adapter
675
+ * context they're written against — the cast is the explicit
676
+ * "this pack requires the TypeScript adapter" contract.
677
+ *
678
+ * Returned units are fed through the adapter's extraction pipeline
679
+ * the same way top-level discovered units are, so each becomes its
680
+ * own `BehavioralSummary`. Carry per-unit `terminals` and
681
+ * `inputMapping` on the `DiscoveredUnit` if the sub-unit's shape
682
+ * differs from the parent pack's defaults.
683
+ */
684
+ subUnits?: (parent: DiscoveredSubUnitParent, ctx: unknown) => DiscoveredSubUnit[];
685
+ /**
686
+ * Pack-supplied top-level discovery callback. Sibling of `subUnits`
687
+ * for the discovery layer: when a framework's discovery convention
688
+ * doesn't fit one of the data-driven `DiscoveryMatch` variants (REST
689
+ * registration, decorator-based controllers, named-export shapes,
690
+ * etc.), the pack ships its own walker here. The adapter calls it
691
+ * once per source file alongside the data-driven dispatch.
692
+ *
693
+ * Use this for framework-specific shapes that don't generalize:
694
+ * React's component-export heuristic (PascalCase + JSX-return),
695
+ * Vue's `.vue` SFC slots, Solid's component conventions, Storybook's
696
+ * `.stories.tsx` file convention. These are real conventions but
697
+ * baking each into the central `DiscoveryMatch` union forces every
698
+ * unrelated pack to know about them. Callbacks keep the central
699
+ * union for the generic primitives and let packs own their own
700
+ * conventions.
701
+ *
702
+ * `ctx` is typed `unknown` for the same reason as `subUnits` — each
703
+ * adapter ships its own context primitive (`TsDiscoveryContext` in
704
+ * `@suss/adapter-typescript`) and packs cast to whichever they're
705
+ * written against. The cast is the "I require the TS adapter"
706
+ * contract.
707
+ *
708
+ * Returned units flow through the adapter's normal pipeline — they
709
+ * get terminals + effects extracted, sub-units synthesized, and
710
+ * summaries assembled exactly as units from data-driven discovery
711
+ * do. Per-unit `terminals` / `inputMapping` overrides on
712
+ * `DiscoveredUnit` work the same way.
713
+ *
714
+ * **Cross-pack dedup.** When this callback discovers a unit at the
715
+ * same `(func, kind)` as a unit from another pack's data-driven
716
+ * discovery, the cross-pack claim dedup in the adapter keeps the
717
+ * first claimant. Pack ordering in the framework list is the
718
+ * precedence signal.
719
+ */
720
+ discoverUnits?: (sourceFile: unknown, ctx: unknown) => DiscoveredCustomUnit[];
721
+ /**
722
+ * Per-call-site recognizers that emit typed `Effect`s alongside the
723
+ * generic `invocation` effect the adapter already captures.
724
+ *
725
+ * **Scope contract.** The adapter walks every CallExpression in
726
+ * the function body and dispatches to every registered recognizer
727
+ * for each call. Walking skips nested function bodies (those are
728
+ * their own units with their own recognizer dispatch). The walk is
729
+ * INDEPENDENT of the existing invocation-effect walker — the
730
+ * existing walker is intentionally narrow (only captures
731
+ * `invocation` effects from bare expression statements + container
732
+ * composition, to avoid double-counting calls that already become
733
+ * terminals). Recognizers don't have that concern; they fire on
734
+ * every call regardless of position, including
735
+ * `const x = await fn(...)` initializers and nested call args
736
+ * (which the invocation walker skips). This independence means
737
+ * recognizer authors can rely on seeing every call in scope.
738
+ *
739
+ * **Cross-pack visibility.** Recognizers fire regardless of which
740
+ * pack discovered the enclosing function — so
741
+ * `@suss/framework-prisma`'s recognizer can fire on Prisma calls
742
+ * inside an `@suss/framework-express` handler. Pack authors don't
743
+ * need to coordinate.
744
+ *
745
+ * **Emission contract.** Returning effects ADDS them to the
746
+ * enclosing default-branch transition; the generic `invocation`
747
+ * effect is preserved either way (typed effects coexist with raw
748
+ * call capture so inspect rendering keeps callee text + args while
749
+ * the checker pairs on the typed shape). Returning `null` / `[]`
750
+ * is the no-match path.
751
+ *
752
+ * **Dedup is the recognizer's responsibility.** The dispatcher
753
+ * doesn't dedupe across calls. A recognizer that wants to fire
754
+ * once per identifier (e.g., to dedupe reads bound to a const
755
+ * that's used N times) tracks its own state across invocations.
756
+ *
757
+ * **Exceptions are caught and logged.** A recognizer that throws
758
+ * is logged to stderr with file path + line number and skipped
759
+ * for that call; the extraction continues. Buggy recognizers
760
+ * don't crash the run.
761
+ *
762
+ * `call` is the language adapter's call-expression handle (opaque
763
+ * here; ts-morph `CallExpression` in `@suss/adapter-typescript`).
764
+ * `ctx` is the adapter's recognizer context (source file, an
765
+ * `extractArgs()` helper that reuses the adapter's own EffectArg
766
+ * builder). Recognizers cast both to the adapter context they're
767
+ * written against — same "this pack requires the TypeScript
768
+ * adapter" contract `subUnits` uses.
769
+ */
770
+ invocationRecognizers?: InvocationRecognizer[];
771
+ /**
772
+ * Optional pack-level import gate. When set, the adapter's
773
+ * pre-filter only considers this pack applicable to source files
774
+ * whose imports include at least one of the listed modules
775
+ * (prefix match — `"@aws-sdk/client-sqs"` matches that module
776
+ * AND any `"@aws-sdk/client-sqs/sub-path"`).
777
+ *
778
+ * Useful for recognizer-only packs that target a specific library:
779
+ * `@suss/framework-aws-sqs` declares `["@aws-sdk/client-sqs"]`,
780
+ * `@suss/framework-prisma` declares `["@prisma/client"]`. Without
781
+ * a gate, recognizer-only packs walk every file in the project —
782
+ * correct but wasted work on large monorepos where most files
783
+ * don't import the library.
784
+ *
785
+ * Discovery-pattern packs already have per-pattern `requiresImport`
786
+ * on `DiscoveryPattern`; this is the pack-level equivalent for
787
+ * packs whose ONLY mechanism is recognizers (no discovery).
788
+ *
789
+ * Empty / undefined means "no gate" — pack walks every file (the
790
+ * default for truly universal recognizers like `@suss/runtime-node`'s
791
+ * process-surface / env-var recognizers, since `process.*` is
792
+ * available without an import).
793
+ */
794
+ requiresImport?: string[];
795
+ /**
796
+ * Per-property-access recognizers — sister to
797
+ * `invocationRecognizers` but firing on `PropertyAccessExpression`
798
+ * nodes rather than `CallExpression` nodes. Use these for patterns
799
+ * that read a value through property access without invoking it:
800
+ * `process.env.X` env-var reads, `Date.now()`-style time reads
801
+ * (which is actually a call, see invocationRecognizers), bare
802
+ * `module.constant` reads, etc.
803
+ *
804
+ * Same scope rules as invocationRecognizers — fires on every
805
+ * PropertyAccessExpression in the function body, skipping nested
806
+ * function bodies. Same emission contract: returned effects land
807
+ * on the enclosing default-branch transition.
808
+ *
809
+ * Same reasoning as invocationRecognizers for the opaque-arg /
810
+ * adapter-narrows pattern.
811
+ */
812
+ accessRecognizers?: AccessRecognizer[];
813
+ }
814
+ /**
815
+ * Per-call-site recognizer hook. See `PatternPack.invocationRecognizers`
816
+ * for the contract and threading model.
817
+ */
818
+ type InvocationRecognizer<TCtx = unknown> = (call: unknown, ctx: TCtx) => Effect[] | null;
819
+ /**
820
+ * Per-property-access recognizer hook. See
821
+ * `PatternPack.accessRecognizers` for the contract and threading model.
822
+ */
823
+ type AccessRecognizer<TCtx = unknown> = (access: unknown, ctx: TCtx) => Effect[] | null;
824
+ /**
825
+ * Minimal handle-shaped description of the parent code unit that
826
+ * `subUnits` operates within. `func` is left opaque here — the
827
+ * language adapter brands its own FunctionRoot type. This interface
828
+ * lives in the extractor only so `PatternPack` can name it;
829
+ * adapter-level context types (`TsSubUnitContext`) narrow `func` to
830
+ * a concrete AST handle.
831
+ */
832
+ interface DiscoveredSubUnitParent {
833
+ /** Handle to the parent's function body. Opaque at extractor level. */
834
+ func: unknown;
835
+ /** Discovered name of the parent (e.g. "Counter"). */
836
+ name: string;
837
+ /** Kind of the parent (usually "component", "handler", etc.). */
838
+ kind: string;
839
+ }
840
+ /**
841
+ * What a pack's `discoverUnits` hook returns per discovered top-level
842
+ * unit. Mirrors `DiscoveredSubUnit` for the discovery layer — the
843
+ * adapter widens these into its own internal `DiscoveredUnit` type
844
+ * (which carries adapter-specific fields like `routeInfo`,
845
+ * `packageExportInfo`, etc.) and pipes them through the normal
846
+ * extraction pipeline.
847
+ *
848
+ * Pack authors stay in opaque-handle land — `func` is whatever the
849
+ * adapter's primitive returns. Adapter-narrows this to its concrete
850
+ * function-root type (e.g. `FunctionRoot` in
851
+ * `@suss/adapter-typescript`).
852
+ */
853
+ interface DiscoveredCustomUnit {
854
+ /** Function body handle, opaque here. */
855
+ func: unknown;
856
+ /** IR code-unit kind (e.g. "component", "handler"). */
857
+ kind: string;
858
+ /** Discovered name (e.g. "UserCard"). */
859
+ name: string;
860
+ /**
861
+ * Terminal patterns to extract from this unit's body. Defaults to
862
+ * the pack-level `terminals` when unset.
863
+ */
864
+ terminals?: TerminalPattern[];
865
+ /**
866
+ * Input mapping for this unit. Defaults to the pack-level
867
+ * `inputMapping` when unset.
868
+ */
869
+ inputMapping?: InputMappingPattern;
870
+ /**
871
+ * REST route identity for units a callback discovers against an
872
+ * external manifest (a SAM/CFN template's `Events` block, an infra
873
+ * routing declaration, etc.) rather than an in-code registration.
874
+ * When set, the adapter builds a `rest` binding from `(method, path)`
875
+ * — the same binding NestJS controllers get via decorator-derived
876
+ * `routeInfo`, without the discoverUnits callback needing to reach
877
+ * into the adapter's binding machinery.
878
+ *
879
+ * One function bound to several routes emits one DiscoveredCustomUnit
880
+ * per route; the adapter's per-file claim dedup keys on
881
+ * `(func, kind, method, path)` so the variants survive.
882
+ */
883
+ routeInfo?: {
884
+ method: string;
885
+ path: string;
886
+ };
887
+ /**
888
+ * Metadata merged onto the resulting summary's `metadata` field.
889
+ */
890
+ metadata?: Record<string, unknown>;
891
+ }
892
+ /**
893
+ * What a pack's `subUnits` hook returns per synthesized child. The
894
+ * adapter pipes each of these through the same extraction + assembly
895
+ * pipeline used for top-level-discovered units.
896
+ */
897
+ interface DiscoveredSubUnit {
898
+ /** Function body handle, opaque here. */
899
+ func: unknown;
900
+ /** IR code-unit kind (e.g. "handler"). */
901
+ kind: string;
902
+ /** Qualified name (e.g. "Counter.button.onClick"). */
903
+ name: string;
904
+ /**
905
+ * Terminal patterns to extract from this sub-unit's body. Defaults
906
+ * to `return` + `throw` when unset — fits handlers / effects cleanly.
907
+ */
908
+ terminals?: TerminalPattern[];
909
+ /**
910
+ * Input mapping for this sub-unit. Defaults to an empty positional
911
+ * mapping when unset — event handlers with one arg should pass
912
+ * `{ type: "positionalParams", params: [{ position: 0, role: "event" }] }`.
913
+ */
914
+ inputMapping?: InputMappingPattern;
915
+ /**
916
+ * Metadata merged onto the resulting summary's `metadata` field.
917
+ * Packs use this to stamp provenance (`metadata.react = { kind: "handler", ... }`).
918
+ */
919
+ metadata?: Record<string, unknown>;
920
+ }
921
+
922
+ /**
923
+ * Build the `discovery` entries for an HTTP-server framework whose
924
+ * handlers are registered via `app.get(path, handler)` /
925
+ * `router.post(...)` / etc.
926
+ *
927
+ * Each `importNames` entry produces one DiscoveryPattern — libraries
928
+ * typically expose both a default export and a named export that both
929
+ * produce the routable instance (e.g. Express has `express()` and
930
+ * `Router()`; Fastify has `fastify()` and `Fastify`). The binding
931
+ * extraction (method from registration, path from position 0) is
932
+ * identical across every HTTP server framework we support.
933
+ *
934
+ * Callers still supply the `methods` list directly: different
935
+ * frameworks support different HTTP verbs (Fastify includes `.head` /
936
+ * `.options`, Express historically does not by default).
937
+ *
938
+ * @example
939
+ * discovery: httpRouteDiscovery({
940
+ * importModule: "express",
941
+ * importNames: ["Router", "express"],
942
+ * methods: [".get", ".post", ".put", ".delete", ".patch"],
943
+ * })
944
+ */
945
+ declare function httpRouteDiscovery(opts: {
946
+ importModule: string;
947
+ importNames: readonly string[];
948
+ methods: readonly string[];
949
+ /** Defaults to "handler". Override for packs that want a different kind. */
950
+ kind?: string;
951
+ }): DiscoveryPattern[];
952
+
953
+ interface RawParameter {
954
+ name: string;
955
+ position: number;
956
+ role: string;
957
+ typeText: string | null;
958
+ }
959
+ interface RawCondition {
960
+ sourceText: string;
961
+ structured: Predicate | null;
962
+ polarity: "positive" | "negative";
963
+ source: "explicit" | "earlyReturn" | "earlyThrow" | "catchBlock";
964
+ }
965
+ interface RawTerminal {
966
+ kind: "response" | "throw" | "return" | "render" | "delegate" | "emit" | "void";
967
+ statusCode: {
968
+ type: "literal";
969
+ value: number;
970
+ } | {
971
+ type: "dynamic";
972
+ sourceText: string;
973
+ } | null;
974
+ body: {
975
+ typeText: string | null;
976
+ shape: TypeShape | null;
977
+ } | null;
978
+ exceptionType: string | null;
979
+ message: string | null;
980
+ /** For render terminals: the component being rendered */
981
+ component: string | null;
982
+ /**
983
+ * For render terminals (JSX / component-tree output): the nested
984
+ * render tree if the pack's extractor can produce one. Flows
985
+ * through to `Output.render.root`. Absent when the extractor sees
986
+ * only the root element name or opted out of tree extraction.
987
+ */
988
+ renderTree: RenderNode | null;
989
+ /** For delegate terminals: where control is passed */
990
+ delegateTarget: string | null;
991
+ /** For emit terminals: the event/channel name */
992
+ emitEvent: string | null;
993
+ location: {
994
+ start: number;
995
+ end: number;
996
+ };
997
+ }
998
+ /**
999
+ * A structured capture of an invocation argument. Literal values
1000
+ * (string, number, boolean), object/array literals, template literals,
1001
+ * identifier references, and nested call expressions are all captured
1002
+ * as structured variants so readers can see *what* was passed even when
1003
+ * the runtime value isn't statically resolvable. `null` is reserved for
1004
+ * the rare case where the argument shape doesn't match any variant
1005
+ * (type assertions with computed operands, arithmetic, etc.) — the
1006
+ * caller still knows how many arguments the call had.
1007
+ *
1008
+ * Useful for recognising literal-string discriminators like
1009
+ * `findings.push({ kind: "scenarioCoverageGap" })` or
1010
+ * `dispatch({ type: "USER_LOGGED_IN" })` at the summary level, and for
1011
+ * preserving argument *shape* (`{ userId, count }`, `getUser(id)`) so
1012
+ * downstream consumers (AI agents, error-taxonomy tooling,
1013
+ * release-note generators) can reason about composition without
1014
+ * re-reading source.
1015
+ */
1016
+ type EffectArg = {
1017
+ kind: "string";
1018
+ value: string;
1019
+ } | {
1020
+ kind: "number";
1021
+ value: number;
1022
+ } | {
1023
+ kind: "boolean";
1024
+ value: boolean;
1025
+ } | {
1026
+ kind: "object";
1027
+ fields: Record<string, EffectArg>;
1028
+ } | {
1029
+ kind: "array";
1030
+ items: EffectArg[];
1031
+ }
1032
+ /**
1033
+ * Template literal with interpolation (`` `Error: ${e.message}` ``).
1034
+ * The resolved runtime value is unknown, but the source text is
1035
+ * preserved so readers can see which variables compose it — useful
1036
+ * for log messages, computed keys, and dedup-style key builders.
1037
+ */
1038
+ | {
1039
+ kind: "template";
1040
+ sourceText: string;
1041
+ }
1042
+ /**
1043
+ * Identifier reference — a bare variable (`userId`), a property-access
1044
+ * chain (`user.profile.email`, `process.env.DATABASE_URL`), or an
1045
+ * element-access chain (`config["host"]`). `name` holds the full
1046
+ * source text so readers can tell which binding flowed into the call
1047
+ * without inferring it from context. Identifiers that resolve to a
1048
+ * module-level `const` with a simple initializer (literal, process.env
1049
+ * access, etc.) are inlined to the initializer's EffectArg form so
1050
+ * closure-over-constants doesn't hide the actual value at call sites.
1051
+ */
1052
+ | {
1053
+ kind: "identifier";
1054
+ name: string;
1055
+ }
1056
+ /**
1057
+ * Nested call expression passed as an argument — `log(formatError(e))`,
1058
+ * `enqueue(buildPayload(ctx))`, inline composition. `callee` is the
1059
+ * source text of the call target; `args` recurses with bounded depth,
1060
+ * so `formatError(e)` reads as `{ callee: "formatError", args: [...] }`
1061
+ * rather than an opaque null.
1062
+ */
1063
+ | {
1064
+ kind: "call";
1065
+ callee: string;
1066
+ args: EffectArg[];
1067
+ } | null;
1068
+ type RawEffect = {
1069
+ type: "mutation";
1070
+ target: string;
1071
+ operation: "create" | "update" | "delete";
1072
+ } | {
1073
+ type: "invocation";
1074
+ callee: string;
1075
+ args: EffectArg[];
1076
+ async: boolean;
1077
+ /**
1078
+ * Ancestor if/switch/ternary conditions that gate reaching this
1079
+ * call. Populated for calls nested inside conditional blocks or
1080
+ * loop bodies; empty for top-level (always-fires) calls.
1081
+ * Converts to `Effect.preconditions: Predicate[]` in the IR —
1082
+ * same RawCondition → Predicate pipeline transitions use.
1083
+ */
1084
+ preconditions?: RawCondition[];
1085
+ } | {
1086
+ type: "emission";
1087
+ event: string;
1088
+ } | {
1089
+ type: "stateChange";
1090
+ variable: string;
1091
+ };
1092
+ interface RawBranch {
1093
+ conditions: RawCondition[];
1094
+ terminal: RawTerminal;
1095
+ effects: RawEffect[];
1096
+ /**
1097
+ * Pre-typed `Effect`s emitted by `PatternPack.invocationRecognizers`.
1098
+ * Bypass the `RawEffect → Effect` conversion that `effects` runs
1099
+ * through — recognizers have full structural knowledge of what
1100
+ * they're emitting (e.g. `interaction(class: "storage-access")` with
1101
+ * binding identity + per-class fields/selector)
1102
+ * and there's no narrower extractor-side representation worth
1103
+ * round-tripping through. Concatenated with converted `effects` at
1104
+ * `assembleSummary` time.
1105
+ */
1106
+ extraEffects?: Effect[];
1107
+ location: {
1108
+ start: number;
1109
+ end: number;
1110
+ };
1111
+ isDefault: boolean;
1112
+ /**
1113
+ * Shape of upstream data the code unit reads within this branch.
1114
+ * Populated by the adapter for client/consumer code units: after
1115
+ * branching on a response status, the consumer accesses fields on
1116
+ * the response body — those accesses are collected into a TypeShape.
1117
+ * The extractor copies this through to Transition.expectedInput.
1118
+ */
1119
+ expectedInput?: TypeShape | null;
1120
+ }
1121
+ interface RawDependencyCall {
1122
+ name: string;
1123
+ assignedTo: string | null;
1124
+ async: boolean;
1125
+ returnType: string | null;
1126
+ location: {
1127
+ start: number;
1128
+ end: number;
1129
+ };
1130
+ }
1131
+ interface RawDeclaredContract {
1132
+ framework: string;
1133
+ responses: Array<{
1134
+ statusCode: number;
1135
+ /**
1136
+ * Structured body schema the contract declares for this status code.
1137
+ * Populated when the adapter can statically resolve the response schema
1138
+ * (e.g. `c.type<T>()`'s type argument). Null when the contract exists
1139
+ * but no body was declared or the schema form isn't yet supported.
1140
+ */
1141
+ body?: TypeShape | null;
1142
+ }>;
1143
+ params?: Record<string, {
1144
+ type: string;
1145
+ required: boolean;
1146
+ }>;
1147
+ /**
1148
+ * Relationship between this declared contract and the `transitions[]`
1149
+ * on the same summary:
1150
+ *
1151
+ * - "derived": both are extracted from the same source data, so
1152
+ * comparing them against each other is tautological. Example: an
1153
+ * OpenAPI stub's contract and its transitions both come from the
1154
+ * operation's `responses` block. The cross-boundary checker
1155
+ * skips per-summary contract-consistency for these.
1156
+ *
1157
+ * - "independent": the contract is a separate statement from the
1158
+ * transitions. Example: a ts-rest handler whose router declares
1159
+ * `responses` and whose implementation is a separate function;
1160
+ * a CFN stub whose `MethodResponses` and integration config are
1161
+ * independent template fields. Contract-consistency comparison
1162
+ * is meaningful.
1163
+ *
1164
+ * Defaults to "independent" when a pack doesn't say — safer to
1165
+ * surface a spurious-but-investigable finding than to silently drop
1166
+ * a real one.
1167
+ */
1168
+ provenance?: "derived" | "independent";
1169
+ }
1170
+ interface RawCodeStructure {
1171
+ identity: {
1172
+ name: string;
1173
+ kind: CodeUnitKind;
1174
+ file: string;
1175
+ range: {
1176
+ start: number;
1177
+ end: number;
1178
+ };
1179
+ exportName: string | null;
1180
+ exportPath: string[] | null;
1181
+ };
1182
+ /**
1183
+ * Three-layer boundary description (`transport`, `semantics`,
1184
+ * `recognition`). See `@suss/behavioral-ir`'s `BoundaryBinding`.
1185
+ * Null when the unit participates in no cross-unit boundary —
1186
+ * helpers, pure utilities, and anything the adapter can't place.
1187
+ */
1188
+ boundaryBinding: BoundaryBinding | null;
1189
+ parameters: RawParameter[];
1190
+ branches: RawBranch[];
1191
+ dependencyCalls: RawDependencyCall[];
1192
+ declaredContract: RawDeclaredContract | null;
1193
+ /**
1194
+ * Names of pack-declared response properties whose semantics is `body`.
1195
+ * For client units this records the accessor a consumer uses to reach the
1196
+ * body (e.g. ["data"] for axios, ["body","json","text"] for fetch). Carried
1197
+ * through to `summary.metadata.http.bodyAccessors` so the cross-boundary
1198
+ * checker can unwrap consumer expectedInput correctly without knowing each
1199
+ * pack. Flat here because this is the adapter→extractor plumbing contract;
1200
+ * nesting under `http` happens when the summary is assembled.
1201
+ */
1202
+ bodyAccessors?: string[];
1203
+ /**
1204
+ * Names of pack-declared response properties whose semantics is
1205
+ * `statusCode`. Same shape as `bodyAccessors` but for the status side —
1206
+ * lets the checker recognise pack-specific names (e.g. fetch's `status`,
1207
+ * axios's `status`, hypothetical `responseStatus`) when matching consumer
1208
+ * branch conditions to provider transitions. Carried through to
1209
+ * `summary.metadata.http.statusAccessors`.
1210
+ */
1211
+ statusAccessors?: string[];
1212
+ /**
1213
+ * Raw GraphQL document source attached to consumer-side graphql
1214
+ * summaries (the inner text of a `useQuery(gql\`...\`)` call).
1215
+ * Assembled into `summary.metadata.graphql.document` for the
1216
+ * checker's pairing layer to parse. Deliberately untouched here —
1217
+ * the extractor has no dependency on graphql-js; parsing happens
1218
+ * at check time.
1219
+ */
1220
+ graphqlDocument?: string;
1221
+ /**
1222
+ * Raw GraphQL schema SDL attached to provider-side resolver
1223
+ * summaries. For schema-first stubs (AppSync), the SDL IS the
1224
+ * schema. For code-first frameworks (Apollo Server), the SDL
1225
+ * comes from the `typeDefs` config option when statically
1226
+ * resolvable. Surfaced as `summary.metadata.graphql.schemaSdl`
1227
+ * so the checker can use the return-type field set when pairing
1228
+ * nested consumer selections.
1229
+ */
1230
+ graphqlSchemaSdl?: string;
1231
+ /**
1232
+ * Declared contract derived from the SDL field for this resolver:
1233
+ * `{ returnType, args, provenance, framework }`. Surfaced as
1234
+ * `summary.metadata.graphql.declaredContract` so
1235
+ * `checkGraphqlContractAgreement` can pair it against other
1236
+ * sources declaring a contract for the same boundary.
1237
+ *
1238
+ * The extractor doesn't derive this — adapters that have the SDL
1239
+ * AND know the (typeName, fieldName) of the resolver populate it.
1240
+ * Today: the TS adapter does this for Apollo-style resolverMap
1241
+ * discovery. NestJS GraphQL (decorator-based) is a follow-up.
1242
+ */
1243
+ graphqlDeclaredContract?: Record<string, unknown>;
1244
+ /**
1245
+ * Set on a consumer-side GraphQL summary whose document reference was
1246
+ * recognized (an imported `TypedDocumentNode` from graphql-codegen,
1247
+ * say) but whose body couldn't be read statically. Surfaced as
1248
+ * `summary.metadata.graphql.unresolvedDocument` so the unreadable
1249
+ * document is accounted for rather than silently dropped. `reference`
1250
+ * is the identifier passed to the hook / imperative call; `reason`
1251
+ * explains what defeated static resolution.
1252
+ */
1253
+ graphqlUnresolvedDocument?: {
1254
+ reference: string;
1255
+ reason: string;
1256
+ };
1257
+ }
1258
+ interface ExtractorOptions {
1259
+ gapHandling: "strict" | "permissive" | "silent";
1260
+ }
1261
+ declare function makeTransitionId(functionName: string, branch: RawBranch): string;
1262
+ declare function assembleSummary(raw: RawCodeStructure, options?: ExtractorOptions): BehavioralSummary;
1263
+ declare function detectGaps(raw: RawCodeStructure, transitions: Transition[], options: ExtractorOptions): Gap[];
1264
+ declare function assessConfidence(raw: RawCodeStructure): ConfidenceInfo;
1265
+ declare function terminalToOutput(terminal: RawTerminal): Output;
1266
+ declare function effectToIR(effect: RawEffect): Effect;
1267
+ declare function paramToInput(param: RawParameter): Input;
1268
+
1269
+ export { type AccessRecognizer, type BindingExtraction, type ContractPattern, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type ExtractorOptions, type InputMappingPattern, type InvocationRecognizer, type PatternPack, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type ResponsePropertyMapping, type ResponsePropertyMeaning, type TerminalExtraction, type TerminalMatch, type TerminalPattern, assembleSummary, assessConfidence, detectGaps, effectToIR, httpRouteDiscovery, makeTransitionId, paramToInput, terminalToOutput };