@saptools/service-flow 0.1.45 → 0.1.47

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.46
4
+
5
+ - Improved ambiguous implementation diagnostics with ready-to-copy scoped hint suggestions for each blocked helper hop.
6
+ - Persisted deterministic service-client ownership across helper boundaries, object and tuple destructuring, returned clients, and transaction aliases while preserving dynamic evidence for ambiguous flows.
7
+ - Tightened OData operation-invocation normalization and dynamic wrapper-path evidence without promoting entity addressing paths to operation calls.
8
+ - Kept strict doctor output concise by default with actionable categories and detail-mode evidence expansion.
9
+
3
10
  ## 0.1.45
4
11
 
5
12
  - Added repeatable scoped implementation hints with explicit selection and mismatch evidence while preserving conservative automatic and legacy repository selection.
package/README.md CHANGED
@@ -481,39 +481,3 @@ MIT
481
481
  ---
482
482
 
483
483
  Made with ❤️ to make your work life easier!
484
-
485
-
486
- ### Service-only trace policy
487
-
488
- `service-flow trace --service <path>` is intentionally not a broad workspace traversal. Provide `--operation`, `--path`, or `--handler`; otherwise the command returns a typed `trace_start_not_found` diagnostic and no edges.
489
-
490
- ### Graph freshness and last-good snapshots
491
-
492
- Repository facts are parsed before publication and committed atomically. Failed indexing attempts record diagnostics and retain the last complete published snapshot and successful fingerprint. Successful fact publication marks the workspace graph stale until `service-flow link` rebuilds dependency, remote-call, and implementation edges for the current fact generation.
493
-
494
- ### Cross-package implementation evidence
495
-
496
- Handler registrations persist parsed class names and import sources. Linking resolves implementation edges only through registered application evidence plus model and handler package dependency edges; a decorator-name match alone is not enough.
497
-
498
- ### Graph variables
499
-
500
- The `graph` command accepts repeatable `--var key=value` options, matching `trace`, for runtime substitution previews in JSON or Mermaid output.
501
-
502
-
503
- ### 0.1.17 parser ownership policy
504
-
505
- Outbound call extraction is AST-based and ignores comments, block comments, and string literals. CAP/service `.on(...)` registrations are indexed only when the receiver has CAP/service evidence, and top-level registrations receive `module:<relative-file>#event:<event-name>:<line>` synthetic owners. Generic event emitters such as desktop or window events are ignored by default rather than guessed as CAP async edges. Unsupported source shapes are surfaced through diagnostics and strict doctor ownerless categories instead of guessed graph edges.
506
-
507
-
508
- ## 0.1.32 hardening notes
509
-
510
- Operation trace starts resolve indexed CDS operations before handler fallback. Ambiguous operation matches, ambiguous/rejected implementation edges, and resolved edges without an executable handler scope now fail closed with structured `resolutionStage` and `resolutionStatus` diagnostics and no speculative traversal. Handler fallback is only considered when no indexed CDS operation matches and the fallback is unique under the existing registration and ownership policy.
511
-
512
- Decorator signals are interpreted as resolved, absent, unsupported, or malformed. Only resolved string/template values, generated `Action<Name>.name`/`Func<Name>.name` constants, qualified generated constants ending in `.name`, and narrow safe `String(identifier)` wrappers can contradict method-name fallback. Unsupported expressions are preserved as evidence but are not treated as proof that a handler targets another operation.
513
-
514
- External HTTP facts persist queryable target kind, stable id, safe label, and dynamic flag on `outbound_calls`; graph evidence uses the same sanitized model. URL user info, fragments, query values, headers, cookies, credentials, tokens, API keys, and request/response bodies are not persisted. Legacy workspaces may relink from old evidence when possible, but full semantic enrichment requires re-index plus relink when old parser facts did not contain safe target metadata.
515
-
516
- Graph taxonomy now keeps CAP operation candidates separate from external HTTP targets. Dynamic, ambiguous, and unresolved CAP calls use operation-candidate semantics, external destinations/endpoints are produced only by `external_http` calls, and terminal local transport-client methods use transport-method semantics rather than external HTTP edges. `doctor --strict` reports external target quality and taxonomy consistency.
517
- ### Runtime and parser hardening
518
-
519
- `service-flow` treats URL and destination templates with substitutions as dynamic external targets, preserves operation-path template evidence without promoting unrelated variables, and parses CDS `@(path: ...)` annotations from original source text. Service extension declarations using both `extend Name` and `extend service Name` are recognized for routing provenance.
@@ -2316,6 +2316,171 @@ function substituteVariables(template, vars) {
2316
2316
  };
2317
2317
  }
2318
2318
 
2319
+ // src/trace/implementation-hints.ts
2320
+ function parseImplementationHint(value) {
2321
+ const hint = {};
2322
+ for (const part of value.split(",")) {
2323
+ const separator = part.indexOf("=");
2324
+ if (separator <= 0 || separator === part.length - 1) throw new Error(`Invalid implementation hint field: ${part}`);
2325
+ assignHintField(hint, part.slice(0, separator).trim(), part.slice(separator + 1).trim());
2326
+ }
2327
+ if (!hint.implementationRepo) throw new Error("Scoped implementation hint requires an implementation repo selection");
2328
+ return { ...hint, implementationRepo: hint.implementationRepo };
2329
+ }
2330
+ function selectImplementation(rawEvidence, hints, legacyRepo) {
2331
+ const evidence = asEvidence(rawEvidence);
2332
+ const scoped = hints ?? [];
2333
+ const matchingHints = scoped.filter((hint2) => hintMatchesEdge(hint2, evidence));
2334
+ if (matchingHints.length === 0) {
2335
+ if (legacyRepo) return selectCandidate(evidence, legacyHint(legacyRepo), "implementation_repo_hint");
2336
+ const reason = scoped.length > 0 ? "no_scoped_hint_matched_edge" : "no_implementation_hint_supplied";
2337
+ return { blocksAutomatic: false, evidence: { status: "not_matched", reason, strategy: "scoped_implementation_hint" } };
2338
+ }
2339
+ if (matchingHints.length > 1) {
2340
+ return {
2341
+ blocksAutomatic: true,
2342
+ evidence: {
2343
+ status: "tied",
2344
+ reason: "multiple_scoped_hints_matched_edge",
2345
+ strategy: "scoped_implementation_hint",
2346
+ matchedHints: matchingHints,
2347
+ candidateCount: matchingHints.length
2348
+ }
2349
+ };
2350
+ }
2351
+ const hint = matchingHints[0];
2352
+ return hint ? selectCandidate(evidence, hint, "scoped_implementation_hint") : { blocksAutomatic: false, evidence: { status: "not_matched" } };
2353
+ }
2354
+ function implementationHintDiagnostic(selection, suggestions) {
2355
+ if (!selection.blocksAutomatic || selection.methodId) return void 0;
2356
+ return {
2357
+ severity: "warning",
2358
+ code: "implementation_hint_mismatch",
2359
+ message: "Implementation hint did not select exactly one viable candidate",
2360
+ hintStatus: selection.evidence.status,
2361
+ candidateCount: selection.evidence.candidateCount,
2362
+ implementationHintSuggestions: Array.isArray(suggestions) && suggestions.length > 0 ? suggestions : void 0,
2363
+ implementationSelection: selection.evidence
2364
+ };
2365
+ }
2366
+ function implementationHintSuggestions(rawEvidence) {
2367
+ const evidence = asEvidence(rawEvidence);
2368
+ const accepted = (evidence.candidates ?? []).filter((candidate) => candidate.accepted);
2369
+ if (accepted.length < 2) return [];
2370
+ const repos = selectableRepositories(accepted);
2371
+ return accepted.flatMap((candidate) => {
2372
+ const repo = candidate.handlerPackage?.name;
2373
+ if (!repo || !repos.includes(repo)) return [];
2374
+ const hint = suggestionHint(evidence, candidate, repo);
2375
+ return [{
2376
+ servicePath: hint.servicePath,
2377
+ operationPath: hint.operationPath,
2378
+ ambiguityReason: evidence.ambiguityReasons?.[0],
2379
+ candidateFamily: hint.candidateFamily,
2380
+ selectableImplementationRepositories: repos,
2381
+ implementationRepo: repo,
2382
+ hint,
2383
+ cli: `--implementation-hint ${hintString(hint)}`
2384
+ }];
2385
+ });
2386
+ }
2387
+ function selectableRepositories(candidates) {
2388
+ const repos = new Set(candidates.flatMap((candidate) => candidate.handlerPackage?.name ? [candidate.handlerPackage.name] : []));
2389
+ return [...repos].filter((repo) => candidates.filter((candidate) => candidateMatchesRepo(candidate, repo)).length === 1).sort();
2390
+ }
2391
+ function assignHintField(hint, key, value) {
2392
+ if (key === "service" || key === "servicePath") hint.servicePath = value;
2393
+ else if (key === "operation" || key === "operationPath") hint.operationPath = value;
2394
+ else if (key === "package" || key === "packageName") hint.packageName = value;
2395
+ else if (key === "repository" || key === "repositoryName") hint.repositoryName = value;
2396
+ else if (key === "family" || key === "candidateFamily") hint.candidateFamily = value;
2397
+ else if (key === "repo" || key === "implementationRepo" || key === "select") hint.implementationRepo = value;
2398
+ else throw new Error(`Unknown implementation hint field: ${key}`);
2399
+ }
2400
+ function selectCandidate(evidence, hint, strategy) {
2401
+ const matches2 = (evidence.candidates ?? []).filter((candidate) => candidate.accepted && candidateMatchesRepo(candidate, hint.implementationRepo));
2402
+ const selected = matches2.length === 1 ? matches2[0] : void 0;
2403
+ if (!selected || selected.methodId === void 0) {
2404
+ return {
2405
+ blocksAutomatic: true,
2406
+ evidence: {
2407
+ status: matches2.length > 1 ? "tied" : "not_matched",
2408
+ reason: matches2.length > 1 ? "hint_matched_multiple_candidates" : "hint_matched_zero_candidates",
2409
+ strategy,
2410
+ matchedHint: hint,
2411
+ selectedRepo: hint.implementationRepo,
2412
+ candidateCount: matches2.length
2413
+ }
2414
+ };
2415
+ }
2416
+ return {
2417
+ methodId: String(selected.methodId),
2418
+ blocksAutomatic: false,
2419
+ evidence: {
2420
+ status: "selected",
2421
+ guided: true,
2422
+ strategy,
2423
+ matchedHint: hint,
2424
+ selectedRepo: hint.implementationRepo,
2425
+ selectedMethodId: selected.methodId,
2426
+ ambiguityReason: evidence.ambiguityReasons?.[0]
2427
+ }
2428
+ };
2429
+ }
2430
+ function suggestionHint(evidence, candidate, repo) {
2431
+ const servicePath = evidence.servicePath ?? candidate.servicePath;
2432
+ const operationPath = evidence.operationPath ?? candidate.operationPath;
2433
+ const family = usefulCandidateFamily(evidence, candidate);
2434
+ return {
2435
+ ...servicePath ? { servicePath } : {},
2436
+ ...operationPath ? { operationPath } : {},
2437
+ ...evidence.modelPackage?.packageName ? { packageName: evidence.modelPackage.packageName } : {},
2438
+ ...evidence.modelPackage?.name ? { repositoryName: evidence.modelPackage.name } : {},
2439
+ ...family ? { candidateFamily: family } : {},
2440
+ implementationRepo: repo
2441
+ };
2442
+ }
2443
+ function usefulCandidateFamily(evidence, candidate) {
2444
+ const family = candidate.handlerPackage?.packageName;
2445
+ if (!family) return void 0;
2446
+ if ((evidence.candidateFamilies ?? []).some((item) => item.packageName === family)) return family;
2447
+ const acceptedFamilies = new Set(
2448
+ (evidence.candidates ?? []).filter((item) => item.accepted).flatMap((item) => item.handlerPackage?.packageName ? [item.handlerPackage.packageName] : [])
2449
+ );
2450
+ return acceptedFamilies.size > 1 ? family : void 0;
2451
+ }
2452
+ function hintString(hint) {
2453
+ const fields = [
2454
+ ["service", hint.servicePath],
2455
+ ["operation", hint.operationPath],
2456
+ ["package", hint.packageName],
2457
+ ["repository", hint.repositoryName],
2458
+ ["family", hint.candidateFamily],
2459
+ ["repo", hint.implementationRepo]
2460
+ ];
2461
+ return fields.flatMap(([key, value]) => value ? [`${key}=${value}`] : []).join(",");
2462
+ }
2463
+ function hintMatchesEdge(hint, evidence) {
2464
+ const model = evidence.modelPackage ?? evidence.candidates?.[0]?.modelPackage;
2465
+ const familyNames = /* @__PURE__ */ new Set([
2466
+ ...(evidence.candidateFamilies ?? []).flatMap((family) => family.packageName ? [family.packageName] : []),
2467
+ ...(evidence.candidates ?? []).flatMap((candidate) => candidate.handlerPackage?.packageName ? [candidate.handlerPackage.packageName] : [])
2468
+ ]);
2469
+ return matches(hint.servicePath, evidence.servicePath ?? evidence.candidates?.[0]?.servicePath) && matches(hint.operationPath, evidence.operationPath ?? evidence.candidates?.[0]?.operationPath) && matches(hint.packageName, model?.packageName) && matches(hint.repositoryName, model?.name) && (!hint.candidateFamily || familyNames.has(hint.candidateFamily));
2470
+ }
2471
+ function candidateMatchesRepo(candidate, value) {
2472
+ return candidate.handlerPackage?.name === value || candidate.handlerPackage?.packageName === value || candidate.sourceFile?.startsWith(value) === true;
2473
+ }
2474
+ function matches(expected, actual) {
2475
+ return expected === void 0 || expected === actual;
2476
+ }
2477
+ function legacyHint(implementationRepo) {
2478
+ return { implementationRepo };
2479
+ }
2480
+ function asEvidence(value) {
2481
+ return value;
2482
+ }
2483
+
2319
2484
  // src/linker/remote-query-target.ts
2320
2485
  function buildRemoteQueryTarget(input) {
2321
2486
  const entity = typeof input.queryEntity === "string" && input.queryEntity.trim() ? input.queryEntity.trim() : void 0;
@@ -2760,13 +2925,14 @@ function linkImplementations(db, workspaceId, generation) {
2760
2925
  candidateFamilies: duplicateFamilies,
2761
2926
  candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
2762
2927
  };
2928
+ const evidenceWithHints = unique ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
2763
2929
  if (accepted.length === 0) {
2764
- db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", "unresolved", "operation", graphId(operation.operationId), "handler_method_candidates", candidates.map((row) => graphId(row.methodId)).join(","), 0, JSON.stringify(evidence), 0, "No implementation candidate passed policy", generation);
2930
+ db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", "unresolved", "operation", graphId(operation.operationId), "handler_method_candidates", candidates.map((row) => graphId(row.methodId)).join(","), 0, JSON.stringify(evidenceWithHints), 0, "No implementation candidate passed policy", generation);
2765
2931
  edgeCount += 1;
2766
2932
  unresolvedCount += 1;
2767
2933
  continue;
2768
2934
  }
2769
- db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidence), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
2935
+ db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidenceWithHints), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
2770
2936
  edgeCount += 1;
2771
2937
  if (unique) resolvedCount += 1;
2772
2938
  else ambiguousCount += 1;
@@ -3005,112 +3171,6 @@ function normalizedOperation(value) {
3005
3171
  return value.startsWith("/") ? value.slice(1) : value;
3006
3172
  }
3007
3173
 
3008
- // src/trace/implementation-hints.ts
3009
- function parseImplementationHint(value) {
3010
- const hint = {};
3011
- for (const part of value.split(",")) {
3012
- const separator = part.indexOf("=");
3013
- if (separator <= 0 || separator === part.length - 1) throw new Error(`Invalid implementation hint field: ${part}`);
3014
- assignHintField(hint, part.slice(0, separator).trim(), part.slice(separator + 1).trim());
3015
- }
3016
- if (!hint.implementationRepo) throw new Error("Scoped implementation hint requires an implementation repo selection");
3017
- return { ...hint, implementationRepo: hint.implementationRepo };
3018
- }
3019
- function selectImplementation(rawEvidence, hints, legacyRepo) {
3020
- const evidence = asEvidence(rawEvidence);
3021
- const scoped = hints ?? [];
3022
- const matchingHints = scoped.filter((hint2) => hintMatchesEdge(hint2, evidence));
3023
- if (matchingHints.length === 0) {
3024
- if (legacyRepo) return selectCandidate(evidence, legacyHint(legacyRepo), "implementation_repo_hint");
3025
- const reason = scoped.length > 0 ? "no_scoped_hint_matched_edge" : "no_implementation_hint_supplied";
3026
- return { blocksAutomatic: false, evidence: { status: "not_matched", reason, strategy: "scoped_implementation_hint" } };
3027
- }
3028
- if (matchingHints.length > 1) {
3029
- return {
3030
- blocksAutomatic: true,
3031
- evidence: {
3032
- status: "tied",
3033
- reason: "multiple_scoped_hints_matched_edge",
3034
- strategy: "scoped_implementation_hint",
3035
- matchedHints: matchingHints,
3036
- candidateCount: matchingHints.length
3037
- }
3038
- };
3039
- }
3040
- const hint = matchingHints[0];
3041
- return hint ? selectCandidate(evidence, hint, "scoped_implementation_hint") : { blocksAutomatic: false, evidence: { status: "not_matched" } };
3042
- }
3043
- function implementationHintDiagnostic(selection) {
3044
- if (!selection.blocksAutomatic || selection.methodId) return void 0;
3045
- return {
3046
- severity: "warning",
3047
- code: "implementation_hint_mismatch",
3048
- message: "Implementation hint did not select exactly one viable candidate",
3049
- hintStatus: selection.evidence.status,
3050
- candidateCount: selection.evidence.candidateCount,
3051
- implementationSelection: selection.evidence
3052
- };
3053
- }
3054
- function assignHintField(hint, key, value) {
3055
- if (key === "service" || key === "servicePath") hint.servicePath = value;
3056
- else if (key === "operation" || key === "operationPath") hint.operationPath = value;
3057
- else if (key === "package" || key === "packageName") hint.packageName = value;
3058
- else if (key === "repository" || key === "repositoryName") hint.repositoryName = value;
3059
- else if (key === "family" || key === "candidateFamily") hint.candidateFamily = value;
3060
- else if (key === "repo" || key === "implementationRepo" || key === "select") hint.implementationRepo = value;
3061
- else throw new Error(`Unknown implementation hint field: ${key}`);
3062
- }
3063
- function selectCandidate(evidence, hint, strategy) {
3064
- const matches2 = (evidence.candidates ?? []).filter((candidate) => candidate.accepted && candidateMatchesRepo(candidate, hint.implementationRepo));
3065
- const selected = matches2.length === 1 ? matches2[0] : void 0;
3066
- if (!selected || selected.methodId === void 0) {
3067
- return {
3068
- blocksAutomatic: true,
3069
- evidence: {
3070
- status: matches2.length > 1 ? "tied" : "not_matched",
3071
- reason: matches2.length > 1 ? "hint_matched_multiple_candidates" : "hint_matched_zero_candidates",
3072
- strategy,
3073
- matchedHint: hint,
3074
- selectedRepo: hint.implementationRepo,
3075
- candidateCount: matches2.length
3076
- }
3077
- };
3078
- }
3079
- return {
3080
- methodId: String(selected.methodId),
3081
- blocksAutomatic: false,
3082
- evidence: {
3083
- status: "selected",
3084
- guided: true,
3085
- strategy,
3086
- matchedHint: hint,
3087
- selectedRepo: hint.implementationRepo,
3088
- selectedMethodId: selected.methodId,
3089
- ambiguityReason: evidence.ambiguityReasons?.[0]
3090
- }
3091
- };
3092
- }
3093
- function hintMatchesEdge(hint, evidence) {
3094
- const model = evidence.modelPackage ?? evidence.candidates?.[0]?.modelPackage;
3095
- const familyNames = /* @__PURE__ */ new Set([
3096
- ...(evidence.candidateFamilies ?? []).flatMap((family) => family.packageName ? [family.packageName] : []),
3097
- ...(evidence.candidates ?? []).flatMap((candidate) => candidate.handlerPackage?.packageName ? [candidate.handlerPackage.packageName] : [])
3098
- ]);
3099
- return matches(hint.servicePath, evidence.servicePath ?? evidence.candidates?.[0]?.servicePath) && matches(hint.operationPath, evidence.operationPath ?? evidence.candidates?.[0]?.operationPath) && matches(hint.packageName, model?.packageName) && matches(hint.repositoryName, model?.name) && (!hint.candidateFamily || familyNames.has(hint.candidateFamily));
3100
- }
3101
- function candidateMatchesRepo(candidate, value) {
3102
- return candidate.handlerPackage?.name === value || candidate.handlerPackage?.packageName === value || candidate.sourceFile?.startsWith(value) === true;
3103
- }
3104
- function matches(expected, actual) {
3105
- return expected === void 0 || expected === actual;
3106
- }
3107
- function legacyHint(implementationRepo) {
3108
- return { implementationRepo };
3109
- }
3110
- function asEvidence(value) {
3111
- return value;
3112
- }
3113
-
3114
3174
  // src/trace/evidence.ts
3115
3175
  function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
3116
3176
  const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
@@ -3292,8 +3352,8 @@ function operationStartScope(db, repoId, start, hintOptions) {
3292
3352
  }
3293
3353
  if (impl.edge) {
3294
3354
  const evidence = parseEvidence(impl.edge.evidence_json);
3295
- const hintDiagnostic = implementationHintDiagnostic(hinted);
3296
- const diagnostics = [{ severity: "warning", code: impl.edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved", message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? "unresolved")}`, resolutionStage: "implementation", resolutionStatus: impl.edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation", implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, implementationAmbiguityReasons: evidence.ambiguityReasons, candidates: evidence.candidates }];
3355
+ const hintDiagnostic = implementationHintDiagnostic(hinted, evidence.implementationHintSuggestions);
3356
+ const diagnostics = [{ severity: "warning", code: impl.edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved", message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? "unresolved")}`, resolutionStage: "implementation", resolutionStatus: impl.edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation", implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, implementationAmbiguityReasons: evidence.ambiguityReasons, implementationHintSuggestions: evidence.implementationHintSuggestions, candidates: evidence.candidates }];
3297
3357
  return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
3298
3358
  }
3299
3359
  return { operationId, diagnostics: [{ severity: "warning", code: "trace_start_implementation_unresolved", message: "Indexed operation matched but no implementation candidate exists", resolutionStage: "implementation", resolutionStatus: "operation_without_implementation" }] };
@@ -3691,9 +3751,9 @@ function trace(db, start, options) {
3691
3751
  const contextMethodId = contextSelection.methodId;
3692
3752
  const contextNode = contextMethodId ? handlerMethodNode(db, contextMethodId) : void 0;
3693
3753
  if (implementation.edge) {
3694
- const hintDiagnostic = implementationHintDiagnostic(contextSelection);
3695
- if (hintDiagnostic) diagnostics.unshift(hintDiagnostic);
3696
3754
  const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
3755
+ const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence.implementationHintSuggestions);
3756
+ if (hintDiagnostic) diagnostics.unshift(hintDiagnostic);
3697
3757
  const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : contextNode;
3698
3758
  const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
3699
3759
  if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
@@ -3761,8 +3821,9 @@ export {
3761
3821
  applyVariables,
3762
3822
  extractPlaceholders,
3763
3823
  substituteVariables,
3764
- linkWorkspace,
3765
3824
  parseImplementationHint,
3825
+ implementationHintSuggestions,
3826
+ linkWorkspace,
3766
3827
  trace
3767
3828
  };
3768
- //# sourceMappingURL=chunk-BXSCE5CR.js.map
3829
+ //# sourceMappingURL=chunk-EGY2A4AT.js.map