@suss/adapter-typescript 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { SourceFile, Node, FunctionDeclaration, FunctionExpression, ArrowFunction, MethodDeclaration, CallExpression, Project, Expression } from 'ts-morph';
2
- import { TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, EffectArg, RawDeclaredContract, ContractPattern, ResponsePropertyMapping } from '@suss/extractor';
1
+ import { SourceFile, Node, FunctionDeclaration, FunctionExpression, ArrowFunction, MethodDeclaration, CallExpression, Project, Symbol, Expression } from 'ts-morph';
2
+ import { TransparentWrapper, RawCondition, DiscoveryPattern, TerminalPattern, InputMappingPattern, LanguageAdapter, PatternPack, ExtractorOptions, InvocationRecognizer, AccessRecognizer, RawCodeStructure, RawTerminal, RawBranch, EffectArg, RawDeclaredContract, ContractPattern, ResponsePropertyMapping } from '@suss/extractor';
3
3
  import { BehavioralSummary, MessageBusSemantics, DeployableUnit, BoundaryBinding, Predicate, TypeShape, ValueRef } from '@suss/behavioral-ir';
4
4
 
5
5
  /** Reported by `lookup`: what the cache decided, and why. */
@@ -15,7 +15,7 @@ interface CacheDiagnostic {
15
15
  missReason?: "no-manifest" | "key-changed" | "tsconfig-changed" | "files-changed";
16
16
  }
17
17
  /**
18
- * Result of a cache lookup. A hit carries the whole summary set; a miss
18
+ * Result of a cache lookup. A hit gives back the whole summary set; a miss
19
19
  * says why, so a caller can render the reason.
20
20
  */
21
21
  type CacheLookup = {
@@ -33,18 +33,18 @@ interface PackFunnel {
33
33
  /**
34
34
  * What the pack calls this build of itself, or null when it declares
35
35
  * nothing. The cache keys on it, so a pack that never changes its
36
- * stamp can answer a later run with an earlier build's results.
36
+ * stamp can serve a later run with an earlier build's results.
37
37
  */
38
38
  version: string | null;
39
39
  /**
40
- * Whether the pack looks for units of its own at all. A pack made
41
- * only of recognisers contributes effects to units other packs found,
42
- * so discovering nothing is how it always behaves and says nothing
43
- * about whether it is working.
40
+ * Whether the pack looks for units of its own at all. A pack made only
41
+ * of recognisers contributes effects to units other packs found, so it
42
+ * always discovers nothing, and that tells you nothing about whether
43
+ * it is working.
44
44
  */
45
45
  discovers: boolean;
46
46
  /**
47
- * Whether the pack carries recognisers. Those fire inside units other
47
+ * Whether the pack has any recognisers. Those fire inside units other
48
48
  * packs discovered, so a pack made only of them contributes effects
49
49
  * and never a summary.
50
50
  */
@@ -70,8 +70,8 @@ interface PackFunnel {
70
70
  * Unit bodies any pack walked in the files this pack's gate selected.
71
71
  *
72
72
  * This is what a recogniser pack had the chance to fire on. Its own
73
- * discovery count says nothing, since it discovers nothing by design,
74
- * and its candidate-file count says nothing either, because a
73
+ * discovery count tells you nothing, since it discovers nothing by
74
+ * design, and neither does its candidate-file count, because a
75
75
  * recogniser only runs where some pack found a unit to walk.
76
76
  */
77
77
  unitsInGatedFiles: number;
@@ -80,7 +80,7 @@ interface PackFunnel {
80
80
  /**
81
81
  * Units this pack kept. A unit an earlier pack already claimed is
82
82
  * dropped here, so a pack can discover plenty and keep none when it
83
- * sits behind a pack that recognises the same code.
83
+ * comes after a pack that recognises the same code.
84
84
  */
85
85
  unitsClaimed: number;
86
86
  /**
@@ -92,19 +92,19 @@ interface PackFunnel {
92
92
  selfCollisions: number;
93
93
  /** Summaries built from those units. */
94
94
  summariesProduced: number;
95
- /** Summaries in the finished run that name this pack as what recognised them. */
95
+ /** Summaries in the finished run that credit this pack for finding them. */
96
96
  summariesBound: number;
97
97
  /**
98
98
  * Bound summaries on the provider side of their boundary.
99
99
  *
100
- * Transitions say what a unit does with a request, which is a
101
- * question only a provider answers. A consumer records what it reads
102
- * back from the response instead, and that lives on the summary's
103
- * metadata. Measuring a client pack against transitions would report
104
- * every working one as extracting nothing.
100
+ * Transitions say what a unit does with a request, which only a
101
+ * provider can say. A consumer records what it reads back from the
102
+ * response instead, and that goes on the summary's metadata. Measuring
103
+ * a client pack against transitions would report every working one as
104
+ * having extracted nothing.
105
105
  */
106
106
  providerSummaries: number;
107
- /** Provider summaries carrying at least one transition. */
107
+ /** Provider summaries with at least one transition. */
108
108
  summariesWithBehavior: number;
109
109
  /**
110
110
  * Where one of this pack's hooks threw. Every count above is a floor
@@ -119,6 +119,17 @@ interface ExtractionReport {
119
119
  filesWalked: number;
120
120
  packs: PackFunnel[];
121
121
  summaries: number;
122
+ /**
123
+ * Files whose exports the checker could not follow, so the run read
124
+ * them as exporting nothing.
125
+ *
126
+ * Without this the artifact cannot tell the two apart: a module whose
127
+ * barrel chain outran the call stack and a module that really does
128
+ * export nothing both produce no summaries and exit 0.
129
+ * Anything reachable only through these files is missing, and every
130
+ * count below is a floor while this is non-empty.
131
+ */
132
+ filesWithUnreadableExports: string[];
122
133
  /**
123
134
  * The first stage whose count was zero, when the run produced
124
135
  * nothing. Null when the run produced summaries.
@@ -129,13 +140,13 @@ type EmptyStage = "tsconfig" | "gateResolution" | "candidateFiles" | "discovery"
129
140
  /**
130
141
  * A pack's hook throwing on one file.
131
142
  *
132
- * The run carries on with the other files, so every count for that pack
143
+ * The run continues with the other files, so every count for that pack
133
144
  * afterwards is a floor rather than a total. Somebody reading those
134
- * counts has to be told, or a pack that broke reads the same as a pack
145
+ * counts has to be told, or a pack that broke looks the same as a pack
135
146
  * that looked and found nothing.
136
147
  */
137
148
  interface PackFailure {
138
- /** The hook that threw, named as a pack author would know it. */
149
+ /** The hook that threw, called what a pack author would call it. */
139
150
  hook: string;
140
151
  /** The file the pack was reading. */
141
152
  file: string;
@@ -148,147 +159,98 @@ interface FileSetQuery {
148
159
  packages: ReadonlyArray<string>;
149
160
  }
150
161
 
162
+ /**
163
+ * The resolution store, and the three questions callers ask it.
164
+ *
165
+ * `resolveCallable` says which function a value comes down to, through
166
+ * any depth of aliasing, imports, re-export barrels, wrapper factories
167
+ * and `.bind`. `resolveWrittenValue` says which expression a value is
168
+ * written as, for callers chasing something that is neither a function
169
+ * nor an object. `filesImportingTransitively` says which of a set of
170
+ * files reach any of a set of packages, which a per-file import check
171
+ * misses whenever a local barrel re-exports the SDK.
172
+ *
173
+ * Facts are extracted per file on demand and only along the module
174
+ * edges a query follows, so cost tracks how indirect the code is.
175
+ */
176
+
151
177
  declare class ResolutionStore {
152
178
  private readonly db;
153
179
  private readonly table;
154
180
  private readonly fullyExtracted;
155
181
  private readonly seededValues;
156
182
  private readonly importedNames;
183
+ private readonly declarations;
157
184
  private readonly graph;
158
185
  private stale;
159
186
  constructor(wrappers?: TransparentWrapper[]);
160
- /**
161
- * The function `value` resolves to, or null when no chain reaches
162
- * one. The result is a ts-morph node in whatever file the function
163
- * actually lives.
164
- *
165
- * Facts come in waves: extract the file the value lives in, ask, and
166
- * only widen to the files it imports when the answer is still
167
- * missing. A value that resolves without leaving its own file costs
168
- * one file of extraction, not the whole import closure.
169
- */
170
187
  resolveCallable(value: Node): Node | null;
171
- /**
172
- * The object literal `value` ends up being, or null. The route a
173
- * registration call names often lives on a shared object built in
174
- * another file, and this is how discovery reads it back.
175
- */
176
188
  resolveObject(value: Node): Node | null;
177
189
  /**
178
- * The expression `value` is written as, or null. Unlike the other two
179
- * questions this one does not care what kind of expression it lands
180
- * on, so it answers for values that are neither functions nor
181
- * objects: a GraphQL document held in a named constant in another
182
- * file comes back as the template literal or tag call that built it.
183
- *
184
- * A value the code computes has no written form to report, so the
185
- * answer is the computing expression itself and the caller gets
186
- * nothing it can use, which is the point.
190
+ * For a value that is neither a function nor an object, such as a
191
+ * GraphQL document kept in a constant in another file.
187
192
  */
188
193
  resolveWrittenValue(value: Node): Node | null;
189
194
  /**
190
- * The names from `modules` this value stands for. A value stands for
191
- * a library name two ways, and a caller asking which library concept
192
- * something is wants both:
193
- *
194
- * - it is that name, under whatever local spelling. `import
195
- * { Controller as Resource }` makes `Resource` the library's
196
- * `Controller`.
197
- * - calling it calls that name. A project decorator written as
198
- * `(path) => Controller(path)` marks a controller, and so does
199
- * one written as `applyDecorators(Controller(path))`.
200
- *
201
- * Several names is the normal answer rather than an ambiguity, since
202
- * a wrapper composing two library decorators applies both. The caller
203
- * asks whether the name it cares about is among them.
204
- *
205
- * One wrapper is applied across hundreds of files, so the answer is
206
- * memoized against the declaration it was asked about. Without that,
207
- * every use site seeds a fresh value and pays another fixpoint.
195
+ * Which names from `modules` this value comes down to, either by being
196
+ * one of them under a local name or by calling into one. Getting
197
+ * several back is normal, since a wrapper can compose two library
198
+ * decorators.
208
199
  */
209
200
  importedNamesOf(value: Node, modules: string[]): string[];
210
201
  /**
211
- * The names from `modules` this value stands for, or null while the
212
- * walk should keep widening.
213
- *
214
- * The distinction is what keeps the cost down. A value that has
215
- * arrived at a package has nothing more to give, because a library's
216
- * own body is not here to read, so a decorator from a library the
217
- * caller did not ask about stops on the first wave rather than
218
- * running out the import closure looking for a match it will never
219
- * find. A value that has only arrived at another file in the project
220
- * keeps going, since the wrapper it names is declared somewhere the
221
- * walk has not reached yet.
202
+ * Null tells the walk to keep widening. A value that landed in a
203
+ * package returns empty instead, because a library's own body is not
204
+ * here to read and widening would never find anything.
222
205
  */
223
206
  private lookupImportedNames;
224
207
  private resolveByWaves;
225
208
  private walkForAnswer;
226
209
  /**
227
- * The answer has been read, so the question and the chain that
228
- * answered it can go. What the query extracted stays: files are the
229
- * expensive part and the next query reads the same facts.
230
- *
231
- * Nothing is owed after this. The rules have seen every fact the
232
- * database holds and concluded what the demand still present asks
233
- * for, which is none, so the next query starts from its own asking
234
- * fact.
210
+ * The question and the chain that settled it are dropped. The files
211
+ * the query extracted stay, since the next query reads the same facts.
212
+ * Nothing is left half-done, so the store is not stale afterwards.
235
213
  */
236
214
  private forgetQuery;
237
- /**
238
- * Somebody asked about this value, which is what the rules follow
239
- * chains for. Separate from `seedValue`, which only speaks for values
240
- * that are expressions; a caller can ask about a declaration too, and
241
- * the answer is still owed.
242
- */
243
215
  private wantValue;
244
216
  /**
245
- * Facts for the queried value itself. File extraction only reaches
246
- * values that hang off exports, and a query can be rooted anywhere:
247
- * a registration call passes `routes.provision` as an argument, and
248
- * nothing else ever emits facts for that expression.
217
+ * File extraction only reaches values hanging off exports, and a query
218
+ * can be rooted anywhere, so the queried value gets its own facts.
249
219
  */
250
220
  private seedValue;
251
221
  private lookupObject;
252
222
  /**
253
- * Reaching two different expressions means the rules cannot tell
254
- * which one the value is written as, and the same reasoning applies
255
- * as for the other two questions: ambiguity is nothing. The value
256
- * itself is not an answer, or every identifier would answer with
257
- * itself the moment its chain went nowhere.
223
+ * Two candidates give null, since ambiguity is nothing. The value
224
+ * itself is never a result either, or an identifier whose chain went
225
+ * nowhere would come back as itself.
258
226
  */
259
227
  private lookupWritten;
260
228
  /**
261
- * For each set of files and packages, which of those files reach any
262
- * of those packages through their imports, following project-local
263
- * re-export chains.
229
+ * Which of these files reach any of these packages, following
230
+ * project-local re-export chains. A barrel package that re-exports an
231
+ * SDK defeats a per-file import check and does not defeat this.
264
232
  */
265
233
  filesImportingTransitively(fileSets: ReadonlyArray<FileSetQuery>): ReadonlyArray<ReadonlySet<SourceFile>>;
266
234
  /**
267
- * The one function this value resolves to. Several rules can reach
268
- * the same answer, which is fine, but reaching two different
269
- * functions means the rules cannot tell which one the value is, and
270
- * picking whichever landed in the relation first would make the
271
- * answer depend on the order facts arrived in. Ambiguity is nothing.
235
+ * Two candidates give null. Picking whichever arrived first would make
236
+ * the result depend on the order the facts came in.
272
237
  */
273
238
  private lookup;
274
- /** Run the rules to fixpoint. */
275
239
  private derive;
240
+ private answersFor;
276
241
  /**
277
- * The second column of an answer relation, for the value asked about.
278
- * The database indexes a column the first time somebody looks it up
279
- * and keeps that index as facts arrive, so this is a map hit and the
280
- * answers a query never asks about cost nothing.
242
+ * The trailing pair of a three-column relation, joined so a Set dedupes
243
+ * on both halves. The join uses the database's own tuple encoding,
244
+ * since a specifier or an identifier can contain any separator
245
+ * character.
281
246
  */
282
- private answersFor;
283
- /** The trailing pair of a three-column answer relation, joined. */
284
247
  private answerPairsFor;
285
- /** Emit a file's facts, unless some earlier query already did. */
286
248
  private extractFile;
287
249
  }
288
250
 
289
251
  type FunctionRoot = FunctionDeclaration | FunctionExpression | ArrowFunction | MethodDeclaration;
290
252
  /**
291
- * Public API — returns RawCondition[] with structured: null.
253
+ * Public API: returns RawCondition[] with structured: null.
292
254
  * Use collectAncestorConditionInfos when you need the Expression nodes.
293
255
  */
294
256
  declare function collectAncestorBranches(terminalNode: Node, functionRoot: FunctionRoot): RawCondition[];
@@ -313,6 +275,12 @@ interface DiscoveredUnit {
313
275
  announcedAt?: Node;
314
276
  kind: string;
315
277
  name: string;
278
+ /**
279
+ * "label" when the name was coined for the reader rather than
280
+ * naming a binding other code can call, the way a registration
281
+ * verb identifies its handler. Absent means the name is a binding.
282
+ */
283
+ nameKind?: "binding" | "label";
316
284
  callSite?: ClientCallSite;
317
285
  /** The discovery pattern that produced this unit. Set by discoverUnits. */
318
286
  pattern?: DiscoveryPattern;
@@ -345,13 +313,14 @@ interface DiscoveredUnit {
345
313
  * fields without needing a separate schema provenance.
346
314
  */
347
315
  resolverInfo?: {
348
- typeName: string;
316
+ /** Null when the source never says which type the resolver attaches to. */
317
+ typeName: string | null;
349
318
  fieldName: string;
350
319
  schemaSdl?: string;
351
320
  };
352
321
  /**
353
322
  * Populated by `graphqlHookCall` discovery (GraphQL consumer side).
354
- * Carries the operation shape the adapter uses to build a
323
+ * Has the operation shape the adapter uses to build a
355
324
  * `graphql-operation` binding. `operationName` is absent for
356
325
  * anonymous operations (`gql\`query { ... }\``, no identifier).
357
326
  *
@@ -360,7 +329,7 @@ interface DiscoveredUnit {
360
329
  * alongside the parsed shape so downstream tools can re-parse if
361
330
  * they need additional detail beyond what we surface here. Absent
362
331
  * when the document body wasn't statically readable and the header
363
- * was recovered from `TypedDocumentNode` type arguments — a
332
+ * was recovered from `TypedDocumentNode` type arguments: a
364
333
  * cross-module codegen reference to a document produced by a helper.
365
334
  *
366
335
  * `variables` list the `$name: Type` declarations at the operation
@@ -368,7 +337,7 @@ interface DiscoveredUnit {
368
337
  * pairing layers can match against resolver args. Empty when the
369
338
  * document body wasn't read.
370
339
  *
371
- * `rootFields` is the list of root-level selection names — the
340
+ * `rootFields` is the list of root-level selection names: the
372
341
  * fields the operation actually selects under Query / Mutation /
373
342
  * Subscription. Used by the checker's pairing pass. Empty when the
374
343
  * document body wasn't read.
@@ -395,9 +364,9 @@ interface DiscoveredUnit {
395
364
  };
396
365
  };
397
366
  /**
398
- * Populated by `packageExports` discovery. Carries the package
399
- * identity the adapter uses to build a `function-call` binding
400
- * with `package` + `exportPath` fields — i.e. a provider summary
367
+ * Populated by `packageExports` discovery. Has the package identity
368
+ * the adapter uses to build a `function-call` binding
369
+ * with `package` + `exportPath` fields: i.e. a provider summary
401
370
  * for a publicly-exported library function.
402
371
  */
403
372
  packageExportInfo?: {
@@ -409,7 +378,7 @@ interface DiscoveredUnit {
409
378
  * controllers). The adapter uses it to build a `rest` binding
410
379
  * with `(method, path)` directly, bypassing the
411
380
  * `bindingExtraction` config used by Express / Fastify (which
412
- * extract from `app.get(...)` registration calls — the wrong
381
+ * extract from `app.get(...)` registration calls: the wrong
413
382
  * shape for decorator-driven controllers).
414
383
  */
415
384
  routeInfo?: {
@@ -418,8 +387,8 @@ interface DiscoveredUnit {
418
387
  };
419
388
  /**
420
389
  * Populated by a pack's `discoverUnits` callback for a message-bus
421
- * consumer whose channel the code names (a handler factory whose
422
- * config carries the expected subject). The adapter uses it to build
390
+ * consumer whose channel the code gives (a handler factory whose
391
+ * config includes the expected subject). The adapter uses it to build
423
392
  * a `message-bus` binding directly, which pairs with producers
424
393
  * sending on the same channel.
425
394
  */
@@ -433,16 +402,41 @@ interface DiscoveredUnit {
433
402
  * Metadata merged onto the assembled summary's `metadata` field.
434
403
  * Populated when a pack's `discoverUnits` callback stamps provenance
435
404
  * on the units it returns (the discovery-layer sibling of the
436
- * per-sub-unit `metadata` the `subUnits` hook carries).
405
+ * per-sub-unit `metadata` the `subUnits` hook returns).
437
406
  */
438
407
  metadata?: Record<string, unknown>;
408
+ /**
409
+ * What a discovery handler could not read about the boundary this
410
+ * unit is on. A route whose path is computed is still a route
411
+ * somebody declared, so the unit goes out with no path claimed and
412
+ * this sentence saying which part went unread. It ends up on the
413
+ * summary as an `unreadOutcome` gap.
414
+ */
415
+ unreadBinding?: string;
416
+ }
417
+
418
+ /**
419
+ * What a route's path should be composed with once mount discovery has
420
+ * run: the prefix a router was mounted under, following however many
421
+ * routers it was mounted onto in turn. Built once per extraction by
422
+ * `buildMountPrefixIndex`, over every mount call the active packs'
423
+ * `DiscoveryPattern.mount` config recognizes.
424
+ */
425
+ interface MountPrefixIndex {
426
+ /**
427
+ * The prefix routes on `routerNode` (a registration subject's own
428
+ * creation site, e.g. a `Router()` call) were mounted under. Empty
429
+ * when nothing mounts it, or when a mount call along the way
430
+ * couldn't be resolved to a literal prefix and a concrete router.
431
+ */
432
+ effectivePrefixFor(routerNode: Node): string;
439
433
  }
440
434
 
441
435
  /**
442
436
  * Discover code units in `sourceFile` by running all patterns.
443
437
  * Deduplicates entries with the same function node and kind.
444
438
  */
445
- declare function discoverUnits(sourceFile: SourceFile, patterns: DiscoveryPattern[], resolution?: ResolutionStore): DiscoveredUnit[];
439
+ declare function discoverUnits(sourceFile: SourceFile, patterns: DiscoveryPattern[], resolution?: ResolutionStore, mountPrefixes?: MountPrefixIndex): DiscoveredUnit[];
446
440
 
447
441
  interface TimingReport {
448
442
  totalMs: number;
@@ -455,7 +449,7 @@ interface TimingReport {
455
449
  interface Timer {
456
450
  /** Run `fn`, accumulate wall time under `label`, return its result. */
457
451
  time<T>(label: string, fn: () => T): T;
458
- /** Async variant — same accumulation rule. */
452
+ /** Async variant: same accumulation rule. */
459
453
  timeAsync<T>(label: string, fn: () => Promise<T>): Promise<T>;
460
454
  /** Snapshot of all accumulated phases, ordered by total time descending. */
461
455
  report(): TimingReport;
@@ -468,82 +462,42 @@ interface Timer {
468
462
  */
469
463
  type DescentBarriers = ReadonlySet<Node>;
470
464
 
471
- declare function extractCodeStructure(unit: DiscoveredUnit, pack: PatternPack, invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers): RawCodeStructure;
465
+ /**
466
+ * The whole extraction, from a tsconfig to a set of behavioral
467
+ * summaries. `createTypeScriptAdapter` is what a caller uses;
468
+ * everything else here is a stage of the run it drives.
469
+ *
470
+ * A run loads the project, asks each pack which files it cares about,
471
+ * discovers units in those files, walks each unit's body for terminals
472
+ * and effects, works out the boundary the unit is on, and assembles a
473
+ * summary. Several passes then run over the finished set: mount
474
+ * prefixes, module-load behavior, the reachable closure, rethrow
475
+ * sources, wrapper expansion, sub-units, and naming. The adapter owns
476
+ * what TypeScript and ECMAScript define; what a call means to a
477
+ * framework or a runtime belongs to a pack.
478
+ */
479
+
480
+ declare function extractCodeStructure(unit: DiscoveredUnit, pack: PatternPack, invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers, resolveWrittenValue?: (value: Node) => Node | null): RawCodeStructure;
472
481
  interface TypeScriptAdapterConfig {
473
482
  tsConfigFilePath?: string;
474
- /**
475
- * What the project being read calls itself, so a summary can carry a
476
- * name nothing else in a run shares.
477
- *
478
- * Two services in one repository both hold `src/handlers.ts`, and a
479
- * summary named after the file and the export is the same on both
480
- * sides until something says which project it came from. Worked out
481
- * from the project's own package.json when nobody says.
482
- */
483
+ /** Separates two services in one repository whose file names match. */
483
484
  workspace?: string;
484
485
  project?: Project;
485
486
  frameworks: PatternPack[];
486
487
  extractorOptions?: ExtractorOptions;
487
- /**
488
- * Include library summaries for every function reachable through a
489
- * static call edge from a pack-discovered unit. Defaults to `true` —
490
- * internal orchestrators, helpers, and utilities that frameworks
491
- * don't recognise become `library`-kind summaries with
492
- * `recognition: "reachable"` so readers can see their behaviour.
493
- * Set to `false` for pack-only extraction (smaller output; only the
494
- * units packs explicitly claim).
495
- */
488
+ /** A `library` summary per function reachable by a static call edge. */
496
489
  includeReachable?: boolean;
497
- /**
498
- * Called once at the end of each `extractAll` / `extractFromFiles`
499
- * with the wall-clock breakdown of the major extraction phases.
500
- * Always-on instrumentation cost is negligible (millisecond-scale
501
- * `performance.now()` calls); the callback is the opt-in for
502
- * surfacing it. CLI uses this to render the per-extract summary
503
- * line and the `--timing` breakdown.
504
- */
505
490
  onTiming?: (report: TimingReport) => void;
506
- /**
507
- * Called once per extract with the cache hit/miss decision and the
508
- * partial-reuse diagnostic — counts of cached summaries that fine-
509
- * grained invalidation would have salvaged on a coarse miss.
510
- * Phase 4a uses this to validate the dep-tracking model before
511
- * activating partial-reuse.
512
- */
513
491
  onCacheDiagnostic?: (diagnostic: CacheDiagnostic) => void;
514
- /**
515
- * Called once per `extractAll` with the funnel: files in the
516
- * tsconfig, files walked, and per-pack gate / candidate / unit /
517
- * summary counts. The CLI renders it when a run produces nothing,
518
- * so "wrote 0 summaries" can say which stage the count died at
519
- * instead of leaving the user to guess.
520
- *
521
- * Not called on a cache hit, where no stage ran.
522
- */
492
+ /** Not called on a cache hit, where no stage ran. */
523
493
  onExtractionReport?: (report: ExtractionReport) => void;
524
- /**
525
- * On-disk cache directory for the extraction cache. Pass an absolute
526
- * path; defaults to `.suss/cache/` next to the tsconfig, and to no
527
- * cache at all when no tsconfig path is supplied. Pass `null` to turn
528
- * caching off. A warm run with nothing changed costs one stat per
529
- * file and returns the previous run's summaries whole.
530
- *
531
- * A process that loaded the adapter from source gets no cache
532
- * whatever this says, because a key made there cannot tell one build
533
- * of the adapter from another.
534
- */
494
+ /** Absolute. `.suss/cache/` beside the tsconfig; `null` turns it off. */
535
495
  cacheDir?: string | null;
536
496
  }
537
- interface TypeScriptAdapter {
538
- project: Project;
539
- /**
540
- * Extract summaries from a specific list of files. Returns a
541
- * Promise so the implementation can do concurrent I/O during
542
- * the pre-filter / lazy-load phases without bottlenecking on
543
- * sync `fs.readFileSync`.
544
- */
497
+ interface TypeScriptAdapter extends LanguageAdapter {
498
+ /** Kept off `LanguageAdapter`, so a caller needing ts-morph asks for it. */
499
+ readonly tsProject: Project;
545
500
  extractFromFiles(filePaths: string[]): Promise<BehavioralSummary[]>;
546
- /** Extract summaries from every (non-declaration) file the Project knows about. */
547
501
  extractAll(): Promise<BehavioralSummary[]>;
548
502
  }
549
503
  declare function createTypeScriptAdapter(config: TypeScriptAdapterConfig): TypeScriptAdapter;
@@ -556,19 +510,18 @@ interface FoundTerminal {
556
510
  * of an arrow that returns without writing `return`. Absent when the
557
511
  * terminal is not a return at all, as a throw is not.
558
512
  *
559
- * The matcher is the only thing that knows, because `node` is
560
- * wherever the matcher stopped looking, which differs by matcher.
561
- * Anything downstream that guessed instead got it wrong.
513
+ * Only the matcher knows this, because `node` is wherever that
514
+ * matcher stopped looking, which differs from matcher to matcher.
515
+ * Anything downstream that tried to guess got it wrong.
562
516
  */
563
517
  source?: Node;
564
518
  /**
565
- * A test that also has to hold for this terminal to be the one that
519
+ * A test that also has to be true for this terminal to be the one that
566
520
  * fires, on top of whatever the path to it required.
567
521
  *
568
- * One call site answers with more than one status when the status is
569
- * written as a choice, and the arms are told apart by nothing else:
570
- * they share a node, so the path engine gives them the same
571
- * conditions.
522
+ * One call site produces more than one status when the status is
523
+ * written as a choice, and nothing else tells the arms apart: they
524
+ * share a node, so the path engine gives them the same conditions.
572
525
  */
573
526
  whenAlso?: RawCondition;
574
527
  }
@@ -581,24 +534,20 @@ interface FoundTerminal {
581
534
  * terminal output is produced inside a Promise executor or `.then`
582
535
  * callback (`res.json(data)` on the unit's own `res` parameter) is still
583
536
  * discovered. Inside a nested function only escaping terminals are
584
- * matched — see `NESTED_ESCAPING_MATCH_TYPES`.
537
+ * matched: see `NESTED_ESCAPING_MATCH_TYPES`.
585
538
  */
586
539
  declare function findTerminals(func: FunctionRoot, patterns: TerminalPattern[], barriers?: DescentBarriers): FoundTerminal[];
587
540
 
588
541
  /**
589
- * Extract all raw branches from a function, composing:
590
- * 1. findTerminals, to locate terminal nodes
591
- * 2. computePathConditions, for the conditions on each path (CFG
592
- * enumeration; declined shapes degrade to sound
593
- * under-specification)
594
- * 3. parseConditionExpression, turning an Expression into a Predicate
595
- * 4. extractInvocationEffects, for bare expression-statement calls,
596
- * attached to the default branch so handler and useEffect bodies
597
- * carry their side-effect set
542
+ * Turns one function into the list of branches a summary is built from.
598
543
  *
599
- * `isDefault` is true when the branch has no conditions, or all
600
- * conditions come from early returns or throws.
544
+ * Terminal matching, path-condition enumeration, predicate parsing and
545
+ * effect capture each live in their own module. This one runs them over
546
+ * the same function and lines the results up: every terminal gets the
547
+ * conditions on each path that reaches it, and the calls in the body get
548
+ * attached to the branch they fire on.
601
549
  */
550
+
602
551
  /**
603
552
  * The branches a function produces, alongside the terminals they came
604
553
  * from. A caller that wants to know which returns went unclaimed needs
@@ -609,7 +558,14 @@ interface RawBranchResult {
609
558
  branches: RawBranch[];
610
559
  terminals: FoundTerminal[];
611
560
  }
612
- declare function extractRawBranches(func: FunctionRoot, terminalPatterns: TerminalPattern[], invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers): RawBranchResult;
561
+ /**
562
+ * Every branch a function produces: one per path that reaches a
563
+ * terminal, with the conditions on that path and the effects that fire
564
+ * along it. A branch is `isDefault` when it has no conditions at all,
565
+ * or when every condition on it came from an early return or throw,
566
+ * since those are the paths that run when no guard fired.
567
+ */
568
+ declare function extractRawBranches(func: FunctionRoot, terminalPatterns: TerminalPattern[], invocationRecognizers?: InvocationRecognizer[], accessRecognizers?: AccessRecognizer[], barriers?: DescentBarriers, resolveWrittenValue?: (value: Node) => Node | null): RawBranchResult;
613
569
 
614
570
  /**
615
571
  * A Project covering `rootDir`, for a codebase with no tsconfig to read.
@@ -623,14 +579,30 @@ declare function createProjectWithoutTsconfig(rootDir: string): {
623
579
  declare function findNearestTsconfig(startDir: string): string | null;
624
580
 
625
581
  /**
626
- * A call a project names as its own dispatcher.
582
+ * Reads a method call whose receiver has a type the project's config
583
+ * points at.
627
584
  *
628
- * `receiver` is the type name, not the variable: a service holds its
629
- * dispatcher in a field, a closure, or a constructor parameter, and
630
- * only the type is stable across those.
585
+ * A pack recognizes library calls by their command class, so it only
586
+ * sees the libraries it was built with. A project that sends every
587
+ * message through a wrapper of its own writes no such call, and the pack
588
+ * sees nothing. This lets the project describe the wrapper instead:
589
+ * which module declares it, which type the receiver has, which method
590
+ * does the sending, and which arguments the subject and body are in.
591
+ *
592
+ * A call on a typed receiver is something TypeScript already tells us,
593
+ * so reading it belongs to the adapter. What the call means on the bus
594
+ * is the pack's judgment, so the pack builds the effect from this.
595
+ */
596
+
597
+ /**
598
+ * A call the project has declared to be its own dispatcher.
599
+ *
600
+ * `receiver` is the type name rather than the variable: a service keeps
601
+ * its dispatcher in a field, a closure or a constructor parameter, and
602
+ * the type is the only thing stable across all three.
631
603
  */
632
604
  interface ConfiguredCallSpec {
633
- /** Module the receiver's type is declared in. */
605
+ /** Module that declares the receiver's type. */
634
606
  module: string;
635
607
  /** Type name of the receiver, as exported from that module. */
636
608
  receiver: string;
@@ -639,14 +611,14 @@ interface ConfiguredCallSpec {
639
611
  /** Argument index carrying the subject. */
640
612
  subjectArg: number;
641
613
  /**
642
- * Argument index carrying the message body. Left out when the
643
- * method takes no single body argument (a batch method takes a
644
- * list of entries), and then the read carries no body.
614
+ * Which argument the message body is. Left out when the method has no
615
+ * single body argument, as a batch method taking a list of entries
616
+ * does, and then no body is reported.
645
617
  */
646
618
  bodyArg?: number;
647
619
  }
648
620
  interface ConfiguredCallRead {
649
- /** The subject the call names, always a string the source states. */
621
+ /** The subject the call sends to, always a literal string. */
650
622
  subject: string;
651
623
  /** The body argument's extracted shape, or null when none applies. */
652
624
  body: EffectArg | null;
@@ -659,13 +631,12 @@ interface ConfiguredCallContext {
659
631
  extractArgs(): EffectArg[];
660
632
  }
661
633
  /**
662
- * Read `call` as the configured send, or answer null.
634
+ * Read `call` as the configured send, or return null.
663
635
  *
664
- * A subject that is not a string the source states (a variable built
665
- * at runtime, a template with substitutions, a computed key) answers
666
- * null, and the pack emits nothing. Naming a channel we cannot read
667
- * would pair a producer against the wrong consumer, which is worse
668
- * than not seeing the producer at all.
636
+ * A subject that is not a literal string (a value built at runtime, a
637
+ * template with substitutions, a computed key) returns null and the pack
638
+ * emits nothing. Guessing a channel we cannot read would pair a producer
639
+ * with the wrong consumer, which is worse than missing the producer.
669
640
  */
670
641
  declare function readConfiguredCall(call: CallExpression, ctx: ConfiguredCallContext, spec: ConfiguredCallSpec): ConfiguredCallRead | null;
671
642
 
@@ -699,11 +670,11 @@ interface TsDiscoveryContext {
699
670
  getFilePath(sourceFile: SourceFile): string;
700
671
  /**
701
672
  * Yield every export from the source file whose declaration is a
702
- * function — function declarations, arrow / function-expression
673
+ * function: function declarations, arrow / function-expression
703
674
  * variable initializers, and re-exports of either. Skips
704
675
  * non-function exports (constants, classes, types).
705
676
  *
706
- * Each entry carries the exported name (the binding the consumer
677
+ * Each entry has the exported name (the binding the consumer
707
678
  * uses), the function root, and whether the export is the file's
708
679
  * default export. `default` is included; the pack decides whether
709
680
  * to handle it (typically the data-driven `namedExport(["default"])`
@@ -715,24 +686,24 @@ interface TsDiscoveryContext {
715
686
  isDefault: boolean;
716
687
  }>;
717
688
  /**
718
- * For an export the project builds by calling a factory, the string a
719
- * property of that call's config object holds:
689
+ * For an export the project builds by calling a factory, the string
690
+ * under a property of that call's config object:
720
691
  * `export const handler = makeWidgetHandler({ subject: "a.b" }, ...)`
721
- * with `{ property: "subject" }` answers `"a.b"`.
692
+ * with `{ property: "subject" }` gives back `"a.b"`.
722
693
  *
723
- * Which function was called and which argument carried the config are
724
- * questions the caller does not have to answer. Every object argument
725
- * is read. Naming the callee or fixing the argument position narrows
726
- * that, for a project whose factories would otherwise collide.
694
+ * The caller does not have to say which function was called or which
695
+ * argument the config was in. Every object argument is read. Giving the
696
+ * callee or fixing the argument position narrows that down, for a
697
+ * project whose factories would otherwise collide.
727
698
  *
728
- * Two arguments holding the property under different values answers
699
+ * Two arguments with the property set to different values give back
729
700
  * null, since nothing says which one was meant.
730
701
  *
731
702
  * The config argument is usually an object literal at the call site;
732
703
  * a variable or import is followed to the literal it resolves to.
733
704
  * `as const` and parentheses around the property value are peeled.
734
705
  * Anything but a string literal underneath (a computed subject, a
735
- * template, a call) answers null — the caller attaches nothing
706
+ * template, a call) gives back null, and the caller attaches nothing
736
707
  * rather than guessing.
737
708
  */
738
709
  exportedCallConfigString(sourceFile: SourceFile, exportName: string, spec: {
@@ -744,30 +715,61 @@ interface TsDiscoveryContext {
744
715
  * Walk a function's body for return statements whose value is a
745
716
  * JSX element / fragment / self-closing tag. Returns true on the
746
717
  * first match; false otherwise. Skips into nested function bodies
747
- * — nested arrow returning JSX is its own component, not part of
718
+ *: nested arrow returning JSX is its own component, not part of
748
719
  * this function's output.
749
720
  *
750
- * Concise-arrow bodies (`() => <X/>`) are handled — the body IS
721
+ * Concise-arrow bodies (`() => <X/>`) are handled: the body IS
751
722
  * the implicit return.
752
723
  */
753
724
  hasJsxReturn(func: FunctionRoot): boolean;
754
725
  }
755
726
  declare function createTsDiscoveryContext(resolution?: ResolutionStore): TsDiscoveryContext;
756
727
 
757
- /** One thing that looks wrong, in the same shape the dogfood invariants report. */
728
+ type ExportedDeclarationMap = ReturnType<SourceFile["getExportedDeclarations"]>;
729
+ /**
730
+ * What a module exports, by exported name. Cached, because ts-morph
731
+ * rebuilds the map from scratch on every `getExportedDeclarations` call
732
+ * and callee resolution asks the same file the same thing once per
733
+ * import site.
734
+ */
735
+ declare function exportedDeclarationsOf(sourceFile: SourceFile): ExportedDeclarationMap;
736
+ /**
737
+ * `Symbol.getAliasedSymbol`, with the re-export chains warmed first so
738
+ * that an import sitting at the top of a deep chain still resolves
739
+ * instead of running the call stack out.
740
+ */
741
+ declare function resolveAliasedSymbol(symbol: Symbol): Symbol | undefined;
742
+
743
+ /**
744
+ * Checks that tell you a pack is probably not working.
745
+ *
746
+ * The funnel shows where a run's counts dropped to zero. These checks
747
+ * ask something narrower of each pack on its own: did this pack lose
748
+ * everything at some stage, when the stage before it had something?
749
+ *
750
+ * A pack that finds nothing in a codebase that does not use its library
751
+ * is working correctly, so a count of zero on its own is never the
752
+ * signal. What makes zero a signal is the count before it. A pack whose
753
+ * import gate picked forty files and whose discovery then found no unit
754
+ * in any of them said "look here" and failed to look. Every later stage
755
+ * works the same way, which is why these checks are one comparison run
756
+ * over a list of pairs rather than one check written per stage.
757
+ */
758
+
759
+ /** One thing that looks wrong, reported the way the dogfood invariants are. */
758
760
  interface HealthViolation {
759
761
  label: string;
760
762
  detail: string;
761
763
  }
762
764
  interface HealthCheck {
763
- /** The property, as something that either holds or does not. */
765
+ /** The property being checked, as something either true or false. */
764
766
  name: string;
765
767
  /**
766
768
  * The heading printed when this check finds something.
767
769
  *
768
- * The property reads as an assertion, so printing `name` above the
769
- * violations of it says "no pack collides with itself" and then lists
770
- * a pack colliding with itself.
770
+ * `name` is written as an assertion, so printing it above the
771
+ * violations would say "no pack collides with itself" and then list a
772
+ * pack colliding with itself.
771
773
  */
772
774
  whenBroken: string;
773
775
  /**
@@ -775,10 +777,10 @@ interface HealthCheck {
775
777
  *
776
778
  * A `run` check found something about the code in front of it, and
777
779
  * the person who started the run can do something about it: drop a
778
- * pack, install a dependency, open an issue with the file that
779
- * broke. A `pack` check found something about how a pack was built,
780
- * which only whoever ships that pack can fix. Printing the second
781
- * kind on every run would teach people to skim past the first.
780
+ * pack, install a dependency, open an issue with the file that broke.
781
+ * A `pack` check found something about how a pack was built, which
782
+ * only whoever ships that pack can fix. Printing the second kind on
783
+ * every run would teach people to skim past the first.
782
784
  */
783
785
  audience: "run" | "pack";
784
786
  violations: HealthViolation[];
@@ -791,10 +793,10 @@ declare function evaluatePackHealth(report: ExtractionReport): HealthCheck[];
791
793
  /**
792
794
  * The health checks that fired, as lines for a terminal.
793
795
  *
794
- * `audiences` is who the caller is printing for, and it is required
795
- * because there is no answer that suits every caller: a CLI run prints
796
- * what the person who started it can act on, and a run whose reader is
797
- * a pack author wants both.
796
+ * `audiences` is who the caller is printing for. It is required because
797
+ * no single choice suits every caller: a CLI run prints what the person
798
+ * who started it can act on, while a run a pack author is reading wants
799
+ * both kinds.
798
800
  */
799
801
  declare function formatPackHealth(checks: ReadonlyArray<HealthCheck>, audiences: ReadonlyArray<HealthCheck["audience"]>): string;
800
802
 
@@ -813,7 +815,7 @@ declare function parseConditionExpression(expr: Expression, depth?: number): Pre
813
815
  declare function isImportedFrom(identifier: Node, expectedModule: string): boolean;
814
816
 
815
817
  interface BranchFieldAccesses {
816
- /** Terminal location (start, end lines) — used to match back to transitions */
818
+ /** Terminal location (start, end lines): used to match back to transitions */
817
819
  terminalLocation: {
818
820
  start: number;
819
821
  end: number;
@@ -828,7 +830,7 @@ interface BranchFieldAccesses {
828
830
  * @param callExpr The API call expression (fetch(), client.getUser())
829
831
  * @param func The enclosing function
830
832
  * @param branchLocations Terminal locations from the extracted branches
831
- * @param responseSemantics Optional response property mappings from the pack —
833
+ * @param responseSemantics Optional response property mappings from the pack ,
832
834
  * used to filter non-body properties instead of a hardcoded list
833
835
  */
834
836
  declare function collectClientFieldAccesses(callExpr: CallExpression, func: Node, branchLocations: Array<{
@@ -838,7 +840,7 @@ declare function collectClientFieldAccesses(callExpr: CallExpression, func: Node
838
840
 
839
841
  /**
840
842
  * Resolve a ts-morph Expression node to a structured ValueRef.
841
- * Uses only expr.getSymbol()?.getDeclarations()[0] for symbol lookup —
843
+ * Uses only expr.getSymbol()?.getDeclarations()[0] for symbol lookup ,
842
844
  * never findReferencesAsNodes() which is project-wide and quadratic.
843
845
  *
844
846
  * Follows intermediate variable assignments (const data = result.body)
@@ -854,7 +856,7 @@ declare function resolveSubject(expr: Expression, depth?: number): ValueRef;
854
856
  * bodies and source text.
855
857
  *
856
858
  * All walks are scoped to the parent function and deliberately do NOT
857
- * recurse into nested function bodies — sub-units of nested functions
859
+ * recurse into nested function bodies, sub-units of nested functions
858
860
  * belong to those functions' summaries, not to the enclosing parent.
859
861
  */
860
862
  interface TsSubUnitContext {
@@ -867,7 +869,7 @@ interface TsSubUnitContext {
867
869
  findJsxAttributes(parent: FunctionRoot): TsJsxAttributeLocation[];
868
870
  /**
869
871
  * Yield every call expression matching `name` (bare identifier
870
- * callee only — e.g. `useEffect(...)`, not `React.useEffect(...)`).
872
+ * callee only: e.g. `useEffect(...)`, not `React.useEffect(...)`).
871
873
  * Nested function bodies are skipped.
872
874
  */
873
875
  findCallExpressionsByName(parent: FunctionRoot, name: string): CallExpression[];
@@ -912,14 +914,14 @@ interface TsJsxAttributeLocation {
912
914
  }
913
915
  declare function createTsSubUnitContext(): TsSubUnitContext;
914
916
 
915
- declare const ADAPTER_VERSION = "0.2.0";
917
+ declare const ADAPTER_VERSION = "0.2.1";
916
918
  /**
917
919
  * Content hash of the given files, in the order they arrive. Empty when
918
920
  * the list is empty or a file cannot be read, so a caller that could not
919
- * place a file gets the same "no stamp" answer as a run from source
921
+ * locate a file gets the same "no stamp" result as a run from source
920
922
  * rather than a hash of a shorter list.
921
923
  *
922
- * The caller resolves the paths, because who a specifier resolves to
924
+ * The caller resolves the paths, because where a specifier resolves to
923
925
  * depends on which package is asking.
924
926
  */
925
927
  declare function computeContentHash(paths: readonly string[]): string;
@@ -934,13 +936,13 @@ declare function computeContentHash(paths: readonly string[]): string;
934
936
  * came from, so folding the config and the code into the stamp is the
935
937
  * loader's job.
936
938
  *
937
- * A source run says so in the digest rather than leaving the adapter
938
- * out of it. Nothing writes under that digest today, and a key that
939
- * names the mode cannot be mistaken for a key that named the code.
939
+ * A source run says as much in the digest rather than leaving the
940
+ * adapter out of it. Nothing writes under that digest today, and a key
941
+ * that says "source" cannot be mistaken for one built from the code.
940
942
  */
941
943
  declare function computeAdapterPacksDigest(packVersions: ReadonlyArray<{
942
944
  name: string;
943
945
  version?: string;
944
946
  }>): string;
945
947
 
946
- export { ADAPTER_VERSION, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type PackFunnel, type Timer, type TimingReport, type TsDiscoveryContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, isImportedFrom, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, resolveSubject };
948
+ export { ADAPTER_VERSION, type CacheDiagnostic, type CacheLookup, type ClientCallSite, type ConfiguredCallContext, type ConfiguredCallRead, type ConfiguredCallSpec, type ContractReadResult, type DiscoveredUnit, type EmptyStage, type ExtractionReport, type FoundTerminal, type FunctionRoot, type HealthCheck, type HealthViolation, type PackFunnel, ResolutionStore, type Timer, type TimingReport, type TsDiscoveryContext, type TsJsxAttributeLocation, type TsSubUnitContext, type TypeScriptAdapter, type TypeScriptAdapterConfig, collectAncestorBranches, collectClientFieldAccesses, computeAdapterPacksDigest, computeContentHash, createProjectWithoutTsconfig, createTsDiscoveryContext, createTsSubUnitContext, createTypeScriptAdapter, discoverUnits, evaluatePackHealth, exportedDeclarationsOf, extractCodeStructure, extractRawBranches, findNearestTsconfig, findTerminals, formatPackHealth, isImportedFrom, parseConditionExpression, readConfiguredCall, readContract, readContractForClientCall, resolveAliasedSymbol, resolveSubject };