@suss/extractor 0.1.0 → 0.1.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.
package/dist/index.cjs CHANGED
@@ -95,7 +95,8 @@ function assembleSummary(raw, options = DEFAULT_OPTIONS) {
95
95
  identity: {
96
96
  name: raw.identity.name,
97
97
  exportPath: raw.identity.exportPath,
98
- boundaryBinding: raw.boundaryBinding ?? null
98
+ boundaryBinding: raw.boundaryBinding ?? null,
99
+ ...raw.deployableUnit !== void 0 ? { deployableUnit: raw.deployableUnit } : {}
99
100
  },
100
101
  inputs,
101
102
  transitions,
@@ -161,6 +162,23 @@ function detectGaps(raw, transitions, options) {
161
162
  description: count === 1 ? "One return in this function matches none of the terminal shapes this pack looks for, so what it produces is not described here" : `${count} returns in this function match none of the terminal shapes this pack looks for, so what they produce is not described here`
162
163
  });
163
164
  }
165
+ if (raw.unreadBinding !== void 0) {
166
+ gaps.push({
167
+ type: "unreadOutcome",
168
+ conditions: [],
169
+ consequence: "unknown",
170
+ description: raw.unreadBinding
171
+ });
172
+ }
173
+ const unreadBody = describeUnreadBody(raw);
174
+ if (unreadBody !== null) {
175
+ gaps.push({
176
+ type: "unreadOutcome",
177
+ conditions: [],
178
+ consequence: "unknown",
179
+ description: unreadBody
180
+ });
181
+ }
164
182
  if (raw.declaredContract) {
165
183
  const producedStatuses = new Set(
166
184
  transitions.flatMap((t) => {
@@ -200,10 +218,34 @@ function detectGaps(raw, transitions, options) {
200
218
  }
201
219
  return gaps;
202
220
  }
221
+ function bodyWentUnread(raw) {
222
+ if (raw.bodyContent === "absent" || raw.bodyContent === "elsewhere") {
223
+ return true;
224
+ }
225
+ return raw.bodyContent === "statements" && raw.branches.length === 0;
226
+ }
227
+ function describeUnreadBody(raw) {
228
+ if (!bodyWentUnread(raw)) {
229
+ return null;
230
+ }
231
+ if (raw.bodyContent === "absent") {
232
+ return "This unit is a declaration with no body behind it, so nothing about what it does was read here";
233
+ }
234
+ if (raw.bodyContent === "elsewhere") {
235
+ return "The handler on this boundary comes from outside the code that registers it, so nothing about what it does was read here";
236
+ }
237
+ if ((raw.unmatchedReturns ?? 0) > 0) {
238
+ return null;
239
+ }
240
+ return "Nothing this unit's body does matches a shape this pack looks for, so what it does is not described here";
241
+ }
203
242
  function assessConfidence(raw) {
204
243
  if ((raw.unmatchedReturns ?? 0) > 0) {
205
244
  return { source: "inferred_static", level: "low" };
206
245
  }
246
+ if (bodyWentUnread(raw)) {
247
+ return { source: "inferred_static", level: "low" };
248
+ }
207
249
  let total = 0;
208
250
  let opaque = 0;
209
251
  for (const branch of raw.branches) {
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Effect, MessageBusSemantics, Predicate, TypeShape, RenderNode, CodeUnitKind, BoundaryBinding, BehavioralSummary, ConfidenceInfo, Transition, Gap, Input, Output } from '@suss/behavioral-ir';
1
+ import { Effect, MessageBusSemantics, DeployableUnit, Predicate, TypeShape, RenderNode, CodeUnitKind, BoundaryBinding, BehavioralSummary, ConfidenceInfo, Transition, Gap, Input, Output } from '@suss/behavioral-ir';
2
2
 
3
3
  type DiscoveryMatch = {
4
4
  type: "namedExport";
@@ -211,30 +211,42 @@ type DiscoveryMatch = {
211
211
  * The adapter populates `DiscoveredUnit.resolverInfo` so the
212
212
  * produced binding carries `graphql-resolver(typeName, fieldName)`.
213
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.
214
+ * (`@Resolver(() => User)` → `"User"`), and from
215
+ * `methodDecoratorTypeMap` when the class decorator has no
216
+ * argument. `fieldName` reads the method decorator's `{ name }`
217
+ * option override when present, otherwise the method name.
219
218
  */
220
219
  type: "decoratedMethod";
221
220
  /**
222
221
  * The module a pack-recognised decorator must be imported from
223
222
  * 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.
223
+ * decorators in their own re-exports, composing the framework's
224
+ * decorator with extra metadata. When that happens, the
225
+ * import-module gate would miss them; pass an array of accepted
226
+ * modules and any one match suffices.
229
227
  */
230
228
  importModule: string | string[];
231
229
  /**
232
230
  * Class decorator names to recognise. The first entry that
233
231
  * appears on a class wins for typeName extraction (the rest are
234
- * fallbacks for codebases with multiple wrapper styles).
232
+ * fallbacks for codebases with multiple wrapper styles). A pack
233
+ * ships only what its own framework declares here and takes a
234
+ * project's wrappers through its options.
235
235
  */
236
236
  classDecorators: string[];
237
237
  methodDecorators: string[];
238
+ /**
239
+ * Method decorator name → the type its field hangs off when the
240
+ * class decorator names none. NestJS puts `@Query` on the root
241
+ * `Query` type and `@Mutation` on `Mutation` whatever the class
242
+ * says, so those two answer for themselves.
243
+ *
244
+ * A decorator the map leaves out has no answer of its own, and a
245
+ * class that names no type leaves the field's owner unread. The
246
+ * binding then names no type and pairs with nothing, rather than
247
+ * claiming a field the schema does not have.
248
+ */
249
+ methodDecoratorTypeMap: Record<string, string>;
238
250
  } | {
239
251
  /**
240
252
  * NestJS-style REST controller discovery. Class decorated with
@@ -248,10 +260,9 @@ type DiscoveryMatch = {
248
260
  * Same wrapper-decorator tolerance as `decoratedMethod`: the
249
261
  * import-module gate fires on at least one method-route
250
262
  * 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.
263
+ * match by name only, so a project-internal wrapper composing
264
+ * the framework's decorator matches once the project names it
265
+ * in the pack's options.
255
266
  *
256
267
  * The adapter populates `DiscoveredUnit.routeInfo` so the
257
268
  * produced binding carries `rest(method, path)`.
@@ -685,14 +696,18 @@ interface TransparentWrapper {
685
696
  interface PatternPack {
686
697
  name: string;
687
698
  /**
688
- * Pack version stamp — feeds the cache invalidation key. Bump on
699
+ * Pack version stamp, which feeds the cache invalidation key. Bump on
689
700
  * any change that affects discovered units / extracted summaries.
690
- * Format is opaque to the adapter; semver is the obvious choice
691
- * but a content hash, build SHA, or monotonic integer works too.
692
- * Optional — packs that omit it use the literal string `"unset"`,
693
- * which means cache entries from one process to the next aren't
694
- * meaningfully versioned (suitable for development; production
695
- * packs should declare a version).
701
+ * Format is opaque to the adapter, so semver or a content hash both
702
+ * work.
703
+ *
704
+ * Optional, because whoever loads the pack knows more about it than
705
+ * the pack does. The CLI folds a hash of the file it loaded and of
706
+ * the config it passed into this stamp, so a pack run through the CLI
707
+ * invalidates on an edit whether or not it declares a version. A host
708
+ * that builds packs some other way carries that responsibility
709
+ * itself. A pack with nothing to stamp reads as `"unset"`, and warm
710
+ * caches will answer for code that has since changed.
696
711
  */
697
712
  version?: string;
698
713
  languages: string[];
@@ -981,6 +996,8 @@ interface DiscoveredCustomUnit {
981
996
  messageBus: MessageBusSemantics["messageBus"];
982
997
  channel: string;
983
998
  };
999
+ /** The thing that gets deployed and runs this unit, when known. */
1000
+ deployableUnit?: DeployableUnit;
984
1001
  /**
985
1002
  * Metadata merged onto the resulting summary's `metadata` field.
986
1003
  */
@@ -1264,6 +1281,23 @@ interface RawDeclaredContract {
1264
1281
  */
1265
1282
  provenance?: "derived" | "independent";
1266
1283
  }
1284
+ /**
1285
+ * What sits where a unit's body should be.
1286
+ *
1287
+ * - "absent": there is no body behind this declaration (an overload
1288
+ * signature, an ambient declaration, an abstract method), so the
1289
+ * pack read nothing about what the unit does.
1290
+ * - "empty": a body with nothing in it. A summary that says nothing
1291
+ * about it has described it completely.
1292
+ * - "statements": a body with work in it, including a concise
1293
+ * arrow's expression. A summary that says nothing about it has
1294
+ * described none of it.
1295
+ * - "elsewhere": the unit's body is not in what this run read. A
1296
+ * route registered with a handler the caller supplies announces a
1297
+ * boundary and names no function to look in, and the boundary is
1298
+ * worth reporting with nothing behind it.
1299
+ */
1300
+ type BodyContent = "absent" | "empty" | "statements" | "elsewhere";
1267
1301
  interface RawCodeStructure {
1268
1302
  identity: {
1269
1303
  name: string;
@@ -1283,6 +1317,8 @@ interface RawCodeStructure {
1283
1317
  * helpers, pure utilities, and anything the adapter can't place.
1284
1318
  */
1285
1319
  boundaryBinding: BoundaryBinding | null;
1320
+ /** The thing that gets deployed and runs this code, when known. */
1321
+ deployableUnit?: DeployableUnit;
1286
1322
  parameters: RawParameter[];
1287
1323
  branches: RawBranch[];
1288
1324
  /**
@@ -1292,6 +1328,14 @@ interface RawCodeStructure {
1292
1328
  * saying it does nothing.
1293
1329
  */
1294
1330
  unmatchedReturns?: number;
1331
+ /**
1332
+ * What the adapter found where the unit's body should be. A summary
1333
+ * with nothing in it means one of two different things, and only the
1334
+ * adapter can say which: a body nobody could read, or a body with
1335
+ * nothing in it to read. Adapters that do not say leave this unset,
1336
+ * and everything downstream treats the summary as it did before.
1337
+ */
1338
+ bodyContent?: BodyContent;
1295
1339
  dependencyCalls: RawDependencyCall[];
1296
1340
  declaredContract: RawDeclaredContract | null;
1297
1341
  /**
@@ -1358,6 +1402,17 @@ interface RawCodeStructure {
1358
1402
  reference: string;
1359
1403
  reason: string;
1360
1404
  };
1405
+ /**
1406
+ * Set when the source does not state part of the boundary this unit
1407
+ * sits on. The binding still goes out with that part left empty, so
1408
+ * the unit pairs with nothing rather than with whatever a guess named,
1409
+ * and this sentence tells a reader which part is missing and why.
1410
+ *
1411
+ * A statement about the reading rather than about the code, so it
1412
+ * lands as an `unreadOutcome` gap and no checker holds it against the
1413
+ * unit.
1414
+ */
1415
+ unreadBinding?: string;
1361
1416
  }
1362
1417
  interface ExtractorOptions {
1363
1418
  gapHandling: "strict" | "permissive" | "silent";
@@ -1370,4 +1425,4 @@ declare function terminalToOutput(terminal: RawTerminal): Output;
1370
1425
  declare function effectToIR(effect: RawEffect): Effect;
1371
1426
  declare function paramToInput(param: RawParameter): Input;
1372
1427
 
1373
- 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, type TransparentWrapper, assembleSummary, assessConfidence, detectGaps, effectToIR, httpRouteDiscovery, makeTransitionId, paramToInput, terminalToOutput };
1428
+ export { type AccessRecognizer, type BindingExtraction, type BodyContent, type ContractPattern, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type ExtractorOptions, type InputMappingPattern, type InvocationRecognizer, type PatternPack, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type ResponsePropertyMapping, type ResponsePropertyMeaning, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type TransparentWrapper, assembleSummary, assessConfidence, detectGaps, effectToIR, httpRouteDiscovery, makeTransitionId, paramToInput, terminalToOutput };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Effect, MessageBusSemantics, Predicate, TypeShape, RenderNode, CodeUnitKind, BoundaryBinding, BehavioralSummary, ConfidenceInfo, Transition, Gap, Input, Output } from '@suss/behavioral-ir';
1
+ import { Effect, MessageBusSemantics, DeployableUnit, Predicate, TypeShape, RenderNode, CodeUnitKind, BoundaryBinding, BehavioralSummary, ConfidenceInfo, Transition, Gap, Input, Output } from '@suss/behavioral-ir';
2
2
 
3
3
  type DiscoveryMatch = {
4
4
  type: "namedExport";
@@ -211,30 +211,42 @@ type DiscoveryMatch = {
211
211
  * The adapter populates `DiscoveredUnit.resolverInfo` so the
212
212
  * produced binding carries `graphql-resolver(typeName, fieldName)`.
213
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.
214
+ * (`@Resolver(() => User)` → `"User"`), and from
215
+ * `methodDecoratorTypeMap` when the class decorator has no
216
+ * argument. `fieldName` reads the method decorator's `{ name }`
217
+ * option override when present, otherwise the method name.
219
218
  */
220
219
  type: "decoratedMethod";
221
220
  /**
222
221
  * The module a pack-recognised decorator must be imported from
223
222
  * 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.
223
+ * decorators in their own re-exports, composing the framework's
224
+ * decorator with extra metadata. When that happens, the
225
+ * import-module gate would miss them; pass an array of accepted
226
+ * modules and any one match suffices.
229
227
  */
230
228
  importModule: string | string[];
231
229
  /**
232
230
  * Class decorator names to recognise. The first entry that
233
231
  * appears on a class wins for typeName extraction (the rest are
234
- * fallbacks for codebases with multiple wrapper styles).
232
+ * fallbacks for codebases with multiple wrapper styles). A pack
233
+ * ships only what its own framework declares here and takes a
234
+ * project's wrappers through its options.
235
235
  */
236
236
  classDecorators: string[];
237
237
  methodDecorators: string[];
238
+ /**
239
+ * Method decorator name → the type its field hangs off when the
240
+ * class decorator names none. NestJS puts `@Query` on the root
241
+ * `Query` type and `@Mutation` on `Mutation` whatever the class
242
+ * says, so those two answer for themselves.
243
+ *
244
+ * A decorator the map leaves out has no answer of its own, and a
245
+ * class that names no type leaves the field's owner unread. The
246
+ * binding then names no type and pairs with nothing, rather than
247
+ * claiming a field the schema does not have.
248
+ */
249
+ methodDecoratorTypeMap: Record<string, string>;
238
250
  } | {
239
251
  /**
240
252
  * NestJS-style REST controller discovery. Class decorated with
@@ -248,10 +260,9 @@ type DiscoveryMatch = {
248
260
  * Same wrapper-decorator tolerance as `decoratedMethod`: the
249
261
  * import-module gate fires on at least one method-route
250
262
  * 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.
263
+ * match by name only, so a project-internal wrapper composing
264
+ * the framework's decorator matches once the project names it
265
+ * in the pack's options.
255
266
  *
256
267
  * The adapter populates `DiscoveredUnit.routeInfo` so the
257
268
  * produced binding carries `rest(method, path)`.
@@ -685,14 +696,18 @@ interface TransparentWrapper {
685
696
  interface PatternPack {
686
697
  name: string;
687
698
  /**
688
- * Pack version stamp — feeds the cache invalidation key. Bump on
699
+ * Pack version stamp, which feeds the cache invalidation key. Bump on
689
700
  * any change that affects discovered units / extracted summaries.
690
- * Format is opaque to the adapter; semver is the obvious choice
691
- * but a content hash, build SHA, or monotonic integer works too.
692
- * Optional — packs that omit it use the literal string `"unset"`,
693
- * which means cache entries from one process to the next aren't
694
- * meaningfully versioned (suitable for development; production
695
- * packs should declare a version).
701
+ * Format is opaque to the adapter, so semver or a content hash both
702
+ * work.
703
+ *
704
+ * Optional, because whoever loads the pack knows more about it than
705
+ * the pack does. The CLI folds a hash of the file it loaded and of
706
+ * the config it passed into this stamp, so a pack run through the CLI
707
+ * invalidates on an edit whether or not it declares a version. A host
708
+ * that builds packs some other way carries that responsibility
709
+ * itself. A pack with nothing to stamp reads as `"unset"`, and warm
710
+ * caches will answer for code that has since changed.
696
711
  */
697
712
  version?: string;
698
713
  languages: string[];
@@ -981,6 +996,8 @@ interface DiscoveredCustomUnit {
981
996
  messageBus: MessageBusSemantics["messageBus"];
982
997
  channel: string;
983
998
  };
999
+ /** The thing that gets deployed and runs this unit, when known. */
1000
+ deployableUnit?: DeployableUnit;
984
1001
  /**
985
1002
  * Metadata merged onto the resulting summary's `metadata` field.
986
1003
  */
@@ -1264,6 +1281,23 @@ interface RawDeclaredContract {
1264
1281
  */
1265
1282
  provenance?: "derived" | "independent";
1266
1283
  }
1284
+ /**
1285
+ * What sits where a unit's body should be.
1286
+ *
1287
+ * - "absent": there is no body behind this declaration (an overload
1288
+ * signature, an ambient declaration, an abstract method), so the
1289
+ * pack read nothing about what the unit does.
1290
+ * - "empty": a body with nothing in it. A summary that says nothing
1291
+ * about it has described it completely.
1292
+ * - "statements": a body with work in it, including a concise
1293
+ * arrow's expression. A summary that says nothing about it has
1294
+ * described none of it.
1295
+ * - "elsewhere": the unit's body is not in what this run read. A
1296
+ * route registered with a handler the caller supplies announces a
1297
+ * boundary and names no function to look in, and the boundary is
1298
+ * worth reporting with nothing behind it.
1299
+ */
1300
+ type BodyContent = "absent" | "empty" | "statements" | "elsewhere";
1267
1301
  interface RawCodeStructure {
1268
1302
  identity: {
1269
1303
  name: string;
@@ -1283,6 +1317,8 @@ interface RawCodeStructure {
1283
1317
  * helpers, pure utilities, and anything the adapter can't place.
1284
1318
  */
1285
1319
  boundaryBinding: BoundaryBinding | null;
1320
+ /** The thing that gets deployed and runs this code, when known. */
1321
+ deployableUnit?: DeployableUnit;
1286
1322
  parameters: RawParameter[];
1287
1323
  branches: RawBranch[];
1288
1324
  /**
@@ -1292,6 +1328,14 @@ interface RawCodeStructure {
1292
1328
  * saying it does nothing.
1293
1329
  */
1294
1330
  unmatchedReturns?: number;
1331
+ /**
1332
+ * What the adapter found where the unit's body should be. A summary
1333
+ * with nothing in it means one of two different things, and only the
1334
+ * adapter can say which: a body nobody could read, or a body with
1335
+ * nothing in it to read. Adapters that do not say leave this unset,
1336
+ * and everything downstream treats the summary as it did before.
1337
+ */
1338
+ bodyContent?: BodyContent;
1295
1339
  dependencyCalls: RawDependencyCall[];
1296
1340
  declaredContract: RawDeclaredContract | null;
1297
1341
  /**
@@ -1358,6 +1402,17 @@ interface RawCodeStructure {
1358
1402
  reference: string;
1359
1403
  reason: string;
1360
1404
  };
1405
+ /**
1406
+ * Set when the source does not state part of the boundary this unit
1407
+ * sits on. The binding still goes out with that part left empty, so
1408
+ * the unit pairs with nothing rather than with whatever a guess named,
1409
+ * and this sentence tells a reader which part is missing and why.
1410
+ *
1411
+ * A statement about the reading rather than about the code, so it
1412
+ * lands as an `unreadOutcome` gap and no checker holds it against the
1413
+ * unit.
1414
+ */
1415
+ unreadBinding?: string;
1361
1416
  }
1362
1417
  interface ExtractorOptions {
1363
1418
  gapHandling: "strict" | "permissive" | "silent";
@@ -1370,4 +1425,4 @@ declare function terminalToOutput(terminal: RawTerminal): Output;
1370
1425
  declare function effectToIR(effect: RawEffect): Effect;
1371
1426
  declare function paramToInput(param: RawParameter): Input;
1372
1427
 
1373
- 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, type TransparentWrapper, assembleSummary, assessConfidence, detectGaps, effectToIR, httpRouteDiscovery, makeTransitionId, paramToInput, terminalToOutput };
1428
+ export { type AccessRecognizer, type BindingExtraction, type BodyContent, type ContractPattern, type DiscoveredCustomUnit, type DiscoveredSubUnit, type DiscoveredSubUnitParent, type DiscoveryMatch, type DiscoveryPattern, type EffectArg, type ExtractorOptions, type InputMappingPattern, type InvocationRecognizer, type PatternPack, type RawBranch, type RawCodeStructure, type RawCondition, type RawDeclaredContract, type RawDependencyCall, type RawEffect, type RawParameter, type RawTerminal, type ResponsePropertyMapping, type ResponsePropertyMeaning, type TerminalExtraction, type TerminalMatch, type TerminalPattern, type TransparentWrapper, assembleSummary, assessConfidence, detectGaps, effectToIR, httpRouteDiscovery, makeTransitionId, paramToInput, terminalToOutput };
package/dist/index.js CHANGED
@@ -64,7 +64,8 @@ function assembleSummary(raw, options = DEFAULT_OPTIONS) {
64
64
  identity: {
65
65
  name: raw.identity.name,
66
66
  exportPath: raw.identity.exportPath,
67
- boundaryBinding: raw.boundaryBinding ?? null
67
+ boundaryBinding: raw.boundaryBinding ?? null,
68
+ ...raw.deployableUnit !== void 0 ? { deployableUnit: raw.deployableUnit } : {}
68
69
  },
69
70
  inputs,
70
71
  transitions,
@@ -130,6 +131,23 @@ function detectGaps(raw, transitions, options) {
130
131
  description: count === 1 ? "One return in this function matches none of the terminal shapes this pack looks for, so what it produces is not described here" : `${count} returns in this function match none of the terminal shapes this pack looks for, so what they produce is not described here`
131
132
  });
132
133
  }
134
+ if (raw.unreadBinding !== void 0) {
135
+ gaps.push({
136
+ type: "unreadOutcome",
137
+ conditions: [],
138
+ consequence: "unknown",
139
+ description: raw.unreadBinding
140
+ });
141
+ }
142
+ const unreadBody = describeUnreadBody(raw);
143
+ if (unreadBody !== null) {
144
+ gaps.push({
145
+ type: "unreadOutcome",
146
+ conditions: [],
147
+ consequence: "unknown",
148
+ description: unreadBody
149
+ });
150
+ }
133
151
  if (raw.declaredContract) {
134
152
  const producedStatuses = new Set(
135
153
  transitions.flatMap((t) => {
@@ -169,10 +187,34 @@ function detectGaps(raw, transitions, options) {
169
187
  }
170
188
  return gaps;
171
189
  }
190
+ function bodyWentUnread(raw) {
191
+ if (raw.bodyContent === "absent" || raw.bodyContent === "elsewhere") {
192
+ return true;
193
+ }
194
+ return raw.bodyContent === "statements" && raw.branches.length === 0;
195
+ }
196
+ function describeUnreadBody(raw) {
197
+ if (!bodyWentUnread(raw)) {
198
+ return null;
199
+ }
200
+ if (raw.bodyContent === "absent") {
201
+ return "This unit is a declaration with no body behind it, so nothing about what it does was read here";
202
+ }
203
+ if (raw.bodyContent === "elsewhere") {
204
+ return "The handler on this boundary comes from outside the code that registers it, so nothing about what it does was read here";
205
+ }
206
+ if ((raw.unmatchedReturns ?? 0) > 0) {
207
+ return null;
208
+ }
209
+ return "Nothing this unit's body does matches a shape this pack looks for, so what it does is not described here";
210
+ }
172
211
  function assessConfidence(raw) {
173
212
  if ((raw.unmatchedReturns ?? 0) > 0) {
174
213
  return { source: "inferred_static", level: "low" };
175
214
  }
215
+ if (bodyWentUnread(raw)) {
216
+ return { source: "inferred_static", level: "low" };
217
+ }
176
218
  let total = 0;
177
219
  let opaque = 0;
178
220
  for (const branch of raw.branches) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/extractor",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Framework-agnostic extraction pipeline that turns raw extractions into suss behavioral summaries.",
6
6
  "keywords": [
@@ -45,7 +45,7 @@
45
45
  "typecheck": "tsc --noEmit"
46
46
  },
47
47
  "dependencies": {
48
- "@suss/behavioral-ir": "0.1.0"
48
+ "@suss/behavioral-ir": "0.1.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "tsup": "*",