@suss/extractor 0.0.2 → 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/README.md +1 -1
- package/dist/index.cjs +57 -1
- package/dist/index.d.cts +183 -24
- package/dist/index.d.ts +183 -24
- package/dist/index.js +57 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Assembly engine that turns raw language-adapter output into a `BehavioralSummary
|
|
|
4
4
|
|
|
5
5
|
## What this package is
|
|
6
6
|
|
|
7
|
-
`@suss/extractor` is the core assembly layer of the suss pipeline. Language adapters (such as `@suss/adapter-typescript`) parse source code and produce a `RawCodeStructure
|
|
7
|
+
`@suss/extractor` is the core assembly layer of the suss pipeline. Language adapters (such as `@suss/adapter-typescript`) parse source code and produce a `RawCodeStructure`, a normalized, adapter-specific intermediate form. The extractor's `assembleSummary` function converts that structure into the final `BehavioralSummary` IR, handling condition polarity, terminal mapping, gap detection, confidence assessment, and `expectedInput` pass-through for client field tracking. It also exports the `RawCodeStructure` type, the `PatternPack` interface, and all related raw types so adapters can share a common contract.
|
|
8
8
|
|
|
9
9
|
## Where it sits in suss
|
|
10
10
|
|
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,
|
|
@@ -150,6 +151,34 @@ function detectGaps(raw, transitions, options) {
|
|
|
150
151
|
return [];
|
|
151
152
|
}
|
|
152
153
|
const gaps = [];
|
|
154
|
+
if (raw.unmatchedReturns !== void 0 && raw.unmatchedReturns > 0) {
|
|
155
|
+
const count = raw.unmatchedReturns;
|
|
156
|
+
gaps.push({
|
|
157
|
+
// Not a contract violation: the handler may be answering
|
|
158
|
+
// correctly, in a shape nobody taught the pack.
|
|
159
|
+
type: "unreadOutcome",
|
|
160
|
+
conditions: [],
|
|
161
|
+
consequence: "unknown",
|
|
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`
|
|
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
|
+
}
|
|
153
182
|
if (raw.declaredContract) {
|
|
154
183
|
const producedStatuses = new Set(
|
|
155
184
|
transitions.flatMap((t) => {
|
|
@@ -189,7 +218,34 @@ function detectGaps(raw, transitions, options) {
|
|
|
189
218
|
}
|
|
190
219
|
return gaps;
|
|
191
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
|
+
}
|
|
192
242
|
function assessConfidence(raw) {
|
|
243
|
+
if ((raw.unmatchedReturns ?? 0) > 0) {
|
|
244
|
+
return { source: "inferred_static", level: "low" };
|
|
245
|
+
}
|
|
246
|
+
if (bodyWentUnread(raw)) {
|
|
247
|
+
return { source: "inferred_static", level: "low" };
|
|
248
|
+
}
|
|
193
249
|
let total = 0;
|
|
194
250
|
let opaque = 0;
|
|
195
251
|
for (const branch of raw.branches) {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, 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"`)
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
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
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
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
|
|
252
|
-
*
|
|
253
|
-
*
|
|
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)`.
|
|
@@ -374,7 +385,49 @@ type BindingExtraction = {
|
|
|
374
385
|
type: "fromRegistration";
|
|
375
386
|
position: number;
|
|
376
387
|
} | {
|
|
388
|
+
type: "fromArgumentProperty";
|
|
389
|
+
position: number;
|
|
390
|
+
property: string;
|
|
391
|
+
} | {
|
|
392
|
+
/**
|
|
393
|
+
* The route path comes from where the file sits, which is how
|
|
394
|
+
* Next.js and React Router describe their routes. The pack
|
|
395
|
+
* spells out its own convention here, since the adapter knows
|
|
396
|
+
* about files and the pack knows what the framework does with
|
|
397
|
+
* their names.
|
|
398
|
+
*
|
|
399
|
+
* `app/api/orders/[id]/route.ts` under `{ root: "app",
|
|
400
|
+
* dropBasenames: ["route"], dynamic: "brackets" }` comes out as
|
|
401
|
+
* `/api/orders/{id}`, which pairs with an Express provider
|
|
402
|
+
* writing `/api/orders/:id`.
|
|
403
|
+
*/
|
|
377
404
|
type: "fromFilename";
|
|
405
|
+
/**
|
|
406
|
+
* Where a route path starts. The directories below it become
|
|
407
|
+
* the path; everything above it belongs to the project layout
|
|
408
|
+
* and is dropped.
|
|
409
|
+
*/
|
|
410
|
+
root: string;
|
|
411
|
+
/**
|
|
412
|
+
* Filenames that say what a file is rather than adding a
|
|
413
|
+
* segment: `route`, `page`, `index`, `_index`.
|
|
414
|
+
*/
|
|
415
|
+
dropBasenames?: string[];
|
|
416
|
+
/**
|
|
417
|
+
* How the framework writes a parameter in a filename. Next.js
|
|
418
|
+
* uses `[id]`, React Router uses `$id`.
|
|
419
|
+
*/
|
|
420
|
+
dynamic?: "brackets" | "dollarPrefix";
|
|
421
|
+
/**
|
|
422
|
+
* Whether a directory in parentheses organises files without
|
|
423
|
+
* appearing in the URL, as `app/(marketing)/about` does.
|
|
424
|
+
*/
|
|
425
|
+
dropParenthesized?: boolean;
|
|
426
|
+
/**
|
|
427
|
+
* Whether one filename holds the whole path with dots between
|
|
428
|
+
* the segments, as `routes/orders.$id.tsx` does.
|
|
429
|
+
*/
|
|
430
|
+
flat?: boolean;
|
|
378
431
|
} | {
|
|
379
432
|
type: "fromContract";
|
|
380
433
|
} | {
|
|
@@ -487,6 +540,10 @@ interface TerminalExtraction {
|
|
|
487
540
|
} | {
|
|
488
541
|
from: "constructor";
|
|
489
542
|
codes: Record<string, number>;
|
|
543
|
+
} | {
|
|
544
|
+
from: "argumentProperty";
|
|
545
|
+
position: number;
|
|
546
|
+
name: string;
|
|
490
547
|
} | {
|
|
491
548
|
from: "argumentConstructor";
|
|
492
549
|
position: number;
|
|
@@ -619,17 +676,38 @@ interface ResponsePropertyMapping {
|
|
|
619
676
|
/** What the value semantically represents */
|
|
620
677
|
semantics: ResponsePropertyMeaning;
|
|
621
678
|
}
|
|
679
|
+
/**
|
|
680
|
+
* A library wrapper whose result is the function it was handed. The
|
|
681
|
+
* adapter works this out on its own for factories declared in the
|
|
682
|
+
* project by reading their bodies. A library's body is not readable,
|
|
683
|
+
* so the pack states it.
|
|
684
|
+
*/
|
|
685
|
+
interface TransparentWrapper {
|
|
686
|
+
/** Callee text as written, e.g. "Sentry.wrapHandler". */
|
|
687
|
+
callee: string;
|
|
688
|
+
/** Which argument holds the wrapped function. */
|
|
689
|
+
argument: number;
|
|
690
|
+
/**
|
|
691
|
+
* The module the callee has to have been imported from. Without it a
|
|
692
|
+
* local object spelled the same way would be taken for the library.
|
|
693
|
+
*/
|
|
694
|
+
module: string;
|
|
695
|
+
}
|
|
622
696
|
interface PatternPack {
|
|
623
697
|
name: string;
|
|
624
698
|
/**
|
|
625
|
-
* Pack version stamp
|
|
699
|
+
* Pack version stamp, which feeds the cache invalidation key. Bump on
|
|
626
700
|
* any change that affects discovered units / extracted summaries.
|
|
627
|
-
* Format is opaque to the adapter
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
632
|
-
*
|
|
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.
|
|
633
711
|
*/
|
|
634
712
|
version?: string;
|
|
635
713
|
languages: string[];
|
|
@@ -792,6 +870,17 @@ interface PatternPack {
|
|
|
792
870
|
* available without an import).
|
|
793
871
|
*/
|
|
794
872
|
requiresImport?: string[];
|
|
873
|
+
/**
|
|
874
|
+
* Library wrappers whose result is the wrapped function. The adapter
|
|
875
|
+
* derives this automatically for project-local factories by reading
|
|
876
|
+
* their bodies; a library wrapper's body is not readable, so the
|
|
877
|
+
* pack states the judgment: a call to `callee` resolves to its
|
|
878
|
+
* `argument`-th argument.
|
|
879
|
+
*
|
|
880
|
+
* `callee` matches the call expression text as written, e.g.
|
|
881
|
+
* `"Sentry.wrapHandler"`.
|
|
882
|
+
*/
|
|
883
|
+
transparentWrappers?: TransparentWrapper[];
|
|
795
884
|
/**
|
|
796
885
|
* Per-property-access recognizers — sister to
|
|
797
886
|
* `invocationRecognizers` but firing on `PropertyAccessExpression`
|
|
@@ -884,6 +973,31 @@ interface DiscoveredCustomUnit {
|
|
|
884
973
|
method: string;
|
|
885
974
|
path: string;
|
|
886
975
|
};
|
|
976
|
+
/**
|
|
977
|
+
* GraphQL field identity for units a callback discovers against an
|
|
978
|
+
* external manifest rather than an in-code resolver map. AppSync
|
|
979
|
+
* routes a field to a Lambda in the deploy template, so the field is
|
|
980
|
+
* the boundary that code serves. When set, the adapter builds a
|
|
981
|
+
* `graphql-resolver` binding from `(typeName, fieldName)`, which
|
|
982
|
+
* pairs with the operations a client sends.
|
|
983
|
+
*/
|
|
984
|
+
resolverInfo?: {
|
|
985
|
+
typeName: string;
|
|
986
|
+
fieldName: string;
|
|
987
|
+
};
|
|
988
|
+
/**
|
|
989
|
+
* Message-bus channel identity for consumer units a callback
|
|
990
|
+
* discovers against a subject the code itself names (a handler
|
|
991
|
+
* factory whose config carries the expected subject). When set, the
|
|
992
|
+
* adapter builds a `message-bus` binding from `(messageBus, channel)`,
|
|
993
|
+
* which pairs with producers sending on the same channel.
|
|
994
|
+
*/
|
|
995
|
+
channelInfo?: {
|
|
996
|
+
messageBus: MessageBusSemantics["messageBus"];
|
|
997
|
+
channel: string;
|
|
998
|
+
};
|
|
999
|
+
/** The thing that gets deployed and runs this unit, when known. */
|
|
1000
|
+
deployableUnit?: DeployableUnit;
|
|
887
1001
|
/**
|
|
888
1002
|
* Metadata merged onto the resulting summary's `metadata` field.
|
|
889
1003
|
*/
|
|
@@ -1167,6 +1281,23 @@ interface RawDeclaredContract {
|
|
|
1167
1281
|
*/
|
|
1168
1282
|
provenance?: "derived" | "independent";
|
|
1169
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";
|
|
1170
1301
|
interface RawCodeStructure {
|
|
1171
1302
|
identity: {
|
|
1172
1303
|
name: string;
|
|
@@ -1186,8 +1317,25 @@ interface RawCodeStructure {
|
|
|
1186
1317
|
* helpers, pure utilities, and anything the adapter can't place.
|
|
1187
1318
|
*/
|
|
1188
1319
|
boundaryBinding: BoundaryBinding | null;
|
|
1320
|
+
/** The thing that gets deployed and runs this code, when known. */
|
|
1321
|
+
deployableUnit?: DeployableUnit;
|
|
1189
1322
|
parameters: RawParameter[];
|
|
1190
1323
|
branches: RawBranch[];
|
|
1324
|
+
/**
|
|
1325
|
+
* Return statements in the body that none of the pack's terminals
|
|
1326
|
+
* matched. A handler shaped in a way the pack does not describe still
|
|
1327
|
+
* returns something, and saying nothing about it reads the same as
|
|
1328
|
+
* saying it does nothing.
|
|
1329
|
+
*/
|
|
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;
|
|
1191
1339
|
dependencyCalls: RawDependencyCall[];
|
|
1192
1340
|
declaredContract: RawDeclaredContract | null;
|
|
1193
1341
|
/**
|
|
@@ -1254,6 +1402,17 @@ interface RawCodeStructure {
|
|
|
1254
1402
|
reference: string;
|
|
1255
1403
|
reason: string;
|
|
1256
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;
|
|
1257
1416
|
}
|
|
1258
1417
|
interface ExtractorOptions {
|
|
1259
1418
|
gapHandling: "strict" | "permissive" | "silent";
|
|
@@ -1266,4 +1425,4 @@ declare function terminalToOutput(terminal: RawTerminal): Output;
|
|
|
1266
1425
|
declare function effectToIR(effect: RawEffect): Effect;
|
|
1267
1426
|
declare function paramToInput(param: RawParameter): Input;
|
|
1268
1427
|
|
|
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 };
|
|
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, 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"`)
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
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
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
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
|
|
252
|
-
*
|
|
253
|
-
*
|
|
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)`.
|
|
@@ -374,7 +385,49 @@ type BindingExtraction = {
|
|
|
374
385
|
type: "fromRegistration";
|
|
375
386
|
position: number;
|
|
376
387
|
} | {
|
|
388
|
+
type: "fromArgumentProperty";
|
|
389
|
+
position: number;
|
|
390
|
+
property: string;
|
|
391
|
+
} | {
|
|
392
|
+
/**
|
|
393
|
+
* The route path comes from where the file sits, which is how
|
|
394
|
+
* Next.js and React Router describe their routes. The pack
|
|
395
|
+
* spells out its own convention here, since the adapter knows
|
|
396
|
+
* about files and the pack knows what the framework does with
|
|
397
|
+
* their names.
|
|
398
|
+
*
|
|
399
|
+
* `app/api/orders/[id]/route.ts` under `{ root: "app",
|
|
400
|
+
* dropBasenames: ["route"], dynamic: "brackets" }` comes out as
|
|
401
|
+
* `/api/orders/{id}`, which pairs with an Express provider
|
|
402
|
+
* writing `/api/orders/:id`.
|
|
403
|
+
*/
|
|
377
404
|
type: "fromFilename";
|
|
405
|
+
/**
|
|
406
|
+
* Where a route path starts. The directories below it become
|
|
407
|
+
* the path; everything above it belongs to the project layout
|
|
408
|
+
* and is dropped.
|
|
409
|
+
*/
|
|
410
|
+
root: string;
|
|
411
|
+
/**
|
|
412
|
+
* Filenames that say what a file is rather than adding a
|
|
413
|
+
* segment: `route`, `page`, `index`, `_index`.
|
|
414
|
+
*/
|
|
415
|
+
dropBasenames?: string[];
|
|
416
|
+
/**
|
|
417
|
+
* How the framework writes a parameter in a filename. Next.js
|
|
418
|
+
* uses `[id]`, React Router uses `$id`.
|
|
419
|
+
*/
|
|
420
|
+
dynamic?: "brackets" | "dollarPrefix";
|
|
421
|
+
/**
|
|
422
|
+
* Whether a directory in parentheses organises files without
|
|
423
|
+
* appearing in the URL, as `app/(marketing)/about` does.
|
|
424
|
+
*/
|
|
425
|
+
dropParenthesized?: boolean;
|
|
426
|
+
/**
|
|
427
|
+
* Whether one filename holds the whole path with dots between
|
|
428
|
+
* the segments, as `routes/orders.$id.tsx` does.
|
|
429
|
+
*/
|
|
430
|
+
flat?: boolean;
|
|
378
431
|
} | {
|
|
379
432
|
type: "fromContract";
|
|
380
433
|
} | {
|
|
@@ -487,6 +540,10 @@ interface TerminalExtraction {
|
|
|
487
540
|
} | {
|
|
488
541
|
from: "constructor";
|
|
489
542
|
codes: Record<string, number>;
|
|
543
|
+
} | {
|
|
544
|
+
from: "argumentProperty";
|
|
545
|
+
position: number;
|
|
546
|
+
name: string;
|
|
490
547
|
} | {
|
|
491
548
|
from: "argumentConstructor";
|
|
492
549
|
position: number;
|
|
@@ -619,17 +676,38 @@ interface ResponsePropertyMapping {
|
|
|
619
676
|
/** What the value semantically represents */
|
|
620
677
|
semantics: ResponsePropertyMeaning;
|
|
621
678
|
}
|
|
679
|
+
/**
|
|
680
|
+
* A library wrapper whose result is the function it was handed. The
|
|
681
|
+
* adapter works this out on its own for factories declared in the
|
|
682
|
+
* project by reading their bodies. A library's body is not readable,
|
|
683
|
+
* so the pack states it.
|
|
684
|
+
*/
|
|
685
|
+
interface TransparentWrapper {
|
|
686
|
+
/** Callee text as written, e.g. "Sentry.wrapHandler". */
|
|
687
|
+
callee: string;
|
|
688
|
+
/** Which argument holds the wrapped function. */
|
|
689
|
+
argument: number;
|
|
690
|
+
/**
|
|
691
|
+
* The module the callee has to have been imported from. Without it a
|
|
692
|
+
* local object spelled the same way would be taken for the library.
|
|
693
|
+
*/
|
|
694
|
+
module: string;
|
|
695
|
+
}
|
|
622
696
|
interface PatternPack {
|
|
623
697
|
name: string;
|
|
624
698
|
/**
|
|
625
|
-
* Pack version stamp
|
|
699
|
+
* Pack version stamp, which feeds the cache invalidation key. Bump on
|
|
626
700
|
* any change that affects discovered units / extracted summaries.
|
|
627
|
-
* Format is opaque to the adapter
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
632
|
-
*
|
|
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.
|
|
633
711
|
*/
|
|
634
712
|
version?: string;
|
|
635
713
|
languages: string[];
|
|
@@ -792,6 +870,17 @@ interface PatternPack {
|
|
|
792
870
|
* available without an import).
|
|
793
871
|
*/
|
|
794
872
|
requiresImport?: string[];
|
|
873
|
+
/**
|
|
874
|
+
* Library wrappers whose result is the wrapped function. The adapter
|
|
875
|
+
* derives this automatically for project-local factories by reading
|
|
876
|
+
* their bodies; a library wrapper's body is not readable, so the
|
|
877
|
+
* pack states the judgment: a call to `callee` resolves to its
|
|
878
|
+
* `argument`-th argument.
|
|
879
|
+
*
|
|
880
|
+
* `callee` matches the call expression text as written, e.g.
|
|
881
|
+
* `"Sentry.wrapHandler"`.
|
|
882
|
+
*/
|
|
883
|
+
transparentWrappers?: TransparentWrapper[];
|
|
795
884
|
/**
|
|
796
885
|
* Per-property-access recognizers — sister to
|
|
797
886
|
* `invocationRecognizers` but firing on `PropertyAccessExpression`
|
|
@@ -884,6 +973,31 @@ interface DiscoveredCustomUnit {
|
|
|
884
973
|
method: string;
|
|
885
974
|
path: string;
|
|
886
975
|
};
|
|
976
|
+
/**
|
|
977
|
+
* GraphQL field identity for units a callback discovers against an
|
|
978
|
+
* external manifest rather than an in-code resolver map. AppSync
|
|
979
|
+
* routes a field to a Lambda in the deploy template, so the field is
|
|
980
|
+
* the boundary that code serves. When set, the adapter builds a
|
|
981
|
+
* `graphql-resolver` binding from `(typeName, fieldName)`, which
|
|
982
|
+
* pairs with the operations a client sends.
|
|
983
|
+
*/
|
|
984
|
+
resolverInfo?: {
|
|
985
|
+
typeName: string;
|
|
986
|
+
fieldName: string;
|
|
987
|
+
};
|
|
988
|
+
/**
|
|
989
|
+
* Message-bus channel identity for consumer units a callback
|
|
990
|
+
* discovers against a subject the code itself names (a handler
|
|
991
|
+
* factory whose config carries the expected subject). When set, the
|
|
992
|
+
* adapter builds a `message-bus` binding from `(messageBus, channel)`,
|
|
993
|
+
* which pairs with producers sending on the same channel.
|
|
994
|
+
*/
|
|
995
|
+
channelInfo?: {
|
|
996
|
+
messageBus: MessageBusSemantics["messageBus"];
|
|
997
|
+
channel: string;
|
|
998
|
+
};
|
|
999
|
+
/** The thing that gets deployed and runs this unit, when known. */
|
|
1000
|
+
deployableUnit?: DeployableUnit;
|
|
887
1001
|
/**
|
|
888
1002
|
* Metadata merged onto the resulting summary's `metadata` field.
|
|
889
1003
|
*/
|
|
@@ -1167,6 +1281,23 @@ interface RawDeclaredContract {
|
|
|
1167
1281
|
*/
|
|
1168
1282
|
provenance?: "derived" | "independent";
|
|
1169
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";
|
|
1170
1301
|
interface RawCodeStructure {
|
|
1171
1302
|
identity: {
|
|
1172
1303
|
name: string;
|
|
@@ -1186,8 +1317,25 @@ interface RawCodeStructure {
|
|
|
1186
1317
|
* helpers, pure utilities, and anything the adapter can't place.
|
|
1187
1318
|
*/
|
|
1188
1319
|
boundaryBinding: BoundaryBinding | null;
|
|
1320
|
+
/** The thing that gets deployed and runs this code, when known. */
|
|
1321
|
+
deployableUnit?: DeployableUnit;
|
|
1189
1322
|
parameters: RawParameter[];
|
|
1190
1323
|
branches: RawBranch[];
|
|
1324
|
+
/**
|
|
1325
|
+
* Return statements in the body that none of the pack's terminals
|
|
1326
|
+
* matched. A handler shaped in a way the pack does not describe still
|
|
1327
|
+
* returns something, and saying nothing about it reads the same as
|
|
1328
|
+
* saying it does nothing.
|
|
1329
|
+
*/
|
|
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;
|
|
1191
1339
|
dependencyCalls: RawDependencyCall[];
|
|
1192
1340
|
declaredContract: RawDeclaredContract | null;
|
|
1193
1341
|
/**
|
|
@@ -1254,6 +1402,17 @@ interface RawCodeStructure {
|
|
|
1254
1402
|
reference: string;
|
|
1255
1403
|
reason: string;
|
|
1256
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;
|
|
1257
1416
|
}
|
|
1258
1417
|
interface ExtractorOptions {
|
|
1259
1418
|
gapHandling: "strict" | "permissive" | "silent";
|
|
@@ -1266,4 +1425,4 @@ declare function terminalToOutput(terminal: RawTerminal): Output;
|
|
|
1266
1425
|
declare function effectToIR(effect: RawEffect): Effect;
|
|
1267
1426
|
declare function paramToInput(param: RawParameter): Input;
|
|
1268
1427
|
|
|
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 };
|
|
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,
|
|
@@ -119,6 +120,34 @@ function detectGaps(raw, transitions, options) {
|
|
|
119
120
|
return [];
|
|
120
121
|
}
|
|
121
122
|
const gaps = [];
|
|
123
|
+
if (raw.unmatchedReturns !== void 0 && raw.unmatchedReturns > 0) {
|
|
124
|
+
const count = raw.unmatchedReturns;
|
|
125
|
+
gaps.push({
|
|
126
|
+
// Not a contract violation: the handler may be answering
|
|
127
|
+
// correctly, in a shape nobody taught the pack.
|
|
128
|
+
type: "unreadOutcome",
|
|
129
|
+
conditions: [],
|
|
130
|
+
consequence: "unknown",
|
|
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`
|
|
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
|
+
}
|
|
122
151
|
if (raw.declaredContract) {
|
|
123
152
|
const producedStatuses = new Set(
|
|
124
153
|
transitions.flatMap((t) => {
|
|
@@ -158,7 +187,34 @@ function detectGaps(raw, transitions, options) {
|
|
|
158
187
|
}
|
|
159
188
|
return gaps;
|
|
160
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
|
+
}
|
|
161
211
|
function assessConfidence(raw) {
|
|
212
|
+
if ((raw.unmatchedReturns ?? 0) > 0) {
|
|
213
|
+
return { source: "inferred_static", level: "low" };
|
|
214
|
+
}
|
|
215
|
+
if (bodyWentUnread(raw)) {
|
|
216
|
+
return { source: "inferred_static", level: "low" };
|
|
217
|
+
}
|
|
162
218
|
let total = 0;
|
|
163
219
|
let opaque = 0;
|
|
164
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.
|
|
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.
|
|
48
|
+
"@suss/behavioral-ir": "0.1.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"tsup": "*",
|