ucn 4.2.2 → 4.2.3
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 +23 -11
- package/core/build-worker.js +11 -2
- package/core/cache.js +21 -1
- package/core/callers.js +618 -43
- package/core/deadcode.js +25 -2
- package/core/entrypoints.js +9 -1
- package/core/output/analysis.js +4 -0
- package/core/project.js +13 -3
- package/core/search.js +149 -26
- package/languages/java.js +32 -3
- package/languages/javascript.js +257 -34
- package/languages/python.js +77 -10
- package/languages/rust.js +99 -1
- package/package.json +4 -4
package/languages/python.js
CHANGED
|
@@ -671,7 +671,7 @@ function constructorTypeInfo(funcNode) {
|
|
|
671
671
|
const attr = funcNode.childForFieldName('attribute');
|
|
672
672
|
const object = funcNode.childForFieldName('object');
|
|
673
673
|
return attr && /^[A-Z]/.test(attr.text)
|
|
674
|
-
? { type: attr.text, qualifier: object?.
|
|
674
|
+
? { type: attr.text, qualifier: object?.text || undefined }
|
|
675
675
|
: undefined;
|
|
676
676
|
}
|
|
677
677
|
return undefined;
|
|
@@ -685,6 +685,8 @@ function findCallsInCode(code, parser) {
|
|
|
685
685
|
const nonCallableNames = new Set(); // Track names assigned non-callable values
|
|
686
686
|
const localVarTypes = new Map(); // Track local variable types: varName -> typeName (for receiverType inference)
|
|
687
687
|
const localVarTypeQualifiers = new Map(); // varName -> imported module alias that owns the inferred type
|
|
688
|
+
const constructedReceiverVars = new Set(); // exact constructor-result bindings
|
|
689
|
+
const withBindingVars = new Set(); // names produced by a context-manager as-target
|
|
688
690
|
// Member-access aliases (fix #218): `append = output.append` makes a later
|
|
689
691
|
// bare `append(part)` a METHOD call on `output` — it must carry the
|
|
690
692
|
// receiver's evidence, never bind by bare name to a same-file def
|
|
@@ -697,6 +699,8 @@ function findCallsInCode(code, parser) {
|
|
|
697
699
|
const moduleAliases = new Set(); // Names bound to MODULES (import httpx / import numpy as np)
|
|
698
700
|
const localVarTypesStack = []; // Stack for function-scoped save/restore of localVarTypes
|
|
699
701
|
const localVarTypeQualifiersStack = [];
|
|
702
|
+
const constructedReceiverVarsStack = [];
|
|
703
|
+
const withBindingVarsStack = [];
|
|
700
704
|
|
|
701
705
|
// Helper: extract first string-arg literal from a call node.
|
|
702
706
|
// Used by route extraction to capture path arg of requests.get('/users'), httpx.get('/users') etc.
|
|
@@ -848,6 +852,16 @@ function findCallsInCode(code, parser) {
|
|
|
848
852
|
}
|
|
849
853
|
}
|
|
850
854
|
if (p.type === 'function_definition' || p.type === 'async_function_definition') {
|
|
855
|
+
const params = p.childForFieldName('parameters');
|
|
856
|
+
if (params) {
|
|
857
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
858
|
+
const prm = params.namedChild(i);
|
|
859
|
+
const prmName = prm.type === 'identifier'
|
|
860
|
+
? prm
|
|
861
|
+
: (prm.childForFieldName('name') || prm.namedChild(0));
|
|
862
|
+
if (prmName?.type === 'identifier' && prmName.text === name) return true;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
851
865
|
const body = p.childForFieldName('body');
|
|
852
866
|
return body ? _bindsNameInScope(body, name) : false;
|
|
853
867
|
}
|
|
@@ -888,6 +902,8 @@ function findCallsInCode(code, parser) {
|
|
|
888
902
|
// Save localVarTypes so inner declarations don't leak to sibling functions
|
|
889
903
|
localVarTypesStack.push(new Map(localVarTypes));
|
|
890
904
|
localVarTypeQualifiersStack.push(new Map(localVarTypeQualifiers));
|
|
905
|
+
constructedReceiverVarsStack.push(new Set(constructedReceiverVars));
|
|
906
|
+
withBindingVarsStack.push(new Set(withBindingVars));
|
|
891
907
|
memberAliasesStack.push(new Map(memberAliases));
|
|
892
908
|
}
|
|
893
909
|
|
|
@@ -912,11 +928,18 @@ function findCallsInCode(code, parser) {
|
|
|
912
928
|
const ctx = value.namedChild(0);
|
|
913
929
|
const target = value.namedChildCount > 1 ? value.namedChild(value.namedChildCount - 1) : null;
|
|
914
930
|
const targetId = target?.type === 'as_pattern_target' ? target.namedChild(0) : null;
|
|
931
|
+
const recordWithTargets = n => {
|
|
932
|
+
if (!n) return;
|
|
933
|
+
if (n.type === 'identifier') withBindingVars.add(n.text);
|
|
934
|
+
for (let i = 0; i < n.namedChildCount; i++) recordWithTargets(n.namedChild(i));
|
|
935
|
+
};
|
|
936
|
+
recordWithTargets(targetId || target);
|
|
915
937
|
if (ctx?.type === 'call' && targetId?.type === 'identifier') {
|
|
916
938
|
const ctor = constructorTypeInfo(ctx.childForFieldName('function'));
|
|
917
939
|
if (ctor) {
|
|
918
940
|
localVarTypes.set(targetId.text, ctor.type);
|
|
919
|
-
|
|
941
|
+
constructedReceiverVars.add(targetId.text);
|
|
942
|
+
if (ctor.qualifier && moduleAliases.has(ctor.qualifier.split('.')[0])) {
|
|
920
943
|
localVarTypeQualifiers.set(targetId.text, ctor.qualifier);
|
|
921
944
|
} else {
|
|
922
945
|
localVarTypeQualifiers.delete(targetId.text);
|
|
@@ -940,6 +963,8 @@ function findCallsInCode(code, parser) {
|
|
|
940
963
|
}
|
|
941
964
|
}
|
|
942
965
|
memberAliases.delete(left.text); // any assignment rebinds the name
|
|
966
|
+
constructedReceiverVars.delete(left.text);
|
|
967
|
+
withBindingVars.delete(left.text);
|
|
943
968
|
// Rebinding without a known type makes any previously inferred
|
|
944
969
|
// type stale — nearest-preceding-assignment semantics (#199's
|
|
945
970
|
// documented rule). Without this, `x = ""; x = render(); x.m()`
|
|
@@ -1017,7 +1042,8 @@ function findCallsInCode(code, parser) {
|
|
|
1017
1042
|
const ctor = constructorTypeInfo(right.childForFieldName('function'));
|
|
1018
1043
|
if (ctor) {
|
|
1019
1044
|
localVarTypes.set(left.text, ctor.type);
|
|
1020
|
-
|
|
1045
|
+
constructedReceiverVars.add(left.text);
|
|
1046
|
+
if (ctor.qualifier && moduleAliases.has(ctor.qualifier.split('.')[0])) {
|
|
1021
1047
|
localVarTypeQualifiers.set(left.text, ctor.qualifier);
|
|
1022
1048
|
} else {
|
|
1023
1049
|
localVarTypeQualifiers.delete(left.text);
|
|
@@ -1101,6 +1127,7 @@ function findCallsInCode(code, parser) {
|
|
|
1101
1127
|
...(argSpread && { argSpread: true }),
|
|
1102
1128
|
enclosingFunction,
|
|
1103
1129
|
uncertain,
|
|
1130
|
+
...(isShadowedByLocal(funcNode, funcNode.text) && { localShadow: true }),
|
|
1104
1131
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
|
|
1105
1132
|
});
|
|
1106
1133
|
}
|
|
@@ -1190,7 +1217,11 @@ function findCallsInCode(code, parser) {
|
|
|
1190
1217
|
receiver,
|
|
1191
1218
|
...(receiverType && { receiverType }),
|
|
1192
1219
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
1220
|
+
...(receiver && constructedReceiverVars.has(receiver) && { receiverConstructed: true }),
|
|
1221
|
+
...(receiver && withBindingVars.has(receiver) && { receiverWithBinding: true }),
|
|
1193
1222
|
...(receiverIsModule && { receiverIsModule: true }),
|
|
1223
|
+
...(receiver && objNode?.type === 'identifier' &&
|
|
1224
|
+
isShadowedByLocal(objNode, receiver) && { receiverLocalBinding: true }),
|
|
1194
1225
|
...(selfAttribute && { selfAttribute }),
|
|
1195
1226
|
...(receiverCall && { receiverCall }),
|
|
1196
1227
|
...(receiverCallIsMethod && { receiverCallIsMethod: true }),
|
|
@@ -1273,6 +1304,12 @@ function findCallsInCode(code, parser) {
|
|
|
1273
1304
|
localVarTypeQualifiers.clear();
|
|
1274
1305
|
for (const [k, v] of savedQualifiers) localVarTypeQualifiers.set(k, v);
|
|
1275
1306
|
}
|
|
1307
|
+
const savedConstructed = constructedReceiverVarsStack.pop();
|
|
1308
|
+
constructedReceiverVars.clear();
|
|
1309
|
+
if (savedConstructed) for (const name of savedConstructed) constructedReceiverVars.add(name);
|
|
1310
|
+
const savedWithBindings = withBindingVarsStack.pop();
|
|
1311
|
+
withBindingVars.clear();
|
|
1312
|
+
if (savedWithBindings) for (const name of savedWithBindings) withBindingVars.add(name);
|
|
1276
1313
|
const savedAliases = memberAliasesStack.pop();
|
|
1277
1314
|
if (savedAliases) {
|
|
1278
1315
|
memberAliases.clear();
|
|
@@ -1304,13 +1341,33 @@ function findImportsInCode(code, parser) {
|
|
|
1304
1341
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1305
1342
|
const child = node.namedChild(i);
|
|
1306
1343
|
if (child.type === 'dotted_name') {
|
|
1307
|
-
// import
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1344
|
+
// `import pkg.submodule` binds `pkg`, while also loading
|
|
1345
|
+
// the complete submodule. Record both ownership edges so
|
|
1346
|
+
// `pkg.public_api()` can resolve through pkg/__init__.py
|
|
1347
|
+
// and `pkg.submodule.member` can still resolve to the
|
|
1348
|
+
// imported child module.
|
|
1349
|
+
const parts = child.text.split('.');
|
|
1350
|
+
if (parts.length > 1) {
|
|
1351
|
+
imports.push({
|
|
1352
|
+
module: parts[0],
|
|
1353
|
+
names: [parts[0]],
|
|
1354
|
+
type: 'import',
|
|
1355
|
+
line
|
|
1356
|
+
});
|
|
1357
|
+
imports.push({
|
|
1358
|
+
module: child.text,
|
|
1359
|
+
names: [],
|
|
1360
|
+
type: 'import-submodule',
|
|
1361
|
+
line
|
|
1362
|
+
});
|
|
1363
|
+
} else {
|
|
1364
|
+
imports.push({
|
|
1365
|
+
module: child.text,
|
|
1366
|
+
names: [child.text],
|
|
1367
|
+
type: 'import',
|
|
1368
|
+
line
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1314
1371
|
} else if (child.type === 'aliased_import') {
|
|
1315
1372
|
// import sys as system
|
|
1316
1373
|
const nameNode = child.namedChild(0);
|
|
@@ -1537,6 +1594,16 @@ function findUsagesInCode(code, name, parser, tree) {
|
|
|
1537
1594
|
usages.push({ line, column, usageType, receiver: object.text });
|
|
1538
1595
|
return true;
|
|
1539
1596
|
}
|
|
1597
|
+
// Constructed receiver: ColorTriplet(...).normalized is a
|
|
1598
|
+
// class-associated property reference, not an unowned bare
|
|
1599
|
+
// name. This matters to class-scoped `tests` queries.
|
|
1600
|
+
if (object && object.type === 'call') {
|
|
1601
|
+
const ctor = object.childForFieldName('function');
|
|
1602
|
+
if (ctor?.type === 'identifier') {
|
|
1603
|
+
usages.push({ line, column, usageType, receiver: ctor.text });
|
|
1604
|
+
return true;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1540
1607
|
// self.attr receiver (unittest setUp idiom: self.w = Widget(3);
|
|
1541
1608
|
// self.w.render()) — record the ATTR name so the instance-type
|
|
1542
1609
|
// map built from the assignment line ('w' → Widget) matches
|
package/languages/rust.js
CHANGED
|
@@ -432,6 +432,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
432
432
|
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
433
433
|
const implInfo = extractImplInfo(node);
|
|
434
434
|
const docstring = extractRustDocstring(lines, startLine);
|
|
435
|
+
const derefTarget = extractDerefTarget(node, implInfo.traitName);
|
|
435
436
|
|
|
436
437
|
types.push({
|
|
437
438
|
name: implInfo.name,
|
|
@@ -442,6 +443,7 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
442
443
|
typeName: implInfo.typeName,
|
|
443
444
|
members: extractImplMembers(node, lines, implInfo.typeName),
|
|
444
445
|
modifiers: [],
|
|
446
|
+
...(derefTarget && { derefTarget }),
|
|
445
447
|
...(docstring && { docstring })
|
|
446
448
|
});
|
|
447
449
|
return true; // matched
|
|
@@ -548,19 +550,41 @@ function _processClass(node, types, processedRanges, lines, code) {
|
|
|
548
550
|
*/
|
|
549
551
|
function _postProcessTraitImpls(types) {
|
|
550
552
|
const implTraits = new Map(); // typeName → [traitName, ...]
|
|
553
|
+
const derefTargets = new Map(); // typeName -> Set<Target>
|
|
551
554
|
for (const t of types) {
|
|
552
555
|
if (t.type === 'impl' && t.traitName && t.typeName) {
|
|
553
556
|
if (!implTraits.has(t.typeName)) implTraits.set(t.typeName, []);
|
|
554
557
|
implTraits.get(t.typeName).push(t.traitName);
|
|
558
|
+
if (t.derefTarget) {
|
|
559
|
+
if (!derefTargets.has(t.typeName)) derefTargets.set(t.typeName, new Set());
|
|
560
|
+
derefTargets.get(t.typeName).add(t.derefTarget);
|
|
561
|
+
}
|
|
555
562
|
}
|
|
556
563
|
}
|
|
557
564
|
for (const t of types) {
|
|
558
565
|
if ((t.type === 'struct' || t.type === 'enum') && implTraits.has(t.name)) {
|
|
559
566
|
t.implements = implTraits.get(t.name);
|
|
567
|
+
const targets = derefTargets.get(t.name);
|
|
568
|
+
if (targets?.size === 1) t.derefTarget = [...targets][0];
|
|
560
569
|
}
|
|
561
570
|
}
|
|
562
571
|
}
|
|
563
572
|
|
|
573
|
+
function extractDerefTarget(implNode, traitName) {
|
|
574
|
+
if (!traitName || !/(^|::)Deref(?:Mut)?$/.test(traitName)) return null;
|
|
575
|
+
let found = null;
|
|
576
|
+
const walk = node => {
|
|
577
|
+
if (found) return;
|
|
578
|
+
if (node.type === 'type_item' && node.childForFieldName('name')?.text === 'Target') {
|
|
579
|
+
found = aliasBaseTypeName(node.childForFieldName('type'));
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
for (let i = 0; i < node.namedChildCount; i++) walk(node.namedChild(i));
|
|
583
|
+
};
|
|
584
|
+
walk(implNode);
|
|
585
|
+
return found;
|
|
586
|
+
}
|
|
587
|
+
|
|
564
588
|
/**
|
|
565
589
|
* Process a node for state object extraction (single-pass helper)
|
|
566
590
|
* Returns true if node was matched, false otherwise
|
|
@@ -654,6 +678,28 @@ function extractStructFields(structNode, codeOrLines) {
|
|
|
654
678
|
const bodyNode = structNode.childForFieldName('body');
|
|
655
679
|
if (!bodyNode) return fields;
|
|
656
680
|
|
|
681
|
+
if (bodyNode.type === 'ordered_field_declaration_list') {
|
|
682
|
+
let position = 0;
|
|
683
|
+
for (let i = 0; i < bodyNode.namedChildCount; i++) {
|
|
684
|
+
const field = bodyNode.namedChild(i);
|
|
685
|
+
// Visibility modifiers and field attributes (`#[serde(..)] u32`)
|
|
686
|
+
// are separate children; the type node owns the tuple position.
|
|
687
|
+
// Numeric member names let the shared declared-field hop resolve
|
|
688
|
+
// `self.0.method()` exactly.
|
|
689
|
+
if (field.type === 'visibility_modifier' ||
|
|
690
|
+
field.type === 'attribute_item') continue;
|
|
691
|
+
const { startLine, endLine } = nodeToLocation(field, code);
|
|
692
|
+
fields.push({
|
|
693
|
+
name: String(position++),
|
|
694
|
+
startLine,
|
|
695
|
+
endLine,
|
|
696
|
+
memberType: 'field',
|
|
697
|
+
fieldType: field.text,
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return fields;
|
|
701
|
+
}
|
|
702
|
+
|
|
657
703
|
for (let i = 0; i < bodyNode.namedChildCount; i++) {
|
|
658
704
|
const field = bodyNode.namedChild(i);
|
|
659
705
|
if (field.type === 'field_declaration') {
|
|
@@ -1474,7 +1520,7 @@ function findCallsInCode(code, parser) {
|
|
|
1474
1520
|
if (obj?.type === 'field_expression') {
|
|
1475
1521
|
const rootNode = obj.childForFieldName('value');
|
|
1476
1522
|
const fldNode = obj.childForFieldName('field');
|
|
1477
|
-
if (fldNode?.type === 'field_identifier' && rootNode &&
|
|
1523
|
+
if ((fldNode?.type === 'field_identifier' || fldNode?.type === 'integer_literal') && rootNode &&
|
|
1478
1524
|
(rootNode.type === 'identifier' || rootNode.type === 'self')) {
|
|
1479
1525
|
receiverRoot = rootNode.text;
|
|
1480
1526
|
receiverField = fldNode.text;
|
|
@@ -2139,6 +2185,30 @@ function _indexInParent(node, parent) {
|
|
|
2139
2185
|
function findUsagesInCode(code, name, parser, tree) {
|
|
2140
2186
|
tree = tree || parseTree(parser, code);
|
|
2141
2187
|
const usages = [];
|
|
2188
|
+
// Lazy same-file enum→variants map: built only when a paren-less
|
|
2189
|
+
// `Type::name` reference needs the enum-variant check.
|
|
2190
|
+
let _enumVariants = null;
|
|
2191
|
+
const sameFileEnumVariant = (enumName, variantName) => {
|
|
2192
|
+
if (_enumVariants === null) {
|
|
2193
|
+
_enumVariants = new Map();
|
|
2194
|
+
traverseTreeCached(tree.rootNode, (n) => {
|
|
2195
|
+
if (n.type !== 'enum_item') return;
|
|
2196
|
+
const enName = n.childForFieldName('name')?.text;
|
|
2197
|
+
const body = n.childForFieldName('body');
|
|
2198
|
+
if (!enName || !body) return;
|
|
2199
|
+
let set = _enumVariants.get(enName);
|
|
2200
|
+
if (!set) { set = new Set(); _enumVariants.set(enName, set); }
|
|
2201
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
2202
|
+
const child = body.namedChild(i);
|
|
2203
|
+
if (child.type === 'enum_variant') {
|
|
2204
|
+
const vn = child.childForFieldName('name')?.text;
|
|
2205
|
+
if (vn) set.add(vn);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
return _enumVariants.get(enumName)?.has(variantName) || false;
|
|
2211
|
+
};
|
|
2142
2212
|
|
|
2143
2213
|
visitNameNodes(tree, code, name, (node) => {
|
|
2144
2214
|
// Look for identifier, field_identifier (method names in obj.method() calls),
|
|
@@ -2201,6 +2271,34 @@ function findUsagesInCode(code, name, parser, tree) {
|
|
|
2201
2271
|
return true;
|
|
2202
2272
|
}
|
|
2203
2273
|
}
|
|
2274
|
+
} else if (sameNode(parent.childForFieldName('name'), node)) {
|
|
2275
|
+
// Associated method value: `Cursive::quit` is a reference
|
|
2276
|
+
// to the method even though no call_expression wraps it.
|
|
2277
|
+
// Preserve its type receiver so the project-aware usage
|
|
2278
|
+
// layer can distinguish it from `Enum::Variant`.
|
|
2279
|
+
const pathNode = parent.childForFieldName('path');
|
|
2280
|
+
if (pathNode) {
|
|
2281
|
+
const segs = pathNode.text.split('::');
|
|
2282
|
+
const receiver = segs[segs.length - 1];
|
|
2283
|
+
// A same-file `enum Receiver { Name }` proves this is
|
|
2284
|
+
// the variant, not an associated item of the queried
|
|
2285
|
+
// symbol — provable without the index, so filtered
|
|
2286
|
+
// here; cross-file receivers stay for the project
|
|
2287
|
+
// layer's owner check.
|
|
2288
|
+
if (receiver && sameFileEnumVariant(receiver, name)) {
|
|
2289
|
+
return true;
|
|
2290
|
+
}
|
|
2291
|
+
if (receiver) {
|
|
2292
|
+
usages.push({
|
|
2293
|
+
line,
|
|
2294
|
+
column,
|
|
2295
|
+
usageType: 'reference',
|
|
2296
|
+
receiver,
|
|
2297
|
+
scopedReference: true,
|
|
2298
|
+
});
|
|
2299
|
+
return true;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2204
2302
|
}
|
|
2205
2303
|
}
|
|
2206
2304
|
// Turbofish call on a bare name: f::<T>() — the identifier's parent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.3",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
5
|
"description": "Code intelligence toolkit for AI agents: extract functions, trace call chains, find callers, and detect dead code without reading entire files. Works as MCP server, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"eval:performance": "node --expose-gc eval/run-performance-gate.js",
|
|
19
19
|
"lint": "eslint core/ cli/ mcp/ languages/ eval/run-*.js eval/*-gate-policy.js",
|
|
20
20
|
"verify": "npm run lint && npm test",
|
|
21
|
-
"trust:gate:semantic": "node eval/run-oracle-eval.js --
|
|
22
|
-
"trust:gate:deadcode": "node eval/run-deadcode-eval.js --
|
|
23
|
-
"trust:gate:performance": "node --expose-gc eval/run-performance-gate.js --
|
|
21
|
+
"trust:gate:semantic": "node eval/run-oracle-eval.js --release --min-precision 0.98 --max-unscored-ratio 0.10",
|
|
22
|
+
"trust:gate:deadcode": "node eval/run-deadcode-eval.js --release --sample 100 --arm default",
|
|
23
|
+
"trust:gate:performance": "node --expose-gc eval/run-performance-gate.js --release --queries 40",
|
|
24
24
|
"trust:gate:fast": "node eval/run-oracle-eval.js --repo preact-signals,httpx --min-precision 0.98 && node --expose-gc eval/run-performance-gate.js --repo preact-signals,httpx --queries 20",
|
|
25
25
|
"trust:gate": "npm run trust:gate:semantic && npm run trust:gate:deadcode && npm run trust:gate:performance"
|
|
26
26
|
},
|