@suss/extractor 0.21.0 → 0.22.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/README.md +40 -0
- package/dist/index.cjs +990 -41
- package/dist/index.d.cts +644 -1
- package/dist/index.d.ts +644 -1
- package/dist/index.js +952 -34
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -22,6 +32,7 @@ var index_exports = {};
|
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
IdMap: () => IdMap,
|
|
24
34
|
IdSet: () => IdSet,
|
|
35
|
+
MAX_ENTRIES: () => MAX_ENTRIES,
|
|
25
36
|
MAX_PATHS: () => MAX_PATHS,
|
|
26
37
|
PathBudgetExceeded: () => PathBudgetExceeded,
|
|
27
38
|
SKIP_CHILDREN: () => SKIP_CHILDREN,
|
|
@@ -31,25 +42,44 @@ __export(index_exports, {
|
|
|
31
42
|
andThenReading: () => andThenReading,
|
|
32
43
|
assembleSummary: () => assembleSummary,
|
|
33
44
|
assessConfidence: () => assessConfidence,
|
|
45
|
+
buildUngatedExtractionReport: () => buildUngatedExtractionReport,
|
|
34
46
|
composeWrappers: () => composeWrappers,
|
|
47
|
+
computeContentHash: () => computeContentHash,
|
|
48
|
+
computeDistHashFrom: () => computeDistHashFrom,
|
|
35
49
|
configuredCallOption: () => configuredCallOption,
|
|
50
|
+
createAdapterStamp: () => createAdapterStamp,
|
|
51
|
+
createCacheLayer: () => createCacheLayer,
|
|
52
|
+
createPackTallies: () => createPackTallies,
|
|
53
|
+
createTimer: () => createTimer,
|
|
36
54
|
detectGaps: () => detectGaps,
|
|
37
55
|
effectToIR: () => effectToIR,
|
|
56
|
+
emptyTally: () => emptyTally,
|
|
38
57
|
enumerateOrDegrade: () => enumerateOrDegrade,
|
|
39
58
|
enumerateStructuredPaths: () => enumerateStructuredPaths,
|
|
59
|
+
evaluatePackHealth: () => evaluatePackHealth,
|
|
40
60
|
firstWrittenReading: () => firstWrittenReading,
|
|
61
|
+
formatPackHealth: () => formatPackHealth,
|
|
41
62
|
guardsHoldOn: () => guardsHoldOn,
|
|
42
63
|
httpRouteDiscovery: () => httpRouteDiscovery,
|
|
43
64
|
makeTransitionId: () => makeTransitionId,
|
|
44
65
|
mapReading: () => mapReading,
|
|
66
|
+
moduleInitStructure: () => moduleInitStructure,
|
|
67
|
+
noopTimer: () => noopTimer,
|
|
68
|
+
packGradients: () => packGradients,
|
|
45
69
|
paramToInput: () => paramToInput,
|
|
70
|
+
projectFileStamp: () => projectFileStamp,
|
|
46
71
|
rawConditionToPredicate: () => rawConditionToPredicate,
|
|
72
|
+
recordPackFailure: () => recordPackFailure,
|
|
47
73
|
registrationHelperDiscovery: () => registrationHelperDiscovery,
|
|
48
74
|
routeHelperIndex: () => routeHelperIndex,
|
|
75
|
+
runDigest: () => runDigest,
|
|
49
76
|
runsBefore: () => runsBefore,
|
|
50
77
|
scopeOption: () => scopeOption,
|
|
51
78
|
sharedGatingConditions: () => sharedGatingConditions,
|
|
79
|
+
stampModuleImports: () => stampModuleImports,
|
|
52
80
|
storageSystemOption: () => storageSystemOption,
|
|
81
|
+
summaryCountsByPack: () => summaryCountsByPack,
|
|
82
|
+
tallyUnit: () => tallyUnit,
|
|
53
83
|
terminalToOutput: () => terminalToOutput,
|
|
54
84
|
unreadableReading: () => unreadableReading,
|
|
55
85
|
unwrapJsonStringify: () => unwrapJsonStringify,
|
|
@@ -59,8 +89,8 @@ __export(index_exports, {
|
|
|
59
89
|
writtenReading: () => writtenReading
|
|
60
90
|
});
|
|
61
91
|
module.exports = __toCommonJS(index_exports);
|
|
62
|
-
var
|
|
63
|
-
var
|
|
92
|
+
var import_node_crypto3 = require("crypto");
|
|
93
|
+
var import_behavioral_ir4 = require("@suss/behavioral-ir");
|
|
64
94
|
|
|
65
95
|
// src/inputReads.ts
|
|
66
96
|
function inputReadsOf(args) {
|
|
@@ -204,8 +234,8 @@ var catchEntryCond = () => fixedCond(
|
|
|
204
234
|
"positive",
|
|
205
235
|
"catchBlock"
|
|
206
236
|
);
|
|
207
|
-
function classify(
|
|
208
|
-
return
|
|
237
|
+
function classify(path3, home, isAncestorOrSelf) {
|
|
238
|
+
return path3.map((cond) => {
|
|
209
239
|
if (cond.branchStmt === null) {
|
|
210
240
|
return cond.info;
|
|
211
241
|
}
|
|
@@ -270,9 +300,9 @@ function chargeBudget(state) {
|
|
|
270
300
|
);
|
|
271
301
|
}
|
|
272
302
|
}
|
|
273
|
-
function recordTerminal(ctx, terminal, home,
|
|
303
|
+
function recordTerminal(ctx, terminal, home, path3) {
|
|
274
304
|
chargeBudget(ctx.state);
|
|
275
|
-
const info = classify(
|
|
305
|
+
const info = classify(path3, home, ctx.isAncestorOrSelf);
|
|
276
306
|
const existing = ctx.state.byTerminal.get(terminal) ?? [];
|
|
277
307
|
existing.push(info);
|
|
278
308
|
ctx.state.byTerminal.set(terminal, existing);
|
|
@@ -283,15 +313,15 @@ function containsTerminal(ctx, stmt) {
|
|
|
283
313
|
}
|
|
284
314
|
return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
|
|
285
315
|
}
|
|
286
|
-
function stepIf(stmt,
|
|
316
|
+
function stepIf(stmt, path3, ctx) {
|
|
287
317
|
const thenExit = exitKindOfList(stmt.thenBody);
|
|
288
318
|
const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
|
|
289
319
|
const thenPath = [
|
|
290
|
-
...
|
|
320
|
+
...path3,
|
|
291
321
|
branchCond(stmt, stmt.condition, "positive", elseExit)
|
|
292
322
|
];
|
|
293
323
|
const elsePath = [
|
|
294
|
-
...
|
|
324
|
+
...path3,
|
|
295
325
|
branchCond(
|
|
296
326
|
stmt,
|
|
297
327
|
stmt.condition,
|
|
@@ -308,7 +338,7 @@ function stepIf(stmt, path, ctx) {
|
|
|
308
338
|
}
|
|
309
339
|
return out;
|
|
310
340
|
}
|
|
311
|
-
function stepSwitch(stmt,
|
|
341
|
+
function stepSwitch(stmt, path3, ctx) {
|
|
312
342
|
const out = [];
|
|
313
343
|
const negations = [];
|
|
314
344
|
let defaultRanBody = false;
|
|
@@ -318,7 +348,7 @@ function stepSwitch(stmt, path, ctx) {
|
|
|
318
348
|
if (hasStrayBreak(group.body)) {
|
|
319
349
|
throw new UnmodeledFlow("non-trailing break in switch clause");
|
|
320
350
|
}
|
|
321
|
-
const groupPath = group.condition === null ? [...
|
|
351
|
+
const groupPath = group.condition === null ? [...path3, ...negations] : [...path3, fixedCond(group.condition, "positive", "explicit")];
|
|
322
352
|
const conts = enumerate(ctx, group.body, groupPath);
|
|
323
353
|
if (conts.length > 0) {
|
|
324
354
|
if (!group.hasTrailingBreak && !fallthroughSafe) {
|
|
@@ -335,24 +365,24 @@ function stepSwitch(stmt, path, ctx) {
|
|
|
335
365
|
}
|
|
336
366
|
}
|
|
337
367
|
if (!defaultRanBody) {
|
|
338
|
-
out.push([...
|
|
368
|
+
out.push([...path3, ...negations]);
|
|
339
369
|
}
|
|
340
370
|
return out;
|
|
341
371
|
}
|
|
342
|
-
function stepLoop(stmt,
|
|
372
|
+
function stepLoop(stmt, path3, ctx) {
|
|
343
373
|
const bodyExit = exitKindOfList(stmt.body);
|
|
344
374
|
if (containsTerminal(ctx, stmt)) {
|
|
345
375
|
enumerate(ctx, stmt.body, [
|
|
346
|
-
...
|
|
376
|
+
...path3,
|
|
347
377
|
loopIterationCond(stmt.condition.sourceText)
|
|
348
378
|
]);
|
|
349
379
|
}
|
|
350
380
|
if (bodyExit !== null) {
|
|
351
|
-
return [[...
|
|
381
|
+
return [[...path3, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
|
|
352
382
|
}
|
|
353
|
-
return [
|
|
383
|
+
return [path3];
|
|
354
384
|
}
|
|
355
|
-
function stepTry(stmt,
|
|
385
|
+
function stepTry(stmt, path3, ctx) {
|
|
356
386
|
if (stmt.finallyBody !== null) {
|
|
357
387
|
const finallyHasTerminal = stmt.finallyBody.some(
|
|
358
388
|
(s) => containsTerminal(ctx, s)
|
|
@@ -362,22 +392,22 @@ function stepTry(stmt, path, ctx) {
|
|
|
362
392
|
}
|
|
363
393
|
}
|
|
364
394
|
const out = [];
|
|
365
|
-
out.push(...enumerate(ctx, stmt.tryBody,
|
|
395
|
+
out.push(...enumerate(ctx, stmt.tryBody, path3));
|
|
366
396
|
if (stmt.catchBody !== null) {
|
|
367
|
-
const catchPath = [...
|
|
397
|
+
const catchPath = [...path3, catchEntryCond()];
|
|
368
398
|
out.push(...enumerate(ctx, stmt.catchBody, catchPath));
|
|
369
399
|
}
|
|
370
400
|
return out;
|
|
371
401
|
}
|
|
372
|
-
function stepExit(stmt,
|
|
402
|
+
function stepExit(stmt, path3, ctx) {
|
|
373
403
|
const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
|
|
374
404
|
if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
|
|
375
|
-
collector.push(
|
|
405
|
+
collector.push(path3);
|
|
376
406
|
}
|
|
377
407
|
return [];
|
|
378
408
|
}
|
|
379
|
-
function stepOpaque(_stmt,
|
|
380
|
-
return [
|
|
409
|
+
function stepOpaque(_stmt, path3, _ctx) {
|
|
410
|
+
return [path3];
|
|
381
411
|
}
|
|
382
412
|
var stepHandlers = {
|
|
383
413
|
if: stepIf,
|
|
@@ -387,20 +417,20 @@ var stepHandlers = {
|
|
|
387
417
|
exit: stepExit,
|
|
388
418
|
opaque: stepOpaque
|
|
389
419
|
};
|
|
390
|
-
function stepCallback(ctx, body,
|
|
420
|
+
function stepCallback(ctx, body, path3) {
|
|
391
421
|
const exits = [];
|
|
392
422
|
ctx.state.callbackExits.push(exits);
|
|
393
423
|
let fellThrough;
|
|
394
424
|
try {
|
|
395
|
-
fellThrough = enumerate(ctx, body,
|
|
425
|
+
fellThrough = enumerate(ctx, body, path3);
|
|
396
426
|
} finally {
|
|
397
427
|
ctx.state.callbackExits.pop();
|
|
398
428
|
}
|
|
399
429
|
const out = [...fellThrough, ...exits];
|
|
400
|
-
return out.length === 0 ? [
|
|
430
|
+
return out.length === 0 ? [path3] : out;
|
|
401
431
|
}
|
|
402
|
-
function stepStatement(ctx, stmt,
|
|
403
|
-
let paths = [
|
|
432
|
+
function stepStatement(ctx, stmt, path3) {
|
|
433
|
+
let paths = [path3];
|
|
404
434
|
for (const body of stmt.callbacks ?? []) {
|
|
405
435
|
paths = paths.flatMap((each) => stepCallback(ctx, body, each));
|
|
406
436
|
}
|
|
@@ -481,8 +511,8 @@ function enumerate(ctx, stmts, prefix) {
|
|
|
481
511
|
let frontiers = [[...prefix]];
|
|
482
512
|
for (const stmt of stmts) {
|
|
483
513
|
const nextFrontiers = [];
|
|
484
|
-
for (const
|
|
485
|
-
nextFrontiers.push(...stepStatement(ctx, stmt,
|
|
514
|
+
for (const path3 of mergeRejoined(frontiers)) {
|
|
515
|
+
nextFrontiers.push(...stepStatement(ctx, stmt, path3));
|
|
486
516
|
}
|
|
487
517
|
frontiers = nextFrontiers;
|
|
488
518
|
if (frontiers.length > MAX_PATHS) {
|
|
@@ -533,9 +563,9 @@ function enumerateStructuredPaths(input) {
|
|
|
533
563
|
isAncestorOrSelf
|
|
534
564
|
};
|
|
535
565
|
const continuations = withRejoined(enumerate(ctx, input.statements, []));
|
|
536
|
-
for (const
|
|
566
|
+
for (const path3 of continuations) {
|
|
537
567
|
chargeBudget(state);
|
|
538
|
-
state.fallthrough.push(classify(
|
|
568
|
+
state.fallthrough.push(classify(path3, null, isAncestorOrSelf));
|
|
539
569
|
}
|
|
540
570
|
return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
|
|
541
571
|
}
|
|
@@ -730,6 +760,70 @@ function runsBefore(effectLine, terminalEndLine) {
|
|
|
730
760
|
return effectLine <= terminalEndLine;
|
|
731
761
|
}
|
|
732
762
|
|
|
763
|
+
// src/moduleImports.ts
|
|
764
|
+
function stampModuleImports(summaries, importsOf) {
|
|
765
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
766
|
+
for (const summary of summaries) {
|
|
767
|
+
const file = summary.location.file;
|
|
768
|
+
if (!byFile.has(file)) {
|
|
769
|
+
const found = importsOf(file);
|
|
770
|
+
byFile.set(
|
|
771
|
+
file,
|
|
772
|
+
found === void 0 ? void 0 : [...new Set(found)].sort()
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
const imports = byFile.get(file);
|
|
776
|
+
if (imports === void 0) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
summary.metadata = {
|
|
780
|
+
...summary.metadata ?? {},
|
|
781
|
+
moduleImports: imports
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/moduleInit.ts
|
|
787
|
+
function moduleInitStructure(options) {
|
|
788
|
+
const { name, file, range, effects } = options;
|
|
789
|
+
return {
|
|
790
|
+
identity: {
|
|
791
|
+
name,
|
|
792
|
+
kind: "module-init",
|
|
793
|
+
file,
|
|
794
|
+
range,
|
|
795
|
+
exportName: null,
|
|
796
|
+
exportPath: null
|
|
797
|
+
},
|
|
798
|
+
boundaryBinding: null,
|
|
799
|
+
parameters: [],
|
|
800
|
+
branches: [
|
|
801
|
+
{
|
|
802
|
+
conditions: [],
|
|
803
|
+
terminal: {
|
|
804
|
+
kind: "void",
|
|
805
|
+
statusCode: null,
|
|
806
|
+
body: null,
|
|
807
|
+
exceptionType: null,
|
|
808
|
+
message: null,
|
|
809
|
+
component: null,
|
|
810
|
+
renderTree: null,
|
|
811
|
+
delegateTarget: null,
|
|
812
|
+
emitEvent: null,
|
|
813
|
+
location: range
|
|
814
|
+
},
|
|
815
|
+
effects: [],
|
|
816
|
+
extraEffects: effects,
|
|
817
|
+
location: range,
|
|
818
|
+
isDefault: true
|
|
819
|
+
}
|
|
820
|
+
],
|
|
821
|
+
unmatchedReturns: 0,
|
|
822
|
+
dependencyCalls: [],
|
|
823
|
+
declaredContract: null
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
733
827
|
// src/packHelpers.ts
|
|
734
828
|
function httpRouteDiscovery(opts) {
|
|
735
829
|
const kind = opts.kind ?? "handler";
|
|
@@ -927,8 +1021,8 @@ function sharedGatingConditions(paths, readPredicate) {
|
|
|
927
1021
|
return [];
|
|
928
1022
|
}
|
|
929
1023
|
const shared = new Set(first.map(keyOf2));
|
|
930
|
-
for (const
|
|
931
|
-
const here = new Set(
|
|
1024
|
+
for (const path3 of rest) {
|
|
1025
|
+
const here = new Set(path3.map(keyOf2));
|
|
932
1026
|
for (const value of shared) {
|
|
933
1027
|
if (!here.has(value)) {
|
|
934
1028
|
shared.delete(value);
|
|
@@ -1057,6 +1151,841 @@ function valueToReadFurtherFrom(reading) {
|
|
|
1057
1151
|
return reading.kind === "written" ? reading.value : null;
|
|
1058
1152
|
}
|
|
1059
1153
|
|
|
1154
|
+
// src/adapterStamp.ts
|
|
1155
|
+
var import_node_crypto = require("crypto");
|
|
1156
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
1157
|
+
var import_node_module = require("module");
|
|
1158
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
1159
|
+
var import_node_url = require("url");
|
|
1160
|
+
var import_meta = {};
|
|
1161
|
+
var ANALYSIS_PACKAGES = [
|
|
1162
|
+
"@suss/extractor",
|
|
1163
|
+
"@suss/resolution",
|
|
1164
|
+
"@suss/datalog",
|
|
1165
|
+
"@suss/behavioral-ir"
|
|
1166
|
+
];
|
|
1167
|
+
var SOURCE_STAMP = { kind: "source" };
|
|
1168
|
+
function createAdapterStamp(config) {
|
|
1169
|
+
let cachedCodeStamp = null;
|
|
1170
|
+
let saidWhyNoCache = false;
|
|
1171
|
+
function codeStamp() {
|
|
1172
|
+
if (cachedCodeStamp !== null) {
|
|
1173
|
+
return cachedCodeStamp;
|
|
1174
|
+
}
|
|
1175
|
+
cachedCodeStamp = readAdapterCodeStamp(config.moduleUrl);
|
|
1176
|
+
return cachedCodeStamp;
|
|
1177
|
+
}
|
|
1178
|
+
function packsDigest(packVersions) {
|
|
1179
|
+
const sortedPacks = [...packVersions].map((p) => `${p.name}@${p.version ?? "unset"}`).sort();
|
|
1180
|
+
const stamp = codeStamp();
|
|
1181
|
+
const adapterStamp = stamp.kind === "bundle" ? `adapter@${config.version}+${stamp.hash}` : `adapter@${config.version}+source`;
|
|
1182
|
+
return `${adapterStamp}|${sortedPacks.join(",")}`;
|
|
1183
|
+
}
|
|
1184
|
+
function declineWhenRunFromSource(cacheDir) {
|
|
1185
|
+
if (cacheDir === null || codeStamp().kind === "bundle") {
|
|
1186
|
+
return cacheDir;
|
|
1187
|
+
}
|
|
1188
|
+
if (!saidWhyNoCache) {
|
|
1189
|
+
saidWhyNoCache = true;
|
|
1190
|
+
process.stderr.write(
|
|
1191
|
+
"[suss] extraction cache off: this process loaded the adapter from source, where nothing can tell one build of it from another. Run the built adapter to cache.\n"
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
return { codeStamp, packsDigest, declineWhenRunFromSource };
|
|
1197
|
+
}
|
|
1198
|
+
function readAdapterCodeStamp(moduleUrl) {
|
|
1199
|
+
try {
|
|
1200
|
+
const selfPath = (0, import_node_url.fileURLToPath)(moduleUrl);
|
|
1201
|
+
const hash = computeDistHashFrom(import_node_path.default.dirname(selfPath));
|
|
1202
|
+
return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
|
|
1203
|
+
} catch {
|
|
1204
|
+
return SOURCE_STAMP;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function computeDistHashFrom(dir) {
|
|
1208
|
+
const candidates = [import_node_path.default.join(dir, "index.js"), import_node_path.default.join(dir, "index.cjs")];
|
|
1209
|
+
for (const candidate of candidates) {
|
|
1210
|
+
if (!import_node_fs.default.existsSync(candidate)) {
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
return computeContentHash([candidate, ...analysisBundles()]);
|
|
1214
|
+
}
|
|
1215
|
+
return "";
|
|
1216
|
+
}
|
|
1217
|
+
function computeContentHash(paths) {
|
|
1218
|
+
if (paths.length === 0) {
|
|
1219
|
+
return "";
|
|
1220
|
+
}
|
|
1221
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
1222
|
+
for (const file of paths) {
|
|
1223
|
+
try {
|
|
1224
|
+
hash.update(import_node_fs.default.readFileSync(file));
|
|
1225
|
+
} catch {
|
|
1226
|
+
return "";
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return hash.digest("hex").slice(0, 16);
|
|
1230
|
+
}
|
|
1231
|
+
function projectFileStamp(paths) {
|
|
1232
|
+
if (paths.length === 0) {
|
|
1233
|
+
return "none";
|
|
1234
|
+
}
|
|
1235
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
1236
|
+
for (const file of [...paths].sort()) {
|
|
1237
|
+
hash.update(file);
|
|
1238
|
+
hash.update("\0");
|
|
1239
|
+
try {
|
|
1240
|
+
hash.update(import_node_fs.default.readFileSync(file));
|
|
1241
|
+
} catch {
|
|
1242
|
+
hash.update("absent");
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return hash.digest("hex").slice(0, 16);
|
|
1246
|
+
}
|
|
1247
|
+
function runDigest(packsDigest, packs, files) {
|
|
1248
|
+
const inputs = packs.flatMap((pack) => pack.discoveryInputs?.(files) ?? []);
|
|
1249
|
+
return `${packsDigest}|reads:${projectFileStamp(inputs)}`;
|
|
1250
|
+
}
|
|
1251
|
+
function analysisBundles() {
|
|
1252
|
+
const require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
1253
|
+
const found = [];
|
|
1254
|
+
for (const name of ANALYSIS_PACKAGES) {
|
|
1255
|
+
try {
|
|
1256
|
+
found.push(require2.resolve(name));
|
|
1257
|
+
} catch {
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return found;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// src/cache.ts
|
|
1264
|
+
var import_node_crypto2 = require("crypto");
|
|
1265
|
+
var import_promises = __toESM(require("fs/promises"), 1);
|
|
1266
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
1267
|
+
var SCHEMA_VERSION = "7";
|
|
1268
|
+
var MAX_ENTRIES = 2;
|
|
1269
|
+
var ENTRY_PREFIX = "key-";
|
|
1270
|
+
var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
|
|
1271
|
+
function createCacheLayer(cacheDir) {
|
|
1272
|
+
if (cacheDir === null) {
|
|
1273
|
+
return {
|
|
1274
|
+
tryHit: async () => null,
|
|
1275
|
+
lookup: async () => ({
|
|
1276
|
+
kind: "miss",
|
|
1277
|
+
diagnostic: { kind: "miss", missReason: "no-manifest" }
|
|
1278
|
+
}),
|
|
1279
|
+
plan: async () => null,
|
|
1280
|
+
write: async () => {
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
return {
|
|
1285
|
+
async tryHit(input) {
|
|
1286
|
+
const result = await this.lookup(input);
|
|
1287
|
+
return result.kind === "hit" ? result.summaries : null;
|
|
1288
|
+
},
|
|
1289
|
+
async lookup(input) {
|
|
1290
|
+
const entryDir = entryDirFor(cacheDir, input);
|
|
1291
|
+
const manifest = await readManifest(
|
|
1292
|
+
import_node_path2.default.join(entryDir, "manifest.json")
|
|
1293
|
+
);
|
|
1294
|
+
if (manifest === null) {
|
|
1295
|
+
return missDiag(await describeAbsentEntry(cacheDir));
|
|
1296
|
+
}
|
|
1297
|
+
const currentConfigStamp = await stampConfigFile(input.configPath);
|
|
1298
|
+
if (!fileStampEquals(manifest.configStamp, currentConfigStamp)) {
|
|
1299
|
+
return missDiag("config-changed");
|
|
1300
|
+
}
|
|
1301
|
+
const currentFiles = await resolveFileStamps(input);
|
|
1302
|
+
if (!fileStampsEqual(manifest.files, currentFiles)) {
|
|
1303
|
+
return missDiag("files-changed");
|
|
1304
|
+
}
|
|
1305
|
+
await markUsed(entryDir);
|
|
1306
|
+
return {
|
|
1307
|
+
kind: "hit",
|
|
1308
|
+
summaries: manifest.summaries,
|
|
1309
|
+
diagnostic: { kind: "hit" }
|
|
1310
|
+
};
|
|
1311
|
+
},
|
|
1312
|
+
async plan(input) {
|
|
1313
|
+
const entryDir = entryDirFor(cacheDir, input);
|
|
1314
|
+
const manifest = await readManifest(
|
|
1315
|
+
import_node_path2.default.join(entryDir, "manifest.json")
|
|
1316
|
+
);
|
|
1317
|
+
if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
|
|
1318
|
+
return null;
|
|
1319
|
+
}
|
|
1320
|
+
const currentConfigStamp = await stampConfigFile(input.configPath);
|
|
1321
|
+
if (!fileStampEquals(manifest.configStamp, currentConfigStamp)) {
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
await markUsed(entryDir);
|
|
1325
|
+
return buildPlan(manifest, await resolveFileStamps(input));
|
|
1326
|
+
},
|
|
1327
|
+
async write(input, summaries, attribution) {
|
|
1328
|
+
const entryDir = entryDirFor(cacheDir, input);
|
|
1329
|
+
const previous = await readManifest(
|
|
1330
|
+
import_node_path2.default.join(entryDir, "manifest.json")
|
|
1331
|
+
);
|
|
1332
|
+
const configStamp = await stampConfigFile(input.configPath);
|
|
1333
|
+
const files = await hashStamps(await resolveFileStamps(input), previous);
|
|
1334
|
+
const manifest = {
|
|
1335
|
+
schemaVersion: SCHEMA_VERSION,
|
|
1336
|
+
adapterPacksDigest: input.adapterPacksDigest,
|
|
1337
|
+
configStamp,
|
|
1338
|
+
files,
|
|
1339
|
+
summaries,
|
|
1340
|
+
...attribution === void 0 ? {} : encodeAttribution(attribution)
|
|
1341
|
+
};
|
|
1342
|
+
await import_promises.default.mkdir(entryDir, { recursive: true });
|
|
1343
|
+
await import_promises.default.writeFile(
|
|
1344
|
+
import_node_path2.default.join(entryDir, "manifest.json"),
|
|
1345
|
+
JSON.stringify(manifest)
|
|
1346
|
+
);
|
|
1347
|
+
await evictOldEntries(cacheDir, entryDir);
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
async function buildPlan(manifest, currentStamps) {
|
|
1352
|
+
const stored = new Map(manifest.files.map((f) => [f.path, f]));
|
|
1353
|
+
const current = new Map(currentStamps.map((f) => [f.path, f]));
|
|
1354
|
+
const changed = /* @__PURE__ */ new Set();
|
|
1355
|
+
const removed = /* @__PURE__ */ new Set();
|
|
1356
|
+
for (const p of stored.keys()) {
|
|
1357
|
+
if (!current.has(p)) {
|
|
1358
|
+
removed.add(p);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const toVerify = [];
|
|
1362
|
+
for (const [p, stamp] of current) {
|
|
1363
|
+
const before = stored.get(p);
|
|
1364
|
+
if (before === void 0) {
|
|
1365
|
+
changed.add(p);
|
|
1366
|
+
} else if (!fileStampEquals(before, stamp)) {
|
|
1367
|
+
if (before.contentHash === void 0) {
|
|
1368
|
+
changed.add(p);
|
|
1369
|
+
} else {
|
|
1370
|
+
toVerify.push(p);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
await Promise.all(
|
|
1375
|
+
toVerify.map(async (p) => {
|
|
1376
|
+
const hash = await hashFile(p);
|
|
1377
|
+
if (hash === null || hash !== stored.get(p)?.contentHash) {
|
|
1378
|
+
changed.add(p);
|
|
1379
|
+
}
|
|
1380
|
+
})
|
|
1381
|
+
);
|
|
1382
|
+
const roots = /* @__PURE__ */ new Map();
|
|
1383
|
+
const rootNames = manifest.roots ?? [];
|
|
1384
|
+
const depPaths = manifest.depPaths ?? [];
|
|
1385
|
+
let rootsDeclined = 0;
|
|
1386
|
+
rootNames.forEach((rootPath, i) => {
|
|
1387
|
+
const meta = manifest.rootMeta?.[i];
|
|
1388
|
+
if (meta === void 0) {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
if (!meta.cacheable) {
|
|
1392
|
+
rootsDeclined += 1;
|
|
1393
|
+
}
|
|
1394
|
+
roots.set(rootPath, {
|
|
1395
|
+
path: rootPath,
|
|
1396
|
+
cacheable: meta.cacheable,
|
|
1397
|
+
deps: meta.deps.flatMap((d) => {
|
|
1398
|
+
const p = depPaths[d];
|
|
1399
|
+
return p === void 0 ? [] : [p];
|
|
1400
|
+
}),
|
|
1401
|
+
claims: meta.claims,
|
|
1402
|
+
meta: meta.meta,
|
|
1403
|
+
packs: meta.packs
|
|
1404
|
+
});
|
|
1405
|
+
});
|
|
1406
|
+
const validRoots = /* @__PURE__ */ new Set();
|
|
1407
|
+
for (const [rootPath, record] of roots) {
|
|
1408
|
+
if (!record.cacheable || !current.has(rootPath)) {
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
if (changed.has(rootPath)) {
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
const depMoved = record.deps.some((d) => changed.has(d) || removed.has(d));
|
|
1415
|
+
if (!depMoved) {
|
|
1416
|
+
validRoots.add(rootPath);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
const owners = manifest.owners ?? [];
|
|
1420
|
+
return {
|
|
1421
|
+
changed,
|
|
1422
|
+
removed,
|
|
1423
|
+
roots,
|
|
1424
|
+
validRoots,
|
|
1425
|
+
rootsDeclined,
|
|
1426
|
+
reuse(valid) {
|
|
1427
|
+
const summaries = [];
|
|
1428
|
+
const reusedOwners = [];
|
|
1429
|
+
manifest.summaries.forEach((summary, i) => {
|
|
1430
|
+
const ownerPaths = (owners[i] ?? []).flatMap((o) => {
|
|
1431
|
+
const p = rootNames[o];
|
|
1432
|
+
return p === void 0 ? [] : [p];
|
|
1433
|
+
});
|
|
1434
|
+
if (ownerPaths.some((p) => valid.has(p))) {
|
|
1435
|
+
summaries.push(summary);
|
|
1436
|
+
reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
return { summaries, owners: reusedOwners };
|
|
1440
|
+
},
|
|
1441
|
+
allSummaries() {
|
|
1442
|
+
return manifest.summaries;
|
|
1443
|
+
},
|
|
1444
|
+
attribution() {
|
|
1445
|
+
return {
|
|
1446
|
+
roots: [...roots.values()],
|
|
1447
|
+
owners: owners.map(
|
|
1448
|
+
(ownerIds) => ownerIds.flatMap((o) => {
|
|
1449
|
+
const p = rootNames[o];
|
|
1450
|
+
return p === void 0 ? [] : [p];
|
|
1451
|
+
})
|
|
1452
|
+
)
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
function encodeAttribution(attribution) {
|
|
1458
|
+
const roots = attribution.roots.map((r) => r.path);
|
|
1459
|
+
const rootIndex = new Map(roots.map((p, i) => [p, i]));
|
|
1460
|
+
const depIndex = /* @__PURE__ */ new Map();
|
|
1461
|
+
const depPaths = [];
|
|
1462
|
+
const depIdOf = (p) => {
|
|
1463
|
+
const existing = depIndex.get(p);
|
|
1464
|
+
if (existing !== void 0) {
|
|
1465
|
+
return existing;
|
|
1466
|
+
}
|
|
1467
|
+
const id = depPaths.length;
|
|
1468
|
+
depPaths.push(p);
|
|
1469
|
+
depIndex.set(p, id);
|
|
1470
|
+
return id;
|
|
1471
|
+
};
|
|
1472
|
+
const rootMeta = attribution.roots.map((r) => ({
|
|
1473
|
+
cacheable: r.cacheable,
|
|
1474
|
+
deps: r.deps.map(depIdOf),
|
|
1475
|
+
claims: r.claims,
|
|
1476
|
+
meta: r.meta,
|
|
1477
|
+
packs: r.packs
|
|
1478
|
+
}));
|
|
1479
|
+
const owners = attribution.owners.map(
|
|
1480
|
+
(ownerPaths) => ownerPaths.flatMap((p) => {
|
|
1481
|
+
const i = rootIndex.get(p);
|
|
1482
|
+
return i === void 0 ? [] : [i];
|
|
1483
|
+
})
|
|
1484
|
+
);
|
|
1485
|
+
return { roots, rootMeta, depPaths, owners };
|
|
1486
|
+
}
|
|
1487
|
+
async function hashStamps(stamps, previous) {
|
|
1488
|
+
const before = new Map((previous?.files ?? []).map((f) => [f.path, f]));
|
|
1489
|
+
return Promise.all(
|
|
1490
|
+
stamps.map(async (stamp) => {
|
|
1491
|
+
const prior = before.get(stamp.path);
|
|
1492
|
+
if (prior !== void 0 && prior.contentHash !== void 0 && fileStampEquals(prior, stamp)) {
|
|
1493
|
+
return { ...stamp, contentHash: prior.contentHash };
|
|
1494
|
+
}
|
|
1495
|
+
const contentHash = await hashFile(stamp.path);
|
|
1496
|
+
return contentHash === null ? stamp : { ...stamp, contentHash };
|
|
1497
|
+
})
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
async function hashFile(filePath) {
|
|
1501
|
+
try {
|
|
1502
|
+
const content = await import_promises.default.readFile(filePath);
|
|
1503
|
+
return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
1504
|
+
} catch {
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
async function describeAbsentEntry(cacheDir) {
|
|
1509
|
+
try {
|
|
1510
|
+
const entries = await import_promises.default.readdir(cacheDir, { withFileTypes: true });
|
|
1511
|
+
return entries.some((e) => e.isDirectory() && isEntryDir(e.name)) ? "key-changed" : "no-manifest";
|
|
1512
|
+
} catch {
|
|
1513
|
+
return "no-manifest";
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function entryDirFor(cacheDir, input) {
|
|
1517
|
+
const key = [
|
|
1518
|
+
SCHEMA_VERSION,
|
|
1519
|
+
input.adapterPacksDigest,
|
|
1520
|
+
input.configPath ?? ""
|
|
1521
|
+
].join(" ");
|
|
1522
|
+
const name = (0, import_node_crypto2.createHash)("sha256").update(key).digest("hex").slice(0, 16);
|
|
1523
|
+
return import_node_path2.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
|
|
1524
|
+
}
|
|
1525
|
+
function isEntryDir(name) {
|
|
1526
|
+
return ENTRY_DIR_NAME.test(name);
|
|
1527
|
+
}
|
|
1528
|
+
async function markUsed(entryDir) {
|
|
1529
|
+
const now = /* @__PURE__ */ new Date();
|
|
1530
|
+
try {
|
|
1531
|
+
await import_promises.default.utimes(entryDir, now, now);
|
|
1532
|
+
} catch {
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
async function evictOldEntries(cacheDir, keepDir) {
|
|
1536
|
+
try {
|
|
1537
|
+
await import_promises.default.rm(import_node_path2.default.join(cacheDir, "manifest.json"), { force: true });
|
|
1538
|
+
const entries = await import_promises.default.readdir(cacheDir, { withFileTypes: true });
|
|
1539
|
+
const dirs = await Promise.all(
|
|
1540
|
+
entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
|
|
1541
|
+
const dir = import_node_path2.default.join(cacheDir, e.name);
|
|
1542
|
+
const stat = await import_promises.default.stat(dir);
|
|
1543
|
+
return { dir, mtimeMs: stat.mtimeMs };
|
|
1544
|
+
})
|
|
1545
|
+
);
|
|
1546
|
+
const doomed = dirs.filter((d) => d.dir !== keepDir).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(MAX_ENTRIES - 1);
|
|
1547
|
+
await Promise.all(
|
|
1548
|
+
doomed.map((d) => import_promises.default.rm(d.dir, { recursive: true, force: true }))
|
|
1549
|
+
);
|
|
1550
|
+
} catch {
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
function missDiag(reason) {
|
|
1554
|
+
return {
|
|
1555
|
+
kind: "miss",
|
|
1556
|
+
diagnostic: { kind: "miss", missReason: reason }
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
async function readManifest(manifestPath) {
|
|
1560
|
+
try {
|
|
1561
|
+
const raw = await import_promises.default.readFile(manifestPath, "utf-8");
|
|
1562
|
+
return JSON.parse(raw);
|
|
1563
|
+
} catch {
|
|
1564
|
+
return null;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
async function stampConfigFile(configPath) {
|
|
1568
|
+
if (configPath === void 0) {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
try {
|
|
1572
|
+
const stat = await import_promises.default.stat(configPath);
|
|
1573
|
+
return { path: configPath, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
1574
|
+
} catch {
|
|
1575
|
+
return null;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
async function resolveFileStamps(input) {
|
|
1579
|
+
const stamped = await Promise.all(
|
|
1580
|
+
input.files.map(async (p) => {
|
|
1581
|
+
try {
|
|
1582
|
+
const stat = await import_promises.default.stat(p);
|
|
1583
|
+
return { path: p, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
1584
|
+
} catch {
|
|
1585
|
+
return { path: p, mtimeMs: -1, size: -1 };
|
|
1586
|
+
}
|
|
1587
|
+
})
|
|
1588
|
+
);
|
|
1589
|
+
stamped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
1590
|
+
return stamped;
|
|
1591
|
+
}
|
|
1592
|
+
function fileStampsEqual(a, b) {
|
|
1593
|
+
return a.length === b.length && a.every((s, i) => fileStampEquals(s, b[i]));
|
|
1594
|
+
}
|
|
1595
|
+
function fileStampEquals(a, b) {
|
|
1596
|
+
if (a === null && b === null) {
|
|
1597
|
+
return true;
|
|
1598
|
+
}
|
|
1599
|
+
if (a === null || b === null) {
|
|
1600
|
+
return false;
|
|
1601
|
+
}
|
|
1602
|
+
return a.path === b.path && a.mtimeMs === b.mtimeMs && a.size === b.size;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// src/extractionReport.ts
|
|
1606
|
+
var import_behavioral_ir3 = require("@suss/behavioral-ir");
|
|
1607
|
+
var emptyTally = () => ({
|
|
1608
|
+
candidateFiles: 0,
|
|
1609
|
+
unitsDiscovered: 0,
|
|
1610
|
+
unitsInGatedFiles: 0,
|
|
1611
|
+
effectsRecognized: 0,
|
|
1612
|
+
unitsClaimed: 0,
|
|
1613
|
+
selfCollisions: 0,
|
|
1614
|
+
summariesProduced: 0,
|
|
1615
|
+
failures: [],
|
|
1616
|
+
helpersMatched: /* @__PURE__ */ new Set()
|
|
1617
|
+
});
|
|
1618
|
+
function recordPackFailure(tally, failure) {
|
|
1619
|
+
const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
|
|
1620
|
+
tally?.failures.push({
|
|
1621
|
+
hook: failure.hook,
|
|
1622
|
+
file: failure.file,
|
|
1623
|
+
message
|
|
1624
|
+
});
|
|
1625
|
+
return `[suss] pack "${failure.pack}" threw from ${failure.hook} while reading ${failure.file}: ${message}
|
|
1626
|
+
`;
|
|
1627
|
+
}
|
|
1628
|
+
function createPackTallies(packs) {
|
|
1629
|
+
const tallies = /* @__PURE__ */ new Map();
|
|
1630
|
+
for (const pack of packs) {
|
|
1631
|
+
tallies.set(pack.name, emptyTally());
|
|
1632
|
+
}
|
|
1633
|
+
return tallies;
|
|
1634
|
+
}
|
|
1635
|
+
function tallyUnit(tallies, recognition) {
|
|
1636
|
+
const tally = recognition === void 0 ? void 0 : tallies.get(recognition);
|
|
1637
|
+
if (tally === void 0) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
tally.unitsDiscovered += 1;
|
|
1641
|
+
tally.summariesProduced += 1;
|
|
1642
|
+
}
|
|
1643
|
+
var emptyCounts = () => ({
|
|
1644
|
+
bound: 0,
|
|
1645
|
+
providers: 0,
|
|
1646
|
+
withBehavior: 0
|
|
1647
|
+
});
|
|
1648
|
+
function summaryCountsByPack(summaries) {
|
|
1649
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1650
|
+
for (const summary of summaries) {
|
|
1651
|
+
const recognition = summary.identity.boundaryBinding?.recognition;
|
|
1652
|
+
if (recognition === void 0 || recognition === null) {
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
const entry = counts.get(recognition) ?? emptyCounts();
|
|
1656
|
+
entry.bound += 1;
|
|
1657
|
+
if (import_behavioral_ir3.BOUNDARY_ROLE[summary.kind] === "provider") {
|
|
1658
|
+
entry.providers += 1;
|
|
1659
|
+
}
|
|
1660
|
+
if (summary.transitions.length > 0) {
|
|
1661
|
+
entry.withBehavior += 1;
|
|
1662
|
+
}
|
|
1663
|
+
counts.set(recognition, entry);
|
|
1664
|
+
}
|
|
1665
|
+
return counts;
|
|
1666
|
+
}
|
|
1667
|
+
function buildUngatedExtractionReport(args) {
|
|
1668
|
+
const bySummary = summaryCountsByPack(args.summaries);
|
|
1669
|
+
const packFunnels = args.packs.map((pack) => {
|
|
1670
|
+
const tally = args.tallies.get(pack.name) ?? emptyTally();
|
|
1671
|
+
const counted = bySummary.get(pack.name) ?? emptyCounts();
|
|
1672
|
+
return {
|
|
1673
|
+
pack: pack.name,
|
|
1674
|
+
version: pack.version,
|
|
1675
|
+
discovers: pack.discovers,
|
|
1676
|
+
recognizes: false,
|
|
1677
|
+
gates: [],
|
|
1678
|
+
unresolvedGates: [],
|
|
1679
|
+
candidateFiles: args.filesWalked,
|
|
1680
|
+
unitsDiscovered: tally.unitsDiscovered,
|
|
1681
|
+
unitsInGatedFiles: 0,
|
|
1682
|
+
effectsRecognized: 0,
|
|
1683
|
+
unitsClaimed: tally.unitsDiscovered,
|
|
1684
|
+
selfCollisions: 0,
|
|
1685
|
+
summariesProduced: tally.summariesProduced,
|
|
1686
|
+
failures: tally.failures,
|
|
1687
|
+
helpersUnmatched: [],
|
|
1688
|
+
summariesBound: counted.bound,
|
|
1689
|
+
providerSummaries: counted.providers,
|
|
1690
|
+
summariesWithBehavior: counted.withBehavior,
|
|
1691
|
+
declarations: null
|
|
1692
|
+
};
|
|
1693
|
+
});
|
|
1694
|
+
return {
|
|
1695
|
+
filesInProject: null,
|
|
1696
|
+
filesWalked: args.filesWalked,
|
|
1697
|
+
packs: packFunnels,
|
|
1698
|
+
summaries: args.summaries.length,
|
|
1699
|
+
filesWithUnreadableExports: [],
|
|
1700
|
+
reassignedNamesUnstated: 0,
|
|
1701
|
+
emptyStage: args.summaries.length === 0 ? firstEmptyUngatedStage(packFunnels, args.filesWalked) : null
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
function firstEmptyUngatedStage(packs, filesWalked) {
|
|
1705
|
+
if (filesWalked === 0) {
|
|
1706
|
+
return "candidateFiles";
|
|
1707
|
+
}
|
|
1708
|
+
if (packs.every((p) => p.unitsDiscovered === 0)) {
|
|
1709
|
+
return "discovery";
|
|
1710
|
+
}
|
|
1711
|
+
return "assembly";
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// src/packHealth.ts
|
|
1715
|
+
function stagesOf(funnel) {
|
|
1716
|
+
const stages = [];
|
|
1717
|
+
const gateSaysSomething = funnel.gates.length > 0 && funnel.unresolvedGates.length === 0;
|
|
1718
|
+
if (funnel.recognizes && !funnel.discovers && gateSaysSomething) {
|
|
1719
|
+
stages.push({
|
|
1720
|
+
from: { count: funnel.unitsInGatedFiles, name: "unit bodies" },
|
|
1721
|
+
to: { count: funnel.effectsRecognized, name: "effects" }
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
if (funnel.discovers && gateSaysSomething) {
|
|
1725
|
+
stages.push({
|
|
1726
|
+
from: { count: funnel.candidateFiles, name: "source files" },
|
|
1727
|
+
to: { count: funnel.unitsDiscovered, name: "units" }
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
stages.push(
|
|
1731
|
+
{
|
|
1732
|
+
from: { count: funnel.unitsClaimed, name: "units" },
|
|
1733
|
+
to: { count: funnel.summariesBound, name: "summaries" }
|
|
1734
|
+
},
|
|
1735
|
+
{
|
|
1736
|
+
from: { count: funnel.summariesBound, name: "summaries" },
|
|
1737
|
+
to: { count: funnel.summariesWithBehavior, name: "transitions" }
|
|
1738
|
+
}
|
|
1739
|
+
);
|
|
1740
|
+
return stages;
|
|
1741
|
+
}
|
|
1742
|
+
function funnelDrops(packs) {
|
|
1743
|
+
const violations = [];
|
|
1744
|
+
for (const funnel of packs) {
|
|
1745
|
+
for (const stage of stagesOf(funnel)) {
|
|
1746
|
+
if (stage.from.count === 0 || stage.to.count > 0) {
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1749
|
+
violations.push({
|
|
1750
|
+
label: funnel.pack,
|
|
1751
|
+
detail: `${stage.from.count} ${stage.from.name} -> 0 ${stage.to.name}`
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
return violations;
|
|
1756
|
+
}
|
|
1757
|
+
function recognizersWithNoUnits(packs) {
|
|
1758
|
+
return packs.filter(
|
|
1759
|
+
(funnel) => funnel.recognizes && !funnel.discovers && funnel.gates.length > 0 && funnel.unresolvedGates.length === 0 && funnel.candidateFiles > 0 && funnel.unitsInGatedFiles === 0 && // The closure walks a recognizer-only pack's gated exports as
|
|
1760
|
+
// roots, so effects recognized there mean the run worked and
|
|
1761
|
+
// the missing framework pack costs attribution, not existence.
|
|
1762
|
+
funnel.effectsRecognized === 0
|
|
1763
|
+
).map((funnel) => ({
|
|
1764
|
+
label: funnel.pack,
|
|
1765
|
+
detail: `${funnel.candidateFiles} gated files, and no pack in this run discovered a unit in them. ${funnel.pack} reads calls inside units another pack finds, so add the pack that finds this project's handlers (-f express, -f fastify, ...), or run suss init to work out which.`
|
|
1766
|
+
}));
|
|
1767
|
+
}
|
|
1768
|
+
function helpersThatMatchedNothing(packs) {
|
|
1769
|
+
return packs.flatMap(
|
|
1770
|
+
(funnel) => funnel.helpersUnmatched.map((helper) => ({
|
|
1771
|
+
label: funnel.pack,
|
|
1772
|
+
detail: `${helper} matched no call in this run, so whatever it registers is missing. suss read that helper out of the project and then failed to match a call to it, which is a bug in suss rather than in your code.`
|
|
1773
|
+
}))
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
function unversionedPacks(packs) {
|
|
1777
|
+
return packs.filter((funnel) => funnel.version === null).map((funnel) => ({
|
|
1778
|
+
label: funnel.pack,
|
|
1779
|
+
detail: "no version declared"
|
|
1780
|
+
}));
|
|
1781
|
+
}
|
|
1782
|
+
function selfCollisions(packs) {
|
|
1783
|
+
return packs.filter((funnel) => funnel.selfCollisions > 0).map((funnel) => ({
|
|
1784
|
+
label: funnel.pack,
|
|
1785
|
+
detail: `${funnel.selfCollisions} ${funnel.selfCollisions === 1 ? "unit" : "units"} matched twice, second dropped`
|
|
1786
|
+
}));
|
|
1787
|
+
}
|
|
1788
|
+
function threwWhileReading(packs) {
|
|
1789
|
+
return packs.filter((funnel) => funnel.failures.length > 0).map((funnel) => {
|
|
1790
|
+
const first = funnel.failures[0];
|
|
1791
|
+
const rest = funnel.failures.length - 1;
|
|
1792
|
+
const alsoIn = rest > 0 ? ` (+${rest} more)` : "";
|
|
1793
|
+
return {
|
|
1794
|
+
label: funnel.pack,
|
|
1795
|
+
detail: `${first.hook} on ${first.file}${alsoIn}: ${first.message}`
|
|
1796
|
+
};
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
function packGradients(report) {
|
|
1800
|
+
const gradients = [];
|
|
1801
|
+
for (const funnel of report.packs) {
|
|
1802
|
+
if (funnel.declarations === null) {
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
gradients.push(gradientOf(funnel.pack, funnel.declarations.declarations));
|
|
1806
|
+
}
|
|
1807
|
+
return gradients;
|
|
1808
|
+
}
|
|
1809
|
+
function gradientOf(pack, declarations) {
|
|
1810
|
+
const gradient = {
|
|
1811
|
+
pack,
|
|
1812
|
+
dataLinks: 0,
|
|
1813
|
+
functionLinks: [],
|
|
1814
|
+
astLinks: [],
|
|
1815
|
+
withoutExample: []
|
|
1816
|
+
};
|
|
1817
|
+
for (const declaration of declarations) {
|
|
1818
|
+
gradient.dataLinks += declaration.dataLinks;
|
|
1819
|
+
for (const question of declaration.functionLinks) {
|
|
1820
|
+
gradient.functionLinks.push(`${declaration.name}.${question}`);
|
|
1821
|
+
}
|
|
1822
|
+
for (const question of declaration.astLinks) {
|
|
1823
|
+
gradient.astLinks.push(`${declaration.name}.${question}`);
|
|
1824
|
+
}
|
|
1825
|
+
if (declaration.example === null) {
|
|
1826
|
+
gradient.withoutExample.push(declaration.name);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
return gradient;
|
|
1830
|
+
}
|
|
1831
|
+
function opaqueLinks(gradients) {
|
|
1832
|
+
return gradients.filter((gradient) => gradient.functionLinks.length > 0).map((gradient) => ({
|
|
1833
|
+
label: gradient.pack,
|
|
1834
|
+
detail: `${gradient.functionLinks.join(", ")} (${gradient.dataLinks} data)`
|
|
1835
|
+
}));
|
|
1836
|
+
}
|
|
1837
|
+
function reachesTheSyntaxTree(gradients) {
|
|
1838
|
+
return gradients.filter((gradient) => gradient.astLinks.length > 0).map((gradient) => ({
|
|
1839
|
+
label: gradient.pack,
|
|
1840
|
+
detail: gradient.astLinks.join(", ")
|
|
1841
|
+
}));
|
|
1842
|
+
}
|
|
1843
|
+
function undocumentedDeclarations(gradients) {
|
|
1844
|
+
return gradients.filter((gradient) => gradient.withoutExample.length > 0).map((gradient) => ({
|
|
1845
|
+
label: gradient.pack,
|
|
1846
|
+
detail: gradient.withoutExample.join(", ")
|
|
1847
|
+
}));
|
|
1848
|
+
}
|
|
1849
|
+
function evaluatePackHealth(report) {
|
|
1850
|
+
const gradients = packGradients(report);
|
|
1851
|
+
return [
|
|
1852
|
+
{
|
|
1853
|
+
name: "no pack throws while it reads",
|
|
1854
|
+
code: "threw",
|
|
1855
|
+
audience: "run",
|
|
1856
|
+
violations: threwWhileReading(report.packs)
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
name: "no pack finds something and records nothing",
|
|
1860
|
+
code: "no-output",
|
|
1861
|
+
audience: "run",
|
|
1862
|
+
violations: funnelDrops(report.packs)
|
|
1863
|
+
},
|
|
1864
|
+
{
|
|
1865
|
+
name: "no pack collides with itself",
|
|
1866
|
+
code: "double-match",
|
|
1867
|
+
audience: "run",
|
|
1868
|
+
violations: selfCollisions(report.packs)
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
name: "every recognizer had units to look inside",
|
|
1872
|
+
code: "no-units",
|
|
1873
|
+
audience: "run",
|
|
1874
|
+
violations: recognizersWithNoUnits(report.packs)
|
|
1875
|
+
},
|
|
1876
|
+
{
|
|
1877
|
+
name: "every registration helper the run read matched a call",
|
|
1878
|
+
code: "no-helper",
|
|
1879
|
+
audience: "run",
|
|
1880
|
+
violations: helpersThatMatchedNothing(report.packs)
|
|
1881
|
+
},
|
|
1882
|
+
{
|
|
1883
|
+
name: "every pack declares a version",
|
|
1884
|
+
code: "no-version",
|
|
1885
|
+
audience: "pack",
|
|
1886
|
+
violations: unversionedPacks(report.packs)
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
name: "every link a declared pack states is data",
|
|
1890
|
+
code: "fn-link",
|
|
1891
|
+
audience: "pack",
|
|
1892
|
+
violations: opaqueLinks(gradients)
|
|
1893
|
+
},
|
|
1894
|
+
{
|
|
1895
|
+
name: "no declared pack reads the syntax tree",
|
|
1896
|
+
code: "ast-link",
|
|
1897
|
+
audience: "pack",
|
|
1898
|
+
violations: reachesTheSyntaxTree(gradients)
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
name: "every declaration states an example",
|
|
1902
|
+
code: "no-example",
|
|
1903
|
+
audience: "pack",
|
|
1904
|
+
violations: undocumentedDeclarations(gradients)
|
|
1905
|
+
}
|
|
1906
|
+
];
|
|
1907
|
+
}
|
|
1908
|
+
function formatPackHealth(checks, audiences) {
|
|
1909
|
+
const fired = checks.filter(
|
|
1910
|
+
(check) => check.violations.length > 0 && audiences.includes(check.audience)
|
|
1911
|
+
);
|
|
1912
|
+
if (fired.length === 0) {
|
|
1913
|
+
return "";
|
|
1914
|
+
}
|
|
1915
|
+
const rows = fired.flatMap(
|
|
1916
|
+
(check) => check.violations.map((violation) => ({
|
|
1917
|
+
code: check.code,
|
|
1918
|
+
pack: violation.label,
|
|
1919
|
+
detail: violation.detail
|
|
1920
|
+
}))
|
|
1921
|
+
);
|
|
1922
|
+
const codeWidth = Math.max(...rows.map((row) => row.code.length));
|
|
1923
|
+
const packWidth = Math.max(...rows.map((row) => row.pack.length));
|
|
1924
|
+
const lines = ["", `Pack health (${rows.length}):`];
|
|
1925
|
+
for (const row of rows) {
|
|
1926
|
+
lines.push(
|
|
1927
|
+
` ${row.code.padEnd(codeWidth)} ${row.pack.padEnd(packWidth)} ${row.detail}`
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
return `${lines.join("\n")}
|
|
1931
|
+
`;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// src/timing.ts
|
|
1935
|
+
function createTimer() {
|
|
1936
|
+
const phases = /* @__PURE__ */ new Map();
|
|
1937
|
+
const start = performance.now();
|
|
1938
|
+
function record(label, deltaMs) {
|
|
1939
|
+
const existing = phases.get(label);
|
|
1940
|
+
if (existing === void 0) {
|
|
1941
|
+
phases.set(label, { durationMs: deltaMs, calls: 1 });
|
|
1942
|
+
} else {
|
|
1943
|
+
existing.durationMs += deltaMs;
|
|
1944
|
+
existing.calls += 1;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
return {
|
|
1948
|
+
time(label, fn) {
|
|
1949
|
+
const t0 = performance.now();
|
|
1950
|
+
try {
|
|
1951
|
+
return fn();
|
|
1952
|
+
} finally {
|
|
1953
|
+
record(label, performance.now() - t0);
|
|
1954
|
+
}
|
|
1955
|
+
},
|
|
1956
|
+
async timeAsync(label, fn) {
|
|
1957
|
+
const t0 = performance.now();
|
|
1958
|
+
try {
|
|
1959
|
+
return await fn();
|
|
1960
|
+
} finally {
|
|
1961
|
+
record(label, performance.now() - t0);
|
|
1962
|
+
}
|
|
1963
|
+
},
|
|
1964
|
+
report() {
|
|
1965
|
+
const totalMs = performance.now() - start;
|
|
1966
|
+
const ordered = [...phases.entries()].map(([label, s]) => ({
|
|
1967
|
+
label,
|
|
1968
|
+
durationMs: s.durationMs,
|
|
1969
|
+
calls: s.calls
|
|
1970
|
+
})).sort((a, b) => b.durationMs - a.durationMs);
|
|
1971
|
+
return { totalMs, phases: ordered };
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
function noopTimer() {
|
|
1976
|
+
return {
|
|
1977
|
+
time(_label, fn) {
|
|
1978
|
+
return fn();
|
|
1979
|
+
},
|
|
1980
|
+
async timeAsync(_label, fn) {
|
|
1981
|
+
return fn();
|
|
1982
|
+
},
|
|
1983
|
+
report() {
|
|
1984
|
+
return { totalMs: 0, phases: [] };
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1060
1989
|
// src/index.ts
|
|
1061
1990
|
function valuesOfOutput(output) {
|
|
1062
1991
|
if (output.type === "response" && output.statusCode !== null) {
|
|
@@ -1069,7 +1998,7 @@ function makeTransitionId(functionName, branch) {
|
|
|
1069
1998
|
const { terminal } = branch;
|
|
1070
1999
|
const statusKey = terminal.statusCode === null ? "none" : terminal.statusCode.type === "literal" ? String(terminal.statusCode.value) : `dyn:${terminal.statusCode.sourceText}`;
|
|
1071
2000
|
const conditionSig = branch.conditions.map((c) => `${c.polarity}:${c.sourceText}`).join(";");
|
|
1072
|
-
const conditionHash = (0,
|
|
2001
|
+
const conditionHash = (0, import_node_crypto3.createHash)("sha1").update(conditionSig).digest("hex").slice(0, 7);
|
|
1073
2002
|
return `${functionName}:${terminal.kind}:${statusKey}:${conditionHash}`;
|
|
1074
2003
|
}
|
|
1075
2004
|
function collapseReading(table, reading) {
|
|
@@ -1193,22 +2122,22 @@ function buildMetadata(raw) {
|
|
|
1193
2122
|
let metadata = {};
|
|
1194
2123
|
const http = buildHttpMetadataValue(raw);
|
|
1195
2124
|
if (http !== null) {
|
|
1196
|
-
metadata = (0,
|
|
2125
|
+
metadata = (0, import_behavioral_ir4.withHttpMetadata)(metadata, http);
|
|
1197
2126
|
}
|
|
1198
2127
|
const graphql = buildGraphqlMetadataValue(raw);
|
|
1199
2128
|
if (graphql !== null) {
|
|
1200
|
-
metadata = (0,
|
|
2129
|
+
metadata = (0, import_behavioral_ir4.withGraphqlMetadata)(metadata, graphql);
|
|
1201
2130
|
}
|
|
1202
2131
|
if (raw.sourceDocumentLabel !== void 0) {
|
|
1203
|
-
metadata = (0,
|
|
2132
|
+
metadata = (0, import_behavioral_ir4.withSourceDocumentMetadata)(metadata, {
|
|
1204
2133
|
label: raw.sourceDocumentLabel
|
|
1205
2134
|
});
|
|
1206
2135
|
}
|
|
1207
2136
|
if (raw.mount !== void 0) {
|
|
1208
|
-
metadata = (0,
|
|
2137
|
+
metadata = (0, import_behavioral_ir4.withMountMetadata)(metadata, raw.mount);
|
|
1209
2138
|
}
|
|
1210
2139
|
if (raw.wrappers !== void 0 && raw.wrappers.length > 0) {
|
|
1211
|
-
metadata = (0,
|
|
2140
|
+
metadata = (0, import_behavioral_ir4.withWrapperMetadata)(metadata, { applied: raw.wrappers });
|
|
1212
2141
|
}
|
|
1213
2142
|
return Object.keys(metadata).length > 0 ? metadata : null;
|
|
1214
2143
|
}
|
|
@@ -1258,7 +2187,7 @@ function answersWithHttpResponses(raw) {
|
|
|
1258
2187
|
if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
|
|
1259
2188
|
return true;
|
|
1260
2189
|
}
|
|
1261
|
-
return (0,
|
|
2190
|
+
return (0, import_behavioral_ir4.exchangesHttpResponses)(raw.boundaryBinding);
|
|
1262
2191
|
}
|
|
1263
2192
|
function detectGaps(raw, transitions, options) {
|
|
1264
2193
|
if (options.gapHandling === "silent") {
|
|
@@ -1473,6 +2402,7 @@ function paramToInput(param) {
|
|
|
1473
2402
|
0 && (module.exports = {
|
|
1474
2403
|
IdMap,
|
|
1475
2404
|
IdSet,
|
|
2405
|
+
MAX_ENTRIES,
|
|
1476
2406
|
MAX_PATHS,
|
|
1477
2407
|
PathBudgetExceeded,
|
|
1478
2408
|
SKIP_CHILDREN,
|
|
@@ -1482,25 +2412,44 @@ function paramToInput(param) {
|
|
|
1482
2412
|
andThenReading,
|
|
1483
2413
|
assembleSummary,
|
|
1484
2414
|
assessConfidence,
|
|
2415
|
+
buildUngatedExtractionReport,
|
|
1485
2416
|
composeWrappers,
|
|
2417
|
+
computeContentHash,
|
|
2418
|
+
computeDistHashFrom,
|
|
1486
2419
|
configuredCallOption,
|
|
2420
|
+
createAdapterStamp,
|
|
2421
|
+
createCacheLayer,
|
|
2422
|
+
createPackTallies,
|
|
2423
|
+
createTimer,
|
|
1487
2424
|
detectGaps,
|
|
1488
2425
|
effectToIR,
|
|
2426
|
+
emptyTally,
|
|
1489
2427
|
enumerateOrDegrade,
|
|
1490
2428
|
enumerateStructuredPaths,
|
|
2429
|
+
evaluatePackHealth,
|
|
1491
2430
|
firstWrittenReading,
|
|
2431
|
+
formatPackHealth,
|
|
1492
2432
|
guardsHoldOn,
|
|
1493
2433
|
httpRouteDiscovery,
|
|
1494
2434
|
makeTransitionId,
|
|
1495
2435
|
mapReading,
|
|
2436
|
+
moduleInitStructure,
|
|
2437
|
+
noopTimer,
|
|
2438
|
+
packGradients,
|
|
1496
2439
|
paramToInput,
|
|
2440
|
+
projectFileStamp,
|
|
1497
2441
|
rawConditionToPredicate,
|
|
2442
|
+
recordPackFailure,
|
|
1498
2443
|
registrationHelperDiscovery,
|
|
1499
2444
|
routeHelperIndex,
|
|
2445
|
+
runDigest,
|
|
1500
2446
|
runsBefore,
|
|
1501
2447
|
scopeOption,
|
|
1502
2448
|
sharedGatingConditions,
|
|
2449
|
+
stampModuleImports,
|
|
1503
2450
|
storageSystemOption,
|
|
2451
|
+
summaryCountsByPack,
|
|
2452
|
+
tallyUnit,
|
|
1504
2453
|
terminalToOutput,
|
|
1505
2454
|
unreadableReading,
|
|
1506
2455
|
unwrapJsonStringify,
|