@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 +7 -0
- package/README.md +0 -36
- package/dist/{chunk-BXSCE5CR.js → chunk-EGY2A4AT.js} +175 -114
- package/dist/chunk-EGY2A4AT.js.map +1 -0
- package/dist/cli.js +30 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +18 -1
- package/src/linker/cross-repo-linker.ts +4 -2
- package/src/output/table-output.ts +16 -1
- package/src/trace/implementation-hints.ts +73 -1
- package/src/trace/trace-engine.ts +4 -4
- package/dist/chunk-BXSCE5CR.js.map +0 -1
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(
|
|
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(
|
|
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-
|
|
3829
|
+
//# sourceMappingURL=chunk-EGY2A4AT.js.map
|