@saptools/service-flow 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  parsePackageJson,
13
13
  parseServiceBindings,
14
14
  trace
15
- } from "./chunk-LCXDOSWL.js";
15
+ } from "./chunk-HS746OHV.js";
16
16
 
17
17
  // src/cli.ts
18
18
  import { Command } from "commander";
@@ -188,7 +188,7 @@ function migrate(db) {
188
188
  // package.json
189
189
  var package_default = {
190
190
  name: "@saptools/service-flow",
191
- version: "0.1.23",
191
+ version: "0.1.25",
192
192
  description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
193
193
  type: "module",
194
194
  publishConfig: {
@@ -554,11 +554,19 @@ function insertSymbolCalls(db, repoId, rows) {
554
554
  insertStmt.run(repoId, caller?.id, target.id, r.calleeExpression, r.importSource, r.sourceFile, r.sourceLine, target.status, 0.8, JSON.stringify({ ...r.evidence, candidateStrategy: target.strategy, candidateCount: target.candidateCount }), target.reason);
555
555
  }
556
556
  }
557
+ function isRelativeImportedSymbolCall(r) {
558
+ return Boolean(r.importSource?.startsWith("."));
559
+ }
557
560
  function resolveSymbolCallTarget(db, repoId, r) {
558
561
  const evidence = r.evidence;
559
562
  const localRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND (name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName);
560
563
  if (localRows.length === 1) return { id: localRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "same_file_exact", candidateCount: 1 };
561
564
  if (localRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple same-file symbol targets matched exactly", strategy: "same_file_exact", candidateCount: localRows.length };
565
+ if (evidence.relation === "class_instance_method" && isRelativeImportedSymbolCall(r)) {
566
+ const classRows = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file<>? AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName);
567
+ if (classRows.length === 1) return { id: classRows[0]?.id ?? null, status: "resolved", reason: null, strategy: "relative_import_class_instance_method", candidateCount: 1 };
568
+ if (classRows.length > 1) return { id: null, status: "ambiguous", reason: "Multiple relative class instance method targets matched exactly", strategy: "relative_import_class_instance_method", candidateCount: classRows.length };
569
+ }
562
570
  const rows = db.prepare("SELECT id,kind,evidence_json evidenceJson FROM symbols WHERE repo_id=? AND source_file<>? AND exported=1 AND (exported_name=? OR name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.calleeLocalName);
563
571
  if (evidence.relation === "relative_import_proxy_member" && rows.length > 1) {
564
572
  const objectMapRows = rows.filter((row) => String(row.evidenceJson ?? "").includes("exported_object_shorthand") || String(row.evidenceJson ?? "").includes("exported_object_literal"));
@@ -672,9 +680,9 @@ function isFunctionLike(node) {
672
680
  function exported(node) {
673
681
  return Boolean(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export);
674
682
  }
675
- function isPublicStaticMethod(node) {
683
+ function isPublicClassMethod(node) {
676
684
  const flags = ts.getCombinedModifierFlags(node);
677
- return (flags & ts.ModifierFlags.Static) !== 0 && (flags & ts.ModifierFlags.Private) === 0 && (flags & ts.ModifierFlags.Protected) === 0;
685
+ return (flags & ts.ModifierFlags.Private) === 0 && (flags & ts.ModifierFlags.Protected) === 0;
678
686
  }
679
687
  function exportDeclarations(source) {
680
688
  const exports = /* @__PURE__ */ new Map();
@@ -696,6 +704,40 @@ function isObjectFunction(node) {
696
704
  var commonTerminalMembers = /* @__PURE__ */ new Set(["push", "includes", "find", "findIndex", "map", "filter", "reduce", "forEach", "some", "every", "toUpperCase", "toLowerCase", "trim", "split", "join", "get", "set", "has"]);
697
705
  var loggerMembers = /* @__PURE__ */ new Set(["trace", "debug", "info", "warn", "error", "fatal", "log"]);
698
706
  var globalObjects = /* @__PURE__ */ new Set(["JSON", "Object", "Array", "String", "Number", "Boolean", "Math", "Date", "Promise", "Reflect"]);
707
+ var builtInConstructors = /* @__PURE__ */ new Set([
708
+ "Set",
709
+ "Map",
710
+ "WeakSet",
711
+ "WeakMap",
712
+ "Date",
713
+ "RegExp",
714
+ "URL",
715
+ "URLSearchParams",
716
+ "Error",
717
+ "EvalError",
718
+ "RangeError",
719
+ "ReferenceError",
720
+ "SyntaxError",
721
+ "TypeError",
722
+ "URIError",
723
+ "AggregateError",
724
+ "ArrayBuffer",
725
+ "SharedArrayBuffer",
726
+ "DataView",
727
+ "Int8Array",
728
+ "Uint8Array",
729
+ "Uint8ClampedArray",
730
+ "Int16Array",
731
+ "Uint16Array",
732
+ "Int32Array",
733
+ "Uint32Array",
734
+ "Float32Array",
735
+ "Float64Array",
736
+ "BigInt64Array",
737
+ "BigUint64Array",
738
+ "Promise",
739
+ "AbortController"
740
+ ]);
699
741
  var capDslRoots = /* @__PURE__ */ new Set(["SELECT", "INSERT", "UPSERT", "UPDATE", "DELETE"]);
700
742
  var requestHelpers = /* @__PURE__ */ new Set(["reject", "error", "info", "warn", "notify"]);
701
743
  var transportMembers = /* @__PURE__ */ new Set(["emit", "publish", "send", "on"]);
@@ -725,6 +767,42 @@ function ignoredFrameworkCall(callee) {
725
767
  function nearest(symbols, line) {
726
768
  return symbols.filter((s) => s.startLine <= line && s.endLine >= line).sort((a, b) => a.endLine - a.startLine - (b.endLine - b.startLine))[0];
727
769
  }
770
+ function argumentEvidence(args, source) {
771
+ return args.map((arg) => {
772
+ if (ts.isIdentifier(arg)) return { kind: "identifier", name: arg.text };
773
+ if (ts.isObjectLiteralExpression(arg)) {
774
+ const properties = [];
775
+ for (const prop of arg.properties) {
776
+ if (ts.isShorthandPropertyAssignment(prop)) properties.push({ kind: "shorthand", property: prop.name.text, argument: prop.name.text });
777
+ if (ts.isPropertyAssignment(prop)) {
778
+ const propName = nameOf(prop.name);
779
+ if (propName && ts.isIdentifier(prop.initializer)) properties.push({ kind: "property_assignment", property: propName, argument: prop.initializer.text });
780
+ }
781
+ }
782
+ return { kind: "object_literal", properties };
783
+ }
784
+ return { kind: "unsupported", expression: arg.getText(source) };
785
+ });
786
+ }
787
+ function bindingLocalName(name, initializer) {
788
+ if (ts.isIdentifier(name)) return name.text;
789
+ if (initializer && ts.isIdentifier(initializer)) return initializer.text;
790
+ return void 0;
791
+ }
792
+ function parameterBindings(params) {
793
+ return params.flatMap((param, index) => {
794
+ if (ts.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
795
+ if (!ts.isObjectBindingPattern(param.name)) return [];
796
+ const properties = param.name.elements.flatMap((element) => {
797
+ if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
798
+ const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
799
+ if (!property) return [];
800
+ const local = bindingLocalName(element.name, element.initializer);
801
+ return local ? [{ property, local }] : [];
802
+ });
803
+ return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
804
+ });
805
+ }
728
806
  async function parseExecutableSymbols(repoPath, filePath) {
729
807
  const text = await fs4.readFile(path4.join(repoPath, filePath), "utf8");
730
808
  const source = ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS);
@@ -735,16 +813,21 @@ async function parseExecutableSymbols(repoPath, filePath) {
735
813
  const exportNames = exportDeclarations(source);
736
814
  const objectExports = /* @__PURE__ */ new Set();
737
815
  const exportedClasses = /* @__PURE__ */ new Set();
816
+ const declaredClasses = /* @__PURE__ */ new Set();
738
817
  const proxyVariables = /* @__PURE__ */ new Map();
818
+ const classInstances = /* @__PURE__ */ new Map();
739
819
  const addSymbol = (kind, localName, node, parentName, exportedName, evidence) => {
740
820
  const parentRoot = parentName?.split(".")[0] ?? "";
741
821
  const declaredExportName = exportedName ?? exportNames.get(parentName ? parentRoot : localName);
742
822
  const qualifiedName = parentName ? `${parentName}.${localName}` : localName;
743
823
  const objectExported = parentName ? objectExports.has(parentRoot) : false;
744
- const classMemberExported = kind === "method" && parentName ? exportedClasses.has(parentRoot) && ts.isMethodDeclaration(node) && isPublicStaticMethod(node) : false;
824
+ const classMemberExported = kind === "method" && parentName ? exportedClasses.has(parentRoot) && ts.isMethodDeclaration(node) && (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static) !== 0 && isPublicClassMethod(node) : false;
745
825
  const effectiveExportedName = classMemberExported || objectExported ? qualifiedName : declaredExportName;
746
- const sourceEvidence = evidence ?? (classMemberExported ? { source: "exported_class_member", exportedClass: parentRoot, memberKind: "static_method" } : declaredExportName ? { exportedName: declaredExportName, source: "export_declaration" } : objectExported ? { exportedName: qualifiedName, source: "exported_object_literal" } : void 0);
747
- symbols.push({ kind, localName: kind === "object_method" ? qualifiedName : localName, exportedName: effectiveExportedName, qualifiedName, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: exported(node) || Boolean(effectiveExportedName), importExportEvidence: sourceEvidence });
826
+ const bindings = isFunctionLike(node) ? parameterBindings(node.parameters) : void 0;
827
+ const params = bindings?.flatMap((binding) => binding.kind === "identifier" ? [binding.name] : []);
828
+ const sourceEvidence = evidence ?? (classMemberExported ? { source: "exported_class_member", exportedClass: parentRoot, memberKind: (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static) !== 0 ? "static_method" : "class_method", parameters: params } : declaredExportName ? { exportedName: declaredExportName, source: "export_declaration" } : objectExported ? { exportedName: qualifiedName, source: "exported_object_literal" } : void 0);
829
+ const parameterEvidence = bindings && bindings.length > 0 ? { parameters: params, parameterBindings: bindings } : {};
830
+ symbols.push({ kind, localName: kind === "object_method" ? qualifiedName : localName, exportedName: effectiveExportedName, qualifiedName, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: exported(node) || Boolean(effectiveExportedName), importExportEvidence: sourceEvidence ? { ...sourceEvidence, ...parameterEvidence } : bindings && bindings.length > 0 ? parameterEvidence : void 0 });
748
831
  };
749
832
  const addAliasSymbol = (objectName, propertyName, node, targetImportSource) => {
750
833
  symbols.push({ kind: "object_alias", localName: propertyName, exportedName: propertyName, qualifiedName: `${objectName}.${propertyName}`, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: true, importExportEvidence: { source: "exported_object_shorthand", objectName, propertyName, targetImportSource } });
@@ -772,6 +855,7 @@ async function parseExecutableSymbols(repoPath, filePath) {
772
855
  visitImports(source);
773
856
  const visitSymbols = (node, parentClass) => {
774
857
  if (ts.isClassDeclaration(node) && node.name) {
858
+ declaredClasses.add(node.name.text);
775
859
  if (exported(node) || exportNames.has(node.name.text)) exportedClasses.add(node.name.text);
776
860
  for (const member of node.members) visitSymbols(member, node.name.text);
777
861
  return;
@@ -845,6 +929,15 @@ async function parseExecutableSymbols(repoPath, filePath) {
845
929
  ts.forEachChild(node, visitProxyVariables);
846
930
  };
847
931
  visitProxyVariables(source);
932
+ const visitClassInstances = (node) => {
933
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isNewExpression(node.initializer) && ts.isIdentifier(node.initializer.expression)) {
934
+ const className = node.initializer.expression.text;
935
+ const importSource = imports.get(className);
936
+ if (!builtInConstructors.has(className) && (importSource && isRelativeImport(importSource) || declaredClasses.has(className))) classInstances.set(node.name.text, { className, importSource });
937
+ }
938
+ ts.forEachChild(node, visitClassInstances);
939
+ };
940
+ visitClassInstances(source);
848
941
  const localCallables = new Set(symbols.flatMap((sym) => [sym.localName, sym.qualifiedName]));
849
942
  const visitCalls = (node) => {
850
943
  if (ts.isCallExpression(node)) {
@@ -853,8 +946,9 @@ async function parseExecutableSymbols(repoPath, filePath) {
853
946
  if (caller) {
854
947
  const callee = callName(node.expression);
855
948
  const proxy = callee.local ? proxyVariables.get(callee.local) : void 0;
856
- const importSource = proxy?.importSource ?? (callee.local ? imports.get(callee.local) : void 0) ?? (callee.member && callee.local ? imports.get(callee.local) : void 0);
857
- const targetName = proxy && callee.member ? callee.member : callee.expression.startsWith("this.") ? callee.member : callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
949
+ const instance = callee.local ? classInstances.get(callee.local) : void 0;
950
+ const importSource = instance?.importSource ?? proxy?.importSource ?? (callee.local ? imports.get(callee.local) : void 0) ?? (callee.member && callee.local ? imports.get(callee.local) : void 0);
951
+ const targetName = instance && callee.member ? `${instance.className}.${callee.member}` : proxy && callee.member ? callee.member : callee.expression.startsWith("this.") ? callee.member : callee.member && callee.local ? `${callee.local}.${callee.member}` : callee.local;
858
952
  const className = caller.qualifiedName.includes(".") ? caller.qualifiedName.split(".")[0] : void 0;
859
953
  const thisTarget = className && callee.member ? `${className}.${callee.member}` : void 0;
860
954
  const loggerLike = callee.receiver?.endsWith(".logger") || callee.local === "logger" || (callee.expression.startsWith("this.logger.") && callee.member ? loggerMembers.has(callee.member) : false);
@@ -862,11 +956,12 @@ async function parseExecutableSymbols(repoPath, filePath) {
862
956
  const provenLocal = Boolean(targetName) && localCallables.has(String(targetName));
863
957
  const provenThisMethod = Boolean(thisTarget && localCallables.has(thisTarget));
864
958
  const provenRelativeImport = Boolean(isRelativeImport(importSource) && targetName);
959
+ const provenClassInstance = Boolean(instance && callee.member && targetName);
865
960
  const importedFromPackage = Boolean(importSource && !isRelativeImport(importSource));
866
961
  const ignored = loggerLike || terminalMember || importedFromPackage || ignoredFrameworkCall(callee);
867
962
  const resolvedTarget = provenThisMethod ? thisTarget : targetName;
868
- const keep = Boolean(resolvedTarget) && !ignored && (provenLocal || provenThisMethod || provenRelativeImport);
869
- if (keep) calls.push({ callerQualifiedName: caller.qualifiedName, calleeExpression: callee.expression, calleeLocalName: resolvedTarget, receiverLocalName: callee.member ? callee.local : void 0, importSource, sourceFile, sourceLine: line, evidence: { relation: proxy ? "relative_import_proxy_member" : importSource ? "relative_import" : provenThisMethod ? "indexed_this_method" : "indexed_local_symbol", caller: caller.qualifiedName, targetName: resolvedTarget, proxyVariableName: proxy?.variableName, factory: proxy?.factory, factoryExpression: proxy?.factory, factoryImportSource: proxy?.importSource, candidateStrategy: proxy ? "proxy_member_exact_export_or_unique_member" : void 0 } });
963
+ const keep = Boolean(resolvedTarget) && !ignored && (provenLocal || provenThisMethod || provenRelativeImport || provenClassInstance);
964
+ if (keep) calls.push({ callerQualifiedName: caller.qualifiedName, calleeExpression: callee.expression, calleeLocalName: resolvedTarget, receiverLocalName: callee.member ? callee.local : void 0, importSource, sourceFile, sourceLine: line, evidence: { relation: instance ? "class_instance_method" : proxy ? "relative_import_proxy_member" : importSource ? "relative_import" : provenThisMethod ? "indexed_this_method" : "indexed_local_symbol", caller: caller.qualifiedName, targetName: resolvedTarget, instanceVariable: instance ? callee.local : void 0, className: instance?.className, methodName: instance ? callee.member : void 0, classImportSource: instance?.importSource, callArguments: argumentEvidence(node.arguments, source), proxyVariableName: proxy?.variableName, factory: proxy?.factory, factoryExpression: proxy?.factory, factoryImportSource: proxy?.importSource, candidateStrategy: instance ? instance.importSource ? "relative_import_class_instance_method" : "same_file_class_instance_method" : proxy ? "proxy_member_exact_export_or_unique_member" : void 0 } });
870
965
  }
871
966
  }
872
967
  ts.forEachChild(node, visitCalls);
@@ -1183,11 +1278,15 @@ function parserQualityDiagnostics(db, strict) {
1183
1278
  const aliasQuality = identityAliasBindingQuality(db);
1184
1279
  const noBindingQuality = remoteActionNoBindingQuality(db);
1185
1280
  const contextualQuality = contextualImplementationQuality(db);
1281
+ const classInstanceQuality = classInstanceNoiseQuality(db);
1282
+ const bindingPropagationQuality = contextualBindingPropagationQuality(db);
1186
1283
  const wrapperQuality = wrapperPathPropagationQuality(db);
1187
1284
  return [
1188
1285
  aliasQuality,
1189
1286
  noBindingQuality,
1190
1287
  contextualQuality,
1288
+ classInstanceQuality,
1289
+ bindingPropagationQuality,
1191
1290
  wrapperQuality,
1192
1291
  remoteQuery,
1193
1292
  invocation,
@@ -1211,22 +1310,68 @@ function identityAliasBindingQuality(db) {
1211
1310
  return { severity: examples.length > 0 ? "warning" : "info", code: "strict_identity_alias_binding_quality", message: "Remote sends that look like missed same-file identity aliases", missedAliasBindingCalls: examples.length, examples };
1212
1311
  }
1213
1312
  function remoteActionNoBindingQuality(db) {
1214
- const rows = db.prepare(`SELECT COALESCE(json_extract(c.evidence_json,'$.receiver'),'unknown') receiverName,COALESCE(e.status,'missing_edge') status,CASE WHEN operation_path_expr LIKE '%(%' THEN 1 ELSE 0 END isODataInvocation,COUNT(*) count
1313
+ const categoryCase = `CASE
1314
+ WHEN c.unresolved_reason='dynamic_operation_path_identifier' THEN 'dynamic_path_identifier'
1315
+ WHEN json_extract(c.evidence_json,'$.classifier')='higher_order_wrapper_literal_path' OR json_extract(c.evidence_json,'$.operationPathExpression') IS NOT NULL THEN 'likely_higher_order_wrapper_path_needed'
1316
+ WHEN json_extract(c.evidence_json,'$.receiver') LIKE '%.%' THEN 'likely_parameter_context_needed'
1317
+ WHEN EXISTS (
1318
+ SELECT 1 FROM symbol_calls sc
1319
+ JOIN symbols caller ON caller.id=sc.caller_symbol_id
1320
+ JOIN symbols callee ON callee.id=sc.callee_symbol_id
1321
+ WHERE sc.status='resolved'
1322
+ AND sc.source_file=c.source_file
1323
+ AND caller.id=c.source_symbol_id
1324
+ AND json_extract(sc.evidence_json,'$.relation')='class_instance_method'
1325
+ AND (callee.evidence_json IS NULL OR json_extract(callee.evidence_json,'$.parameterBindings') IS NULL)
1326
+ ) THEN 'likely_instance_method_parameter_metadata_needed'
1327
+ WHEN EXISTS (SELECT 1 FROM service_bindings b WHERE b.repo_id=c.repo_id AND b.source_file=c.source_file AND ABS(b.source_line-c.source_line) < 50) THEN 'likely_missing_assignment_binding'
1328
+ WHEN e.status='unresolved' AND COALESCE(e.unresolved_reason,'') LIKE '%No indexed target operation%' THEN 'no_indexed_target_operation'
1329
+ WHEN c.operation_path_expr IS NOT NULL AND (c.operation_path_expr LIKE '/%' OR c.operation_path_expr NOT LIKE '%/%') THEN 'operation_path_only_no_static_service_signal'
1330
+ ELSE 'external_or_entity_path_not_action' END`;
1331
+ const rows = db.prepare(`SELECT ${categoryCase} category,COALESCE(e.status,'missing_edge') status,COUNT(*) count
1215
1332
  FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
1216
1333
  WHERE c.call_type='remote_action' AND c.operation_path_expr IS NOT NULL AND c.service_binding_id IS NULL
1217
- GROUP BY receiverName,status,isODataInvocation ORDER BY count DESC,receiverName,status`).all();
1218
- const examples = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath,COALESCE(e.status,'missing_edge') status,
1219
- CASE WHEN EXISTS (SELECT 1 FROM service_bindings b WHERE b.repo_id=c.repo_id AND b.source_file=c.source_file AND COALESCE(b.helper_chain_json,'') LIKE '%' || '"aliasOf":"' || json_extract(c.evidence_json,'$.receiver') || '"' || '%') THEN 'likely_missed_alias_or_helper_propagation' ELSE 'operation_path_only_call' END category
1334
+ GROUP BY category,status ORDER BY count DESC,category,status`).all();
1335
+ const examples = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath,COALESCE(e.status,'missing_edge') status,${categoryCase} category
1220
1336
  FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
1221
- WHERE c.call_type='remote_action' AND c.operation_path_expr IS NOT NULL AND c.service_binding_id IS NULL ORDER BY c.source_file,c.source_line LIMIT 5`).all();
1337
+ WHERE c.call_type='remote_action' AND c.operation_path_expr IS NOT NULL AND c.service_binding_id IS NULL ORDER BY c.source_file,c.source_line LIMIT 8`).all();
1222
1338
  const total = rows.reduce((sum, row) => sum + Number(row.count ?? 0), 0);
1223
1339
  return { severity: total > 0 ? "warning" : "info", code: "strict_remote_action_no_binding_quality", message: "Remote actions with operation paths but no service binding id", total, breakdown: rows, examples };
1224
1340
  }
1341
+ function classInstanceNoiseQuality(db) {
1342
+ const builtIns = ["Set", "Map", "WeakSet", "WeakMap", "Date", "RegExp", "URL", "URLSearchParams", "Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "AggregateError", "ArrayBuffer", "SharedArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array", "Promise", "AbortController"];
1343
+ const placeholders = builtIns.map(() => "?").join(",");
1344
+ const aggregate = db.prepare(`SELECT COUNT(*) total,
1345
+ SUM(CASE WHEN status='unresolved' THEN 1 ELSE 0 END) unresolved,
1346
+ SUM(CASE WHEN status='unresolved' AND json_extract(evidence_json,'$.className') IN (${placeholders}) THEN 1 ELSE 0 END) unresolvedBuiltIn
1347
+ FROM symbol_calls WHERE json_extract(evidence_json,'$.relation')='class_instance_method'`).get(...builtIns);
1348
+ const byConstructor = db.prepare(`SELECT json_extract(evidence_json,'$.className') constructorName,COUNT(*) unresolvedCount
1349
+ FROM symbol_calls WHERE status='unresolved' AND json_extract(evidence_json,'$.relation')='class_instance_method'
1350
+ GROUP BY constructorName ORDER BY unresolvedCount DESC,constructorName LIMIT 10`).all();
1351
+ return { severity: Number(aggregate.unresolvedBuiltIn ?? 0) > 0 ? "warning" : "info", code: "strict_class_instance_noise_quality", message: "Class-instance symbol-call aggregate with built-in constructor guard", totalClassInstanceCalls: Number(aggregate.total ?? 0), unresolvedClassInstanceCalls: Number(aggregate.unresolved ?? 0), unresolvedBuiltInClassInstanceCalls: Number(aggregate.unresolvedBuiltIn ?? 0), unresolvedByConstructor: byConstructor };
1352
+ }
1353
+ function contextualBindingPropagationQuality(db) {
1354
+ const serviceClientCalls = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc
1355
+ WHERE json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')`).get();
1356
+ const missingMetadata = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
1357
+ WHERE sc.status='resolved' AND json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')
1358
+ AND (s.evidence_json IS NULL OR json_extract(s.evidence_json,'$.parameterBindings') IS NULL)`).get();
1359
+ const destructuredUnmapped = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
1360
+ WHERE json_extract(sc.evidence_json,'$.callArguments[0].kind')='object_literal'
1361
+ AND json_extract(s.evidence_json,'$.parameterBindings[0].kind')='object_pattern'
1362
+ AND json_array_length(json_extract(sc.evidence_json,'$.callArguments[0].properties')) > json_array_length(json_extract(s.evidence_json,'$.parameterBindings[0].properties'))`).get();
1363
+ const contextualResolved = db.prepare(`SELECT COUNT(*) count FROM graph_edges WHERE json_extract(evidence_json,'$.contextualServiceBindingSelected')=1 AND status='resolved'`).get();
1364
+ return { severity: Number(missingMetadata.count ?? 0) + Number(destructuredUnmapped.count ?? 0) > 0 ? "warning" : "info", code: "strict_contextual_binding_propagation_quality", message: "Contextual service-client propagation aggregate", localSymbolCallsWithServiceClientArguments: Number(serviceClientCalls.count ?? 0), calleeSymbolsMissingParameterMetadata: Number(missingMetadata.count ?? 0), destructuredObjectParametersPossiblyUnmapped: Number(destructuredUnmapped.count ?? 0), contextualRemoteActionsResolved: Number(contextualResolved.count ?? 0) };
1365
+ }
1225
1366
  function contextualImplementationQuality(db) {
1226
- const rows = db.prepare(`SELECT COALESCE(json_extract(evidence_json,'$.contextualImplementation.status'),'not_applicable') status,COUNT(*) count
1227
- FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status IN ('ambiguous','unresolved') GROUP BY status ORDER BY count DESC,status`).all();
1367
+ const rows = db.prepare(`SELECT status,COALESCE(unresolved_reason,status) reason,COUNT(*) count
1368
+ FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status IN ('ambiguous','unresolved') GROUP BY status,reason ORDER BY status,count DESC,reason`).all();
1369
+ const examples = db.prepare(`SELECT json_extract(evidence_json,'$.servicePath') servicePath,json_extract(evidence_json,'$.operationPath') operationPath,status,unresolved_reason unresolvedReason,
1370
+ json_extract(evidence_json,'$.candidates[0].rejectedReasons[0]') topRejectedReason,
1371
+ json_extract(evidence_json,'$.candidates[0].acceptedReasons[0]') topAcceptedReason
1372
+ FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status IN ('ambiguous','unresolved') ORDER BY status,id LIMIT 6`).all();
1228
1373
  const total = rows.reduce((sum, row) => sum + Number(row.count ?? 0), 0);
1229
- return { severity: total > 0 ? "warning" : "info", code: "strict_contextual_implementation_quality", message: "Runtime-resolved implementation hops stopped by ambiguous or unresolved implementation edges", total, breakdown: rows };
1374
+ return { severity: total > 0 ? "warning" : "info", code: "strict_contextual_implementation_quality", message: "Implementation hops stopped by ambiguous or unresolved implementation edges", total, breakdown: rows, examples };
1230
1375
  }
1231
1376
  function wrapperPathPropagationQuality(db) {
1232
1377
  const examples = db.prepare(`SELECT source_file sourceFile,source_line sourceLine,json_extract(evidence_json,'$.receiver') receiverName,json_extract(evidence_json,'$.operationPathExpression') pathIdentifier,CASE WHEN json_extract(evidence_json,'$.literalCallerArgumentDetected') IS NOT NULL THEN 1 ELSE 0 END literalCallerArgumentDetected