@saptools/service-flow 0.1.52 → 0.1.53

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +9 -12
  3. package/TECHNICAL-NOTE.md +11 -3
  4. package/dist/{chunk-PTLDSHRC.js → chunk-LFH7C46B.js} +2214 -1044
  5. package/dist/chunk-LFH7C46B.js.map +1 -0
  6. package/dist/cli.js +157 -11
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/package.json +1 -1
  10. package/src/cli/001-doctor-projection.ts +140 -0
  11. package/src/cli/doctor.ts +20 -3
  12. package/src/db/repositories.ts +10 -2
  13. package/src/linker/000-implementation-candidates.ts +641 -0
  14. package/src/linker/001-implementation-evidence-projection.ts +119 -0
  15. package/src/linker/002-call-evidence.ts +226 -0
  16. package/src/linker/cross-repo-linker.ts +27 -453
  17. package/src/linker/dynamic-edge-resolver.ts +35 -0
  18. package/src/linker/external-http-target.ts +24 -3
  19. package/src/linker/helper-package-linker.ts +18 -2
  20. package/src/linker/service-resolver.ts +45 -2
  21. package/src/output/doctor-output.ts +13 -4
  22. package/src/output/table-output.ts +4 -2
  23. package/src/trace/000-dynamic-target-types.ts +12 -0
  24. package/src/trace/001-dynamic-identity.ts +45 -17
  25. package/src/trace/003-dynamic-references.ts +236 -35
  26. package/src/trace/004-dynamic-candidate-sources.ts +155 -0
  27. package/src/trace/005-implementation-selection.ts +187 -0
  28. package/src/trace/006-contextual-projection.ts +30 -0
  29. package/src/trace/007-implementation-start-diagnostic.ts +61 -0
  30. package/src/trace/dynamic-targets.ts +205 -159
  31. package/src/trace/evidence.ts +35 -9
  32. package/src/trace/implementation-hints.ts +148 -8
  33. package/src/trace/selectors.ts +74 -9
  34. package/src/trace/trace-engine.ts +39 -52
  35. package/src/utils/000-bounded-projection.ts +161 -0
  36. package/dist/chunk-PTLDSHRC.js.map +0 -1
package/dist/cli.js CHANGED
@@ -29,12 +29,14 @@ import {
29
29
  parsePackageJson,
30
30
  parseServiceBindings,
31
31
  parseVars,
32
+ projectBounded,
32
33
  reposByName,
33
34
  selectorRepoAmbiguousDiagnostic,
35
+ stableProjectionValue,
34
36
  trace,
35
37
  upsertRepository,
36
38
  upsertWorkspace
37
- } from "./chunk-PTLDSHRC.js";
39
+ } from "./chunk-LFH7C46B.js";
38
40
 
39
41
  // src/cli.ts
40
42
  import { Command } from "commander";
@@ -236,7 +238,7 @@ function migrate(db) {
236
238
  // package.json
237
239
  var package_default = {
238
240
  name: "@saptools/service-flow",
239
- version: "0.1.52",
241
+ version: "0.1.53",
240
242
  description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
241
243
  type: "module",
242
244
  publishConfig: {
@@ -1130,13 +1132,131 @@ async function indexWorkspace(db, workspaceId, options) {
1130
1132
  }
1131
1133
  }
1132
1134
 
1135
+ // src/cli/001-doctor-projection.ts
1136
+ var boundedDoctorArrayKeys = /* @__PURE__ */ new Set([
1137
+ "candidates",
1138
+ "candidateScores",
1139
+ "candidateFamilies",
1140
+ "candidateEvidence",
1141
+ "candidatePaths",
1142
+ "candidateRawPaths",
1143
+ "candidateNormalizedOperationPaths",
1144
+ "normalizedCandidateOperations",
1145
+ "candidateLiterals",
1146
+ "bindingCandidates",
1147
+ "bindingAlternatives",
1148
+ "registrations",
1149
+ "implementationHintSuggestions",
1150
+ "selectableImplementationRepositories",
1151
+ "matchedHints",
1152
+ "candidateSuggestions",
1153
+ "dynamicTargetCandidates",
1154
+ "dynamicTargetCandidateSuggestions",
1155
+ "rejectedCandidates",
1156
+ "suggestedVarSets",
1157
+ "copyableExamples",
1158
+ "examples",
1159
+ "expandedExamples",
1160
+ "selectorSuggestions",
1161
+ "serviceSuggestions",
1162
+ "repositories"
1163
+ ]);
1164
+ function boundDoctorDiagnostics(diagnostics) {
1165
+ return diagnostics.map(boundDoctorDiagnostic);
1166
+ }
1167
+ function boundDoctorDiagnostic(diagnostic) {
1168
+ const bounded = boundDoctorValue(diagnostic);
1169
+ return isDiagnostic(bounded) ? bounded : {};
1170
+ }
1171
+ function boundDoctorValue(value) {
1172
+ if (Array.isArray(value)) return value.map(boundDoctorValue);
1173
+ if (!isDiagnostic(value)) return value;
1174
+ const input = value;
1175
+ const output = {};
1176
+ for (const [key, child] of Object.entries(input)) {
1177
+ if (!Array.isArray(child) || !boundedDoctorArrayKeys.has(key)) {
1178
+ output[key] = boundDoctorValue(child);
1179
+ continue;
1180
+ }
1181
+ const projection = projectBounded(child.map(boundDoctorValue), compareDiagnostic);
1182
+ output[key] = projection.items;
1183
+ addProjectionMetadata(output, input, key, projection);
1184
+ }
1185
+ return output;
1186
+ }
1187
+ function isDiagnostic(value) {
1188
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1189
+ }
1190
+ function addProjectionMetadata(output, input, key, projection) {
1191
+ const names = projectionNames(key);
1192
+ const total = Math.max(
1193
+ numericValue(input[names.total]),
1194
+ projection.totalCount,
1195
+ siblingCollectionCount(input, key)
1196
+ );
1197
+ output[names.total] = total;
1198
+ output[names.shown] = projection.shownCount;
1199
+ output[names.omitted] = Math.max(0, total - projection.shownCount);
1200
+ }
1201
+ function siblingCollectionCount(input, key) {
1202
+ if (key !== "examples" || !Array.isArray(input.expandedExamples)) return 0;
1203
+ return input.expandedExamples.length;
1204
+ }
1205
+ function projectionNames(key) {
1206
+ const stem = projectionStem(key);
1207
+ return {
1208
+ total: `${stem}Count`,
1209
+ shown: `shown${upperFirst(stem)}Count`,
1210
+ omitted: `omitted${upperFirst(stem)}Count`
1211
+ };
1212
+ }
1213
+ function projectionStem(key) {
1214
+ const stems = {
1215
+ candidates: "candidate",
1216
+ candidateScores: "candidateScore",
1217
+ candidateFamilies: "candidateFamily",
1218
+ candidateEvidence: "candidateEvidence",
1219
+ candidatePaths: "candidatePath",
1220
+ candidateRawPaths: "candidateRawPath",
1221
+ candidateNormalizedOperationPaths: "candidateNormalizedOperationPath",
1222
+ normalizedCandidateOperations: "normalizedCandidateOperation",
1223
+ candidateLiterals: "candidateLiteral",
1224
+ bindingCandidates: "bindingCandidate",
1225
+ bindingAlternatives: "bindingAlternative",
1226
+ registrations: "registration",
1227
+ implementationHintSuggestions: "implementationHintSuggestion",
1228
+ selectableImplementationRepositories: "selectableImplementationRepository",
1229
+ matchedHints: "matchedHint",
1230
+ candidateSuggestions: "candidateSuggestion",
1231
+ dynamicTargetCandidates: "dynamicTargetCandidate",
1232
+ dynamicTargetCandidateSuggestions: "dynamicTargetCandidateSuggestion",
1233
+ rejectedCandidates: "rejectedCandidate",
1234
+ suggestedVarSets: "suggestedVarSet",
1235
+ copyableExamples: "copyableExample",
1236
+ examples: "example",
1237
+ expandedExamples: "expandedExample",
1238
+ selectorSuggestions: "selectorSuggestion",
1239
+ serviceSuggestions: "serviceSuggestion"
1240
+ };
1241
+ return stems[key] ?? "repository";
1242
+ }
1243
+ function compareDiagnostic(left, right) {
1244
+ return stableProjectionValue(left).localeCompare(stableProjectionValue(right));
1245
+ }
1246
+ function numericValue(value) {
1247
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1248
+ }
1249
+ function upperFirst(value) {
1250
+ return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
1251
+ }
1252
+
1133
1253
  // src/cli/doctor.ts
1134
1254
  function linkUpgradeWarnings(db) {
1135
1255
  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)));
1136
1256
  }
1137
1257
  function doctorDiagnostics(db, strict, options = {}) {
1138
1258
  const diagnostics = db.prepare("SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id").all();
1139
- return [
1259
+ return boundDoctorDiagnostics([
1140
1260
  ...diagnostics,
1141
1261
  ...healthDiagnostics(db, strict),
1142
1262
  ...remoteTargetWithoutImplementationDiagnostics(db, strict, Boolean(options.detail)),
@@ -1144,7 +1264,7 @@ function doctorDiagnostics(db, strict, options = {}) {
1144
1264
  ...schemaDriftDiagnostics(db, strict),
1145
1265
  ...analyzerVersionDiagnostics(db, strict),
1146
1266
  ...parserQualityDiagnostics(db, strict, options)
1147
- ];
1267
+ ]);
1148
1268
  }
1149
1269
  function healthDiagnostics(db, strict) {
1150
1270
  return db.prepare(
@@ -1356,6 +1476,10 @@ function addImplementationCategory(grouped, row) {
1356
1476
  const current = grouped.get(key) ?? { category, baseOperation, reason, candidateFamily: family, count: 0, servicePaths: [], examples: [] };
1357
1477
  const hintSuggestions = implementationSuggestions(evidence);
1358
1478
  const candidates = asRecords(evidence.candidates);
1479
+ const candidateCount2 = Math.max(
1480
+ numericValue2(evidence.candidateCount),
1481
+ candidates.length
1482
+ );
1359
1483
  current.count += 1;
1360
1484
  current.servicePaths.push(String(row.servicePath ?? evidence.servicePath ?? ""));
1361
1485
  current.examples.push({
@@ -1363,7 +1487,15 @@ function addImplementationCategory(grouped, row) {
1363
1487
  operation: row.operationName,
1364
1488
  status: row.status,
1365
1489
  reason: row.unresolvedReason,
1366
- candidateCount: candidates.length,
1490
+ candidateCount: candidateCount2,
1491
+ shownCandidateCount: Math.min(
1492
+ candidateCount2,
1493
+ numericValue2(evidence.shownCandidateCount) || candidates.length
1494
+ ),
1495
+ omittedCandidateCount: Math.max(
1496
+ 0,
1497
+ candidateCount2 - (numericValue2(evidence.shownCandidateCount) || candidates.length)
1498
+ ),
1367
1499
  candidateEvidence: candidates.slice(0, 3),
1368
1500
  implementationHintSuggestions: hintSuggestions
1369
1501
  });
@@ -1701,6 +1833,9 @@ function remoteActionNoBindingQuality(db) {
1701
1833
  function candidateCount(value) {
1702
1834
  return Number(parseObject(value).candidateCount ?? 0);
1703
1835
  }
1836
+ function numericValue2(value) {
1837
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1838
+ }
1704
1839
  function parseObject(value) {
1705
1840
  if (value && typeof value === "object" && !Array.isArray(value)) return value;
1706
1841
  try {
@@ -1766,8 +1901,10 @@ function hintLines(evidence) {
1766
1901
  const hints = suggestions.flatMap((item) => isRecord(item) && typeof item.cli === "string" ? [item.cli] : []);
1767
1902
  const unique = [...new Set(hints)];
1768
1903
  const shown = unique.slice(0, 3).map((hint) => `try ${hint}`);
1769
- if (unique.length > shown.length)
1770
- shown.push(`... ${unique.length - shown.length} more hint(s) available in JSON`);
1904
+ const omitted = numberValue(evidence.omittedImplementationHintSuggestionCount);
1905
+ const remaining = Math.max(0, unique.length - shown.length) + omitted;
1906
+ if (remaining > 0)
1907
+ shown.push(`... ${remaining} additional hint(s) omitted; use a scoped --implementation-hint`);
1771
1908
  return [...dynamicLines, ...shown];
1772
1909
  }
1773
1910
  function dynamicHintLines(evidence) {
@@ -1857,8 +1994,12 @@ function compactMessage(diagnostic) {
1857
1994
  }
1858
1995
  function suggestedHintLines(diagnostic) {
1859
1996
  const direct = cliHints(diagnostic.suggestedHints);
1860
- if (direct.length > 0) return cappedHints(direct);
1861
- return cappedHints(cliHintsFromSuggestions(diagnostic.implementationHintSuggestions));
1997
+ const omitted = numericValue3(diagnostic.omittedImplementationHintSuggestionCount);
1998
+ if (direct.length > 0) return cappedHints(direct, omitted);
1999
+ return cappedHints(
2000
+ cliHintsFromSuggestions(diagnostic.implementationHintSuggestions),
2001
+ omitted
2002
+ );
1862
2003
  }
1863
2004
  function cliHints(value) {
1864
2005
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
@@ -1873,12 +2014,17 @@ function cliHintsFromSuggestions(value) {
1873
2014
  function isRecord2(value) {
1874
2015
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1875
2016
  }
1876
- function cappedHints(hints) {
2017
+ function cappedHints(hints, omitted) {
1877
2018
  const unique = [...new Set(hints)];
1878
2019
  const shown = unique.slice(0, 3);
1879
- if (unique.length > shown.length) shown.push(`... ${unique.length - shown.length} more hint(s) available in --format json`);
2020
+ const remaining = Math.max(0, unique.length - shown.length) + omitted;
2021
+ if (remaining > 0)
2022
+ shown.push(`... ${remaining} additional hint(s) omitted; use a scoped --implementation-hint`);
1880
2023
  return shown;
1881
2024
  }
2025
+ function numericValue3(value) {
2026
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
2027
+ }
1882
2028
  function cleanDoctorMessage() {
1883
2029
  return `${pc.green("No diagnostics recorded")}
1884
2030
  `;