@saptools/service-flow 0.1.44 → 0.1.45

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
@@ -9,11 +9,12 @@ import {
9
9
  parseCdsFile,
10
10
  parseDecorators,
11
11
  parseHandlerRegistrations,
12
+ parseImplementationHint,
12
13
  parseOutboundCalls,
13
14
  parsePackageJson,
14
15
  parseServiceBindings,
15
16
  trace
16
- } from "./chunk-UKNPHTUS.js";
17
+ } from "./chunk-BXSCE5CR.js";
17
18
 
18
19
  // src/cli.ts
19
20
  import { Command } from "commander";
@@ -209,7 +210,7 @@ function migrate(db) {
209
210
  // package.json
210
211
  var package_default = {
211
212
  name: "@saptools/service-flow",
212
- version: "0.1.44",
213
+ version: "0.1.45",
213
214
  description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
214
215
  type: "module",
215
216
  publishConfig: {
@@ -814,6 +815,10 @@ function argumentEvidence(args, source) {
814
815
  }
815
816
  return { kind: "object_literal", properties };
816
817
  }
818
+ if (ts.isArrayLiteralExpression(arg)) {
819
+ const elements = arg.elements.flatMap((element, index) => ts.isIdentifier(element) ? [{ index, kind: "identifier", name: element.text }] : []);
820
+ return { kind: "array_literal", elements };
821
+ }
817
822
  return { kind: "unsupported", expression: arg.getText(source) };
818
823
  });
819
824
  }
@@ -860,15 +865,21 @@ function parameterPropertyAliases(fn, source) {
860
865
  function parameterBindings(params) {
861
866
  return params.flatMap((param, index) => {
862
867
  if (ts.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
863
- if (!ts.isObjectBindingPattern(param.name)) return [];
864
- const properties = param.name.elements.flatMap((element) => {
865
- if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
866
- const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
867
- if (!property) return [];
868
- const local = bindingLocalName(element.name, element.initializer);
869
- return local ? [{ property, local }] : [];
870
- });
871
- return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
868
+ if (ts.isObjectBindingPattern(param.name)) {
869
+ const properties = param.name.elements.flatMap((element) => {
870
+ if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
871
+ const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
872
+ if (!property) return [];
873
+ const local = bindingLocalName(element.name, element.initializer);
874
+ return local ? [{ property, local }] : [];
875
+ });
876
+ return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
877
+ }
878
+ if (ts.isArrayBindingPattern(param.name)) {
879
+ const elements = param.name.elements.flatMap((element, elementIndex) => ts.isBindingElement(element) && !element.dotDotDotToken && ts.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
880
+ return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
881
+ }
882
+ return [];
872
883
  });
873
884
  }
874
885
  async function parseExecutableSymbols(repoPath, filePath) {
@@ -1295,7 +1306,7 @@ async function indexWorkspace(db, workspaceId, options) {
1295
1306
  function linkUpgradeWarnings(db) {
1296
1307
  return [...schemaDriftDiagnostics(db, true), ...analyzerVersionDiagnostics(db, true)].filter((item) => ["schema_legacy_columns_present", "external_target_columns_missing_data", "reindex_required_after_upgrade", "reindex_required_after_analyzer_upgrade"].includes(String(item.code)));
1297
1308
  }
1298
- function doctorDiagnostics(db, strict) {
1309
+ function doctorDiagnostics(db, strict, options = {}) {
1299
1310
  const diagnostics = db.prepare("SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id").all();
1300
1311
  return [
1301
1312
  ...diagnostics,
@@ -1303,7 +1314,7 @@ function doctorDiagnostics(db, strict) {
1303
1314
  ...localServiceDiagnostics(db, strict),
1304
1315
  ...schemaDriftDiagnostics(db, strict),
1305
1316
  ...analyzerVersionDiagnostics(db, strict),
1306
- ...parserQualityDiagnostics(db, strict)
1317
+ ...parserQualityDiagnostics(db, strict, options)
1307
1318
  ];
1308
1319
  }
1309
1320
  function healthDiagnostics(db, strict) {
@@ -1380,7 +1391,7 @@ function localServiceDiagnostics(db, strict) {
1380
1391
  if (strict && implementationContext > 0) out.push({ severity: "info", code: "local_service_calls_resolved_by_implementation_context", message: `Local service calls resolved by implementation-context ownership: ${implementationContext}` });
1381
1392
  return out;
1382
1393
  }
1383
- function parserQualityDiagnostics(db, strict) {
1394
+ function parserQualityDiagnostics(db, strict, options) {
1384
1395
  if (!strict) return [];
1385
1396
  const symbol = symbolCallQuality(db);
1386
1397
  const dbq = dbQueryQuality(db);
@@ -1389,7 +1400,7 @@ function parserQualityDiagnostics(db, strict) {
1389
1400
  identityAliasBindingQuality(db),
1390
1401
  remoteActionNoBindingQuality(db),
1391
1402
  contextualImplementationQuality(db),
1392
- implementationCandidateQuality(db),
1403
+ implementationCandidateQuality(db, Boolean(options.detail)),
1393
1404
  classInstanceNoiseQuality(db),
1394
1405
  contextualBindingPropagationQuality(db),
1395
1406
  nestedThisReceiverQuality(db),
@@ -1452,19 +1463,25 @@ function graphDynamicFlagQuality(db) {
1452
1463
  const row = db.prepare("SELECT COUNT(*) count FROM graph_edges WHERE status='terminal' AND is_dynamic=1").get();
1453
1464
  return { severity: Number(row.count ?? 0) > 0 ? "warning" : "info", code: "strict_graph_dynamic_flag_consistency", message: "Graph dynamic flag consistency aggregate", dynamicTerminalEdges: Number(row.count ?? 0) };
1454
1465
  }
1455
- function implementationCandidateQuality(db) {
1456
- const categories = [...implementationEdgeCategories(db), missingParameterMetadataCategory(db)].filter((item) => item.count > 0);
1466
+ function implementationCandidateQuality(db, detail) {
1467
+ const categories = [...implementationEdgeCategories(db, detail), missingParameterMetadataCategory(db, detail), dynamicWrapperCategory(db, detail)].filter((item) => item.count > 0);
1457
1468
  const total = categories.reduce((sum, item) => sum + item.count, 0);
1458
- return { severity: total > 0 ? "warning" : "info", code: "strict_implementation_candidate_quality", message: "Implementation candidate ambiguity and rejection aggregate", total, categories };
1469
+ return { severity: total > 0 ? "warning" : "info", code: "strict_implementation_candidate_quality", message: "Implementation candidate ambiguity and rejection aggregate", total, summary: implementationSummary(categories), categories };
1459
1470
  }
1460
- function implementationEdgeCategories(db) {
1471
+ function implementationEdgeCategories(db, detail) {
1461
1472
  const rows = db.prepare(`SELECT e.status,e.unresolved_reason unresolvedReason,e.evidence_json evidenceJson,o.operation_name operationName,base.operation_name baseOperation,s.service_path servicePath
1462
1473
  FROM graph_edges e JOIN cds_operations o ON o.id=CAST(e.from_id AS INTEGER)
1463
1474
  JOIN cds_services s ON s.id=o.service_id LEFT JOIN cds_operations base ON base.id=o.base_operation_id
1464
1475
  WHERE e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status IN ('ambiguous','unresolved') ORDER BY s.service_path,o.operation_name,e.id`).all();
1465
1476
  const grouped = /* @__PURE__ */ new Map();
1466
1477
  for (const row of rows) addImplementationCategory(grouped, row);
1467
- return [...grouped.values()].map(({ servicePaths, ...item }) => ({ ...item, servicePathPattern: pathPattern(servicePaths), examples: item.examples.slice(0, 3) }));
1478
+ return [...grouped.values()].map(({ servicePaths, ...item }) => ({
1479
+ ...item,
1480
+ servicePathPattern: pathPattern(servicePaths),
1481
+ suggestedAction: categoryAction(String(item.category)),
1482
+ examples: item.examples.slice(0, 3),
1483
+ expandedExamples: detail ? item.examples : void 0
1484
+ }));
1468
1485
  }
1469
1486
  function addImplementationCategory(grouped, row) {
1470
1487
  const evidence = parseObject(row.evidenceJson);
@@ -1479,16 +1496,41 @@ function addImplementationCategory(grouped, row) {
1479
1496
  current.examples.push({ servicePath: row.servicePath, operation: row.operationName, status: row.status, reason: row.unresolvedReason });
1480
1497
  grouped.set(key, current);
1481
1498
  }
1482
- function missingParameterMetadataCategory(db) {
1499
+ function missingParameterMetadataCategory(db, detail = false) {
1483
1500
  const examples = db.prepare(`SELECT sc.source_file sourceFile,sc.source_line sourceLine,sc.callee_expression calleeExpression
1484
1501
  FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
1485
1502
  WHERE sc.status='resolved' AND json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')
1486
1503
  AND (s.evidence_json IS NULL OR json_extract(s.evidence_json,'$.parameterBindings') IS NULL)
1487
- ORDER BY sc.source_file,sc.source_line LIMIT 3`).all();
1504
+ ORDER BY sc.source_file,sc.source_line`).all();
1488
1505
  const row = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
1489
1506
  WHERE sc.status='resolved' AND json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')
1490
1507
  AND (s.evidence_json IS NULL OR json_extract(s.evidence_json,'$.parameterBindings') IS NULL)`).get();
1491
- return { category: "missing_parameter_metadata", reason: "callee symbol is missing parameter binding metadata", candidateFamily: "symbol_parameter_metadata", count: Number(row.count ?? 0), examples };
1508
+ return { category: "missing_parameter_metadata", reason: "callee symbol is missing parameter binding metadata", candidateFamily: "symbol_parameter_metadata", count: Number(row.count ?? 0), suggestedAction: categoryAction("missing_parameter_metadata"), examples: examples.slice(0, 3), expandedExamples: detail ? examples : void 0 };
1509
+ }
1510
+ function dynamicWrapperCategory(db, detail) {
1511
+ const rows = db.prepare(`SELECT source_file sourceFile,source_line sourceLine,
1512
+ json_extract(evidence_json,'$.receiver') receiverName,
1513
+ COALESCE(json_extract(evidence_json,'$.missingPathIdentifier'),json_extract(evidence_json,'$.operationPathExpression')) pathIdentifier
1514
+ FROM outbound_calls WHERE call_type='remote_action' AND unresolved_reason='dynamic_operation_path_identifier'
1515
+ ORDER BY source_file,source_line`).all();
1516
+ return { category: "dynamic_wrapper_paths", reason: "wrapper path cannot be proven statically", candidateFamily: "wrapper_path", count: rows.length, suggestedAction: categoryAction("dynamic_wrapper_paths"), examples: rows.slice(0, 3), expandedExamples: detail ? rows : void 0 };
1517
+ }
1518
+ function implementationSummary(categories) {
1519
+ const grouped = /* @__PURE__ */ new Map();
1520
+ for (const category of categories) {
1521
+ const key = [category.category, category.candidateFamily, category.reason].join("\0");
1522
+ const current = grouped.get(key) ?? { category: category.category, candidateFamily: category.candidateFamily, reason: category.reason, severity: "warning", suggestedAction: category.suggestedAction, count: 0 };
1523
+ current.count += category.count;
1524
+ grouped.set(key, current);
1525
+ }
1526
+ return [...grouped.values()].sort((left, right) => String(left.category).localeCompare(String(right.category)) || String(left.candidateFamily).localeCompare(String(right.candidateFamily)));
1527
+ }
1528
+ function categoryAction(category) {
1529
+ if (category === "duplicate_package_name_candidates") return "Use scoped --implementation-hint fields to select one repository for each ambiguous hop.";
1530
+ if (category === "missing_strong_ownership_evidence") return "Add an explicit package dependency, local service-path ownership, or registration ownership evidence.";
1531
+ if (category === "missing_parameter_metadata") return "Export a statically analyzable helper with named or destructured parameters.";
1532
+ if (category === "dynamic_wrapper_paths") return "Pass a literal path or provide the reported runtime identifier with --var key=value.";
1533
+ return "Inspect the capped examples and add stronger implementation ownership evidence.";
1492
1534
  }
1493
1535
  function implementationCategory(row, evidence) {
1494
1536
  const reasons = JSON.stringify([evidence.ambiguityReasons, evidence.candidateFamilies, evidence.candidates, row.unresolvedReason]);
@@ -1788,7 +1830,7 @@ function createProgram() {
1788
1830
  );
1789
1831
  }).catch(fail)
1790
1832
  );
1791
- program.command("trace").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--handler <name>").option("--depth <n>", "trace depth", "25").option("--format <format>", "table|json|mermaid", "table").option("--include-external").option("--include-db").option("--include-async").option("--implementation-repo <name>").option("--var <key=value>", "dynamic variable", collect, []).action(
1833
+ program.command("trace").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--handler <name>").option("--depth <n>", "trace depth", "25").option("--format <format>", "table|json|mermaid", "table").option("--include-external").option("--include-db").option("--include-async").option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).action(
1792
1834
  (opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
1793
1835
  const result = trace(
1794
1836
  db,
@@ -1805,7 +1847,8 @@ function createProgram() {
1805
1847
  includeExternal: Boolean(opts.includeExternal),
1806
1848
  includeDb: Boolean(opts.includeDb),
1807
1849
  includeAsync: Boolean(opts.includeAsync),
1808
- implementationRepo: opts.implementationRepo
1850
+ implementationRepo: opts.implementationRepo,
1851
+ implementationHints: opts.implementationHint.map(parseImplementationHint)
1809
1852
  }
1810
1853
  );
1811
1854
  process.stdout.write(
@@ -1874,7 +1917,7 @@ function createProgram() {
1874
1917
  process.stdout.write(renderJson(rows));
1875
1918
  }).catch(fail)
1876
1919
  );
1877
- program.command("graph").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--format <format>", "mermaid|json", "mermaid").option("--implementation-repo <name>").option("--var <key=value>", "dynamic variable", collect, []).action(
1920
+ program.command("graph").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--format <format>", "mermaid|json", "mermaid").option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).action(
1878
1921
  (opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
1879
1922
  const result = trace(
1880
1923
  db,
@@ -1890,7 +1933,8 @@ function createProgram() {
1890
1933
  includeDb: true,
1891
1934
  includeExternal: true,
1892
1935
  vars: parseVars(opts.var),
1893
- implementationRepo: opts.implementationRepo
1936
+ implementationRepo: opts.implementationRepo,
1937
+ implementationHints: opts.implementationHint.map(parseImplementationHint)
1894
1938
  }
1895
1939
  );
1896
1940
  process.stdout.write(
@@ -1915,9 +1959,9 @@ function createProgram() {
1915
1959
  process.stdout.write(renderJson(rows));
1916
1960
  }).catch(fail)
1917
1961
  );
1918
- program.command("doctor").option("--workspace <path>").option("--strict").action(
1962
+ program.command("doctor").option("--workspace <path>").option("--strict").option("--detail").action(
1919
1963
  (opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
1920
- const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict));
1964
+ const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict), { detail: Boolean(opts.detail) });
1921
1965
  process.stdout.write(
1922
1966
  allDiagnostics.length ? renderJson(allDiagnostics) : `${pc.green("No diagnostics recorded")}
1923
1967
  `