@saptools/service-flow
Traces one CAP operation end to end across several repositories without running anything — handlers, helper packages, local database access, remote OData calls, external HTTP and async channels.
Indexes independent git repositories, persists the CDS facts it finds in SQLite, then resolves calls that cross repository boundaries. The point is the multi-repo case: a call graph a single project's tooling cannot see because the other half is checked out somewhere else.
Facts
Where it sits
Who is behind it
In the atlas: maintains 18, contributes to 18@dongitranmaintainer
What it declares
Every artifact
- Type
- CLI
- Origin
- Independently maintained
- Runtime
- build time
As a CLI
- Distributed through
- npm
Connections
Seeing what running code did between input and result
Download
Install
npm add @saptools/service-flowDerived from the package name. No recipe recorded; the README has the configuration.
README
quoted from dongitran/saptools under MIT, read🧭 @saptools/service-flow
Trace SAP CAP service-to-service flows across multi-repository TypeScript workspaces.
Index independent Git repositories, persist CAP/CDS facts in SQLite, resolve cross-repo service calls, and explain one operation end-to-end through handlers, helper packages, local database access, remote OData calls, external HTTP calls, and async channels — without running the applications.
Install • Quick Start • CLI • FAQ
✨ Features
- 🧭 Cross-repository CAP tracing — starts from a repo, service, operation path, operation name, or handler and follows the indexed flow across workspace boundaries
- 🧩 Static CAP/CDS indexing — extracts services, actions, functions, events, handler classes, decorator metadata, handler registrations, executable symbols, local helper calls, and package-level
cds.requires - 🔗 Service-to-service linking — resolves
cds.connect.to(...),remote.send(...),cds.services.*style calls, helper package imports, dynamic candidates, and unresolved evidence into graph edges - 🗄️ SQLite-backed workspace cache — stores deterministic facts under
.service-flow/service-flow.dbso large workspaces can be queried repeatedly without reparsing everything - 🧠 Dynamic edge support — preserves parameterized destinations and service paths such as
svc_${objectCode}_process, then lets trace and graph commands apply runtime--var key=valuevalues or explicitly explore/infer bounded dynamic target candidates - 📊 Multiple output modes — renders human-readable tables, authoritative detailed JSON, Mermaid diagrams, and versioned compact JSON for AI-oriented topology analysis
- 🩺 Diagnostics-first workflow — records parse/index issues and exposes them through
service-flow doctorinstead of hiding partial analysis - 🧩 CAP helper-aware binding evidence — follows imported helpers exported directly or through named export lists and separates alias, destination, and service-path expressions for dynamic
cds.connect.to(alias, options)calls - 🧭 Nested workspace discovery — scans nested repositories even when the selected root is itself a valid Git repository, while ignoring empty
.gitplaceholders - 🔐 Secret-aware summaries — redacts sensitive keys in persisted summaries and CLI output while keeping useful source evidence
- 📦 Standalone CLI & typed package — ships as an npm CLI with TypeScript definitions for integration into other saptools workflows
📦 Install
# Global CLI
npm install -g @saptools/service-flow
# Or as a dependency
npm install @saptools/service-flow
# pnpm add @saptools/service-flow
# yarn add @saptools/service-flow
Note
Requires Node.js ≥ 24.0.0 for the bundled
node:sqliteruntime. The CLI uses a persistent SQLite driver (node:sqlitein supported Node builds) for bound parameters, transactions, WAL, busy timeouts, and read-only query commands. The analyzer is static: it reads files and package metadata, but it does not start CAP services, connect to SAP BTP, or execute application code.
Correctness notes
- Runtime
--varvalues are considered for dynamic remote targets and template event names. Placeholder keys are the full trimmed expression inside a balanced${...}region, so keys such asdomainInfo.serviceName,tenantInfo.region?.toLowerCase(), anditems?.[0].servicecan be supplied literally without JavaScript evaluation. Quote complex keys in the shell, for example--var 'tenantInfo.region?.toLowerCase()=lookup'. Local database, external HTTP, and already resolved static edges keep their persisted status, target, reason, and confidence. Partial substitutions remain dynamic and report the missing placeholder names. traceandgraphboth accept repeatable--var key=valueoptions. Effective substitutions are rendered in trace evidence without mutating the persisted graph. Confidence values are bounded to[0, 1].- Contextual binding attempts retain a labelled
contextualPreSubstitutionStatefor auditability. After compatible--varsubstitution, a dynamic edge,effectiveResolution, andlinker.reasonuse the same sorted current missing-key message. Structural contextual blockers remain separately visible ascontextualBlocker; they cannot be inferred away. - Dynamic target exploration is explicit. Default
--dynamic-mode strictkeeps every target with unresolved runtime variables fail-closed, but diagnostics can provide complete copyable--varsets.--dynamic-mode candidatesrenders only viable, capped, explicitly unselected branches while the route remains unresolved, never enters their handler bodies, and adds no exploratory branch once complete explicit values resolve the route.--dynamic-mode infertraverses only when the top viable, complete candidate scores at least0.85and exceeds the runner-up by more than0.05; exact ties, candidates exactly on the margin, conflicts, duplicate identities, incomplete leaders, and weaker scores stay unresolved. - Explicit variables are applied before candidate ranking. When a call has one selected binding, its original service-path, alias, and destination templates are matched against each concrete candidate before any repository-wide fallback; a package-level require is considered only when its alias matches that binding in the same caller repository. A conflict between a supplied value and a value derived from one of those validated signals rejects the candidate and yields
no_candidate_after_runtime_substitutionrather than a resolver-only target. - Identity fallback removes an npm scope, splits camel case, lowercases, folds non-alphanumeric separator runs to
_, and trims edge separators. It requires the complete normalized concrete route-owner package basename or repository name to match one placeholder with literal prefix and suffix, a resolved implementation for that effective operation, and workspace-wide unique identity/value evidence. A helper-owned implementation is valid evidence for an inherited operation, but helper and base-model names are never used as route identity. It never uses substrings, duplicate names, or discovery order. Generated commands shell-quote expression keys and values when required. --max-dynamic-candidatesbounds candidate branches, viable and rejected suggestions, variable sets, nested derivation/conflict evidence, and projected persisted candidate arrays in table and detailed JSON output. Link-time and doctor candidate-like arrays carry stable total/shown/omitted metadata; canonical SQLite facts, rather than a displayed prefix, remain the source for trace inference and implementation hints.omittedCandidateCountmeans omitted viable candidates; rejected candidates have separate total, shown, and omitted counts.- Repository selectors on list, trace, graph, and inspect commands narrow scope by workspace and exact repository ID. Unknown selectors return empty machine-readable diagnostics instead of falling back to another workspace or the whole database.
- Helper-package dependency edges prefer exact indexed package names. Duplicate package-name candidates are persisted as ambiguous evidence rather than silently selecting one repository.
- Handler registration parsing is AST-based for common
createCombinedHandler({ handler: ... })forms: direct arrays, arrays assembled with spreads, non-handlersarray names, aliased class imports, default-imported arrays, named exported arrays, and safe relative re-exports. Class-level rows keep registration file/line and import evidence. - Zero-argument CAP lifecycle method decorators in the supported
OnCreate/Read/Update/Delete,Before*, andAfter*families are indexed only with runtime (not type-only) handler/import evidence. Nonzero arguments, missing bodies, and unsupported shapes remain non-executable with raw resolution evidence. Lifecycle phase, event, canonical decorator, and source location remain distinct from CDS action/function implementation evidence. A known handler with no executable method reportshandler_methods_not_indexed; a mixed class reportshandler_decorators_not_indexedwhile its supported methods remain traceable. - Implementation edges require both operation compatibility and registration evidence. Decorator operation signals are stronger than method-name fallback; common generated names such as
FuncGetConfigurationandActionGetConfigurationare normalized before comparison, and a contradictory decorator rejects the candidate even when the TypeScript method name collides. Same-repository registrations do not need a self-dependency edge; cross-package matches use registration or handler-package dependencies on the model package. Duplicate strong candidates are stored as ambiguous implementation edges. - A resolved implementation edge exposes
selectedHandlerwith the selected method id, class, method, repository, file, line, and accepted status. This provenance is cross-checked with the graph target before table output uses it. Candidaterankremains the raw discovery order;displayRankis selection-aware, and bounded rendering preserves that established order. Ambiguous and unresolved implementation edges have no selected handler provenance. - Traces render persisted
OPERATION_IMPLEMENTED_BY_HANDLERhops after static or runtime remote operation resolution, including terminal handler nodes and ambiguous or unresolved implementation evidence when traversal cannot continue. - Repository fingerprints include source content, package name/version, dependencies and devDependencies, scripts, normalized
cds.requires(including nested credentials), package file content, and the analyzer version. Metadata-only changes therefore trigger reindexing. - Index publication is designed around the last-good snapshot: failed parse or persistence attempts are recorded as diagnostics and must not be mixed with older graph facts. After indexing changes, relink before relying on graph/trace output; doctor reports stale or inconsistent stores where detectable.
- Source discovery, file reads, hashing, parsing, and publication are all inside the repository-level protected indexing flow. A failed read keeps the previous fingerprint and facts, marks the repository failed, and records a
source_read_faileddiagnostic; a later successful index clears superseded read-failure diagnostics during fact publication. - Index preparation remains deterministic and sequential. Each source file and
package.jsonis read once into a repository-scoped immutable snapshot, and all TypeScript parser entrypoints share one lazy AST for that file. Publication uses one savepoint per repository; structured prepared-fact failures are contained at the individual fact, omitted with site-aware diagnostics, and do not discard valid sibling facts. Unexpected repository failures still roll back that repository, while the final package-invalidation/materialization pass remains workspace-atomic. - Index writers are coordinated per SQLite database with a short atomic claim. A second writer fails with
index_writer_active; a claim that remains SQLite-locked past the bounded wait reportsindex_writer_coordination_failed. A dead owner or sufficiently old legacy ownerless row is reconciled before a new claim.clean, including--db-only, uses the same claim and removes only exact SQLite sidecar files. Normal failures finalize their run row, while read-only list, trace, inspect, and doctor commands remain usable whenever the short publication transaction is not active. - Normal successful database commands on supported Node 24 runtimes suppress the known
node:sqliteexperimental warning so JSON stdout remains parseable and stderr-clean. Real service-flow errors still use stderr and non-zero exit codes. - Doctor treats a
runningindex run as abandoned only after 60 minutes and includes the run id/start time. Active short-lived concurrent runs are not default warnings. - Fresh databases include foreign keys for key graph, run, and diagnostic tables. Migrated legacy stores that still lack that metadata are reported by doctor with
legacy_schema_weaker_foreign_keys; rebuild into a fresh database if strict structural parity is required. - Parser warnings describe analysis completeness, while routing status describes graph behavior. A terminal DB edge can remain terminal while still exposing parser warning evidence about an unknown entity.
- Persisted graph rows take precedence during
traceandgraph: resolved call edges keep theirgraph_edges.id,outbound_calls.id, call-site file/line, outbound parser evidence, linker status/reason, and selected target evidence. Contextual runtime resolution can enrich evidence but does not replace an already resolved persisted target. Terminal start diagnostics such as ambiguous operations or rejected implementations return zero nodes and zero edges by default; candidates remain in structured diagnostics for automation. - Every AST-backed call owner is selected from executable symbols by complete zero-based, half-open UTF-16 span containment. Event subscriptions and their handler references prefer the exact same-span synthetic
event_registration; otherwise the narrowest containing scope and a fixed binary kind/name order apply. Physical line remains display evidence and is never an identity fallback. - Event-subscription handler facts have the durable role
event_subscribe_handler, retainfactOrigin: event_subscribe_handler_reference, and keep resolver-ownedcandidateStrategyseparate. Every supported subscription records one closedhandlerReferenceStatus; named/member/namespace and supported single-wrapper references require one same-span role row, while explicitly unsupported inline/wrapper/reference shapes and a missing argument require none. - Event receiver analysis structurally proves CAP-connect declarations and reaching assignments, including later assignment, destructuring, control-flow nesting, formatter-split chains, and one direct helper-return hop. A missing lexical binding remains a visible lower-confidence fact rather than being treated as proof of a non-CAP receiver. Mixed assignments remain unproven; proven non-CAP declarations plus observed socket, stream, and desktop receiver names are excluded. A static name reached through an unproven receiver remains visible as a dynamic candidate, but cannot create a terminal subscriber traversal until receiver proof exists.
- Stable string consts, enum members, and const-object properties can supply static event names from the same file, an exact relative module, or a uniquely proven package public entry. Computed access, mutable or non-string members, hidden/duplicate containers, incomplete public surfaces, and unsupported alias chains fail closed with constant-specific reasons.
- Event names supplied as template literals are persisted as opaque, non-evaluated expressions instead of being dropped. Strict traces keep them dynamic until every exact emit- and subscribe-side placeholder key is supplied with
--var; only equal, case-sensitive substituted names traverse the subscriber boundary. A canonical positional hole key may resolve different source-variable names on both sides. - Equal template skeletons can appear under
--dynamic-mode candidatesasEVENT_SHAPE_CANDIDATE_SUBSCRIBERedges only when their hole layout is identical and at least one literal span is eight characters or longer. These edges are always dynamic and non-authoritative. Strict output reports how many candidates it hid and points to the opt-in flag; an excessive link-time expansion is refused with an explicit diagnostic rather than silently truncated. - Repository environment declarations are opt-in allowlisted facts. The schema-14 analyzer reads configured
eventEnvironmentKeysfromnodemon.json,.env,manifest.yml, andmta.yaml; the shipped default is empty.index --event-environment-key <KEY>may be repeated to override the configured list for one invocation. Facts retain dev/deployment provenance and support onlytoUpperCase()/toLowerCase(). Shape candidates sharing an environment key pair only at equal deployment values; missing or conflicting values fail closed instead of producing a Cartesian product. Adjacent non-allowlisted keys are never persisted, and doctor reports configured keys with zero matches. - A subscription and its handler reference associate only by workspace, repository, normalized source file, and the complete non-null outer
.on(...)call span, with matching line/caller validation. Link never falls back to caller, line, start offset, label, or case-folded name heuristics. Resolved, ambiguous, unresolved, and explicitly unsupported associations remain distinguishable. - Service bindings carry their own declaration/assignment span and
owned_exactorownerless_file_scopestatus. Supported calls carry the exact visible binding site and a complete outer-to-inner lexical-scope proof capped at 16 scopes; persistence joins by repository, file, variable, and full site span. A deeper proof fails closed instead of being truncated. Shadowed, future, branch-dependent, hoisted, ambiguous, or otherwise unsupported flows remain unselected rather than using same-line/source-order proximity. - Package symbol calls preserve module origin, ESM/CommonJS binding shape, local and imported names, requested package, and requested public subpath. Resolution requires a unique package repository plus a complete public-entry/name exposure proof and an executable body. An explicit
exportsmap is authoritative; unsupported conditional/wildcard maps, unsupported or ambiguous entrypoints/barrels, unexposed internals, wrong subpaths, declaration-only targets, duplicate repositories/targets, incomplete retained evidence, mutable/reassigned/escaped public values, and mutated CommonJS namespace objects fail closed rather than selecting a stale body. Incomplete exact-scope evidence usespublic_surface_evidence_incomplete; this conservative compatibility cost is intentional for package shapes the static analyzer cannot prove. - One-hop derived import calls retain the import and target-declaration identities separately. Relative default-class instances resolve only when the exact requested module contains the matching declared class method and marks that class as the default export; package-derived members never fall through to a coincidental same-file name. A relative object shorthand that points onward to a package stays visibly unsupported, and every unresolved or ambiguous symbol-call fact is rendered as a non-traversable trace edge.
- Structural
cds.runrecognition is insensitive to source-line formatting. Query evidence recordshasForUpdate: truewhen the proven fluent CQL chain contains.forUpdate(...), without inspecting or retaining the lock argument. - A local deterministic helper may contribute a service binding through one direct return of
cds.connect.to(...)orcds.connect.messaging(...), including a try block whose catch only logs and swallows the error. The evidence strategy issingle_hop_helper_return; branching, a returning/connect-producing catch or finally block, and second-hop helpers remain fail-closed. - Repository public-surface evidence uses the versioned
service-flow/package-public-surface@1carrier and retains at most 256 public exposure records with truthful total/shown/omitted metadata. A displayed prefix never proves uniqueness or absence; an omitted requested scope stays unresolved unless an authoritative exact-name count proves the decision. - OData entity paths are conservative terminal remote entity edges. Reads, mutations, deletes, navigation paths, media-stream paths such as
/Documents(ID)/content, and uppercase unknown entity-set candidates do not inflate unresolved operation counts. Lowercase action/function-style paths remain eligible for indexed operation resolution. - External HTTP destinations are static only when a safe literal or local const literal proves the value. Identifier, property-read, function-call, and arbitrary destination expressions are dynamic with stable
destination:dynamic:<hash>ids and neutral labels; conditional literal branches expose only safe candidate names. - Remote
.send({ query })facts retain the historical fallback method for compatibility, but evidence now distinguishes an absentmethod(methodDefaulted) from a present dynamic method (dynamicMethodDefaulted) and records a structural CQLqueryRootsuch asSELECT.one.fromorINSERT.intowhen proven. - Schema version 14 adds canonical event-skeleton columns, a repository environment-declaration carrier, and generated string-constant facts/indexes. Schema 15 restores the historical schema-14 default and normalizes the repository carrier to the current empty allowlist without rewriting the applied migration. Its table rebuild verifies the required SQLite pragmas, direct child counts, and repository foreign-key targets. Package
0.1.80uses analyzer0.1.80-facts.1; read-only commands report bounded schema/reindex diagnostics and link preserves the last good graph until migration, force reindex, and force relink succeed.
Updates
Every command first checks npm for a newer @saptools/service-flow (at most once an hour, one small request
with a 2-second timeout) and, when one exists, installs that exact version with the package manager
that owns the running binary and re-runs the command you typed on the new version. Both steps are
announced on stderr; nothing is printed when the install is already current:
service-flow: updating 0.2.0 -> 0.3.0 ...
service-flow: updated to 0.3.0; re-running the command
If the install cannot complete, one stderr line gives the manual command and the command runs on the
installed version; that version is not retried for a day. service-flow self-update forces the check and
install now; service-flow self-update --check only reports.
| Control | Effect |
|---|---|
SAPTOOLS_AUTO_UPDATE=on|notify|off | on (default) installs and re-runs; notify prints the manual command once per version; off never checks. Applies to every @saptools CLI. |
SERVICE_FLOW_AUTO_UPDATE | same values, this CLI only; wins over the global variable |
SAPTOOLS_UPDATE_INTERVAL_MINUTES | minutes between checks (default 60; 0 checks on every run) |
SAPTOOLS_NPM_REGISTRY | registry to check and install from (default: npm's configured registry, then npmjs) |
SAPTOOLS_UPDATE_DEBUG=1 | explain on stderr why nothing happened |
The updater switches itself off in CI (CI set), under NODE_ENV=test or NO_UPDATE_NOTIFIER, when
the binary runs from a source checkout, an npm link or an npx cache, and inside the re-run itself.
It never writes to stdout, never asks for input, never uses sudo, and never moves onto a prerelease.
Its state lives in ~/.saptools/updates/.
🚀 Quick Start
# 1. Initialize a workspace that contains many CAP/helper Git repositories
service-flow init /path/to/workspace
# 2. Index source facts from every discovered repository
service-flow index --workspace /path/to/workspace
# 3. Resolve cross-repository edges after all repos have been indexed
service-flow link --workspace /path/to/workspace
# 4. Trace one operation as a readable table
service-flow trace --workspace /path/to/workspace --repo facade-service --operation doWork
# 5. Generate a Mermaid diagram for documentation
service-flow graph --workspace /path/to/workspace --service /FacadeService --path /doWork --format mermaid
# 6. Check parse/index diagnostics
service-flow doctor --workspace /path/to/workspace
After init, the workspace configuration and SQLite database live below the selected workspace by default. Run index whenever source changes; unchanged repositories are skipped unless --force is supplied. Then run link to rebuild the graph edges used by trace and graph.
Read-only commands (trace, graph, list, inspect, and doctor) may
temporarily target a specific database without rewriting shared workspace
configuration:
SERVICE_FLOW_DB=/absolute/path/to/service-flow.db \
service-flow graph --workspace /path/to/workspace --repo facade-service
SERVICE_FLOW_DB is an operational database selector, not an analyzed
workspace environment value. It never redirects index or link.
To allow non-secret event prefix declarations, edit
.service-flow/config.json and add the exact keys used by the workspace:
{
"eventEnvironmentKeys": ["REGION_CODE", "TENANT_CODE"]
}
Keys must match [A-Z_][A-Z0-9_]{0,63} and the list is capped at 16.
Only listed keys are read into facts; changing the list changes repository
fingerprints. Run index and link after editing it.
Repeat --event-environment-key on index to replace and persist the
configured list for this and later index invocations:
service-flow index --workspace /path/to/workspace \
--event-environment-key REGION_CODE \
--event-environment-key TENANT_CODE
Important
Package
0.1.80uses schema 15 and analyzer0.1.80-facts.1. Parser evidence changed, so existing workspaces require:service-flow index --workspace /path/to/workspace --force service-flow link --workspace /path/to/workspace --forceLink refuses stale or incompletely reindexed facts before deleting the previous graph. Package/CLI version, SQLite schema version, and analyzer compatibility version are independent contracts; a later output-only package patch can keep the same analyzer version without forcing another reindex.
🧰 CLI
🏁 service-flow init <workspace>
Discover nested Git repositories, create workspace state, save configuration, and record repository metadata.
service-flow init /path/to/workspace
service-flow init /path/to/workspace --db /custom/path/service-flow.db
service-flow init /path/to/workspace --ignore node_modules dist coverage .git
| Flag | Description |
|---|---|
--db <path> | Store the SQLite database at a custom path instead of <workspace>/.service-flow/service-flow.db |
--ignore <pattern...> | Override the default discovery ignore patterns |
🔎 service-flow index
Parse repository files and persist CAP facts. Use --repo for a focused refresh or --force when you want to re-index unchanged files.
service-flow index --workspace /path/to/workspace
service-flow index --workspace /path/to/workspace --repo facade-service
service-flow index --workspace /path/to/workspace --repo identity-service --force
| Flag | Description |
|---|---|
--workspace <path> | Workspace root or a path that can load the saved workspace configuration |
--repo <name> | Index only one repository by discovered repository name |
--force | Re-index even when file hashes indicate nothing changed |
🔗 service-flow link
Resolve indexed outbound calls after repositories have been indexed. This rebuilds the graph_edges table for the workspace. The summary separates remote operation calls resolved, local operation calls resolved, unresolved operation calls, ambiguous operation calls, dynamic operation calls, terminal call edges, and resolved/ambiguous/unresolved/missing event-subscription handler associations. Each subscription creates one EVENT_SUBSCRIPTION_HANDLED_BY edge; emit sites do not multiply that persisted cardinality.
service-flow link --workspace /path/to/workspace
service-flow link --workspace /path/to/workspace --force
| Flag | Description |
|---|---|
--workspace <path> | Workspace to link |
--force | Accepted for workflow symmetry; linking always rebuilds graph edges |
Pipeline-safe output
Normal command output is safe to pipe to a Unix consumer that intentionally stops reading early. A closed stdout pipe stops further output without an unhandled EPIPE stack trace; unrelated stdout failures still use the normal stderr diagnostic and non-zero exit outcome. Complete detailed JSON, compact JSON, table, and Mermaid output bytes follow the same stdout policy.
Trace and graph validate --format before opening the database. An unknown value writes a clear error to stderr, emits no stdout, and exits non-zero instead of silently falling back to a different format.
🧵 service-flow trace
Trace one starting point and render table, detailed JSON, compact JSON, or Mermaid output. Trace
walks linked graph_edges, so a resolved remote operation is followed into the
target handler up to --depth instead of showing only calls in the first file.
With --include-async, an emitted event is matched by its exact, case-sensitive raw name to every current-generation subscription in the same indexed workspace. Each registration produces a bridge to its resolved handler, and each distinct handler scope is expanded at most once for the same evaluation context. Duplicate registrations therefore remain visible without duplicating the handler body. A bridge is still rendered at the depth boundary, but its handler body is not expanded; structural self-cycles and mutual event cycles are rendered and stopped deterministically. Subscriber evaluation begins with an empty binding/payload context. This is a workspace-wide static name inference, not proof that a runtime broker, channel, tenant, ordering rule, payload, or deployed application will deliver an event. A subscribe call never reverse-triggers emitters, and includeAsync=false neither follows event bridges nor treats event-handler references as ordinary synchronous calls.
Symbol-scoped helper traversal
service-flow trace starts from the selected handler method symbol, renders outbound calls owned by that symbol, and follows conservative local helper-call facts. Handler helper properties such as helper = async () => { ... } and helper = function () { ... } are indexed as ClassName.helper; top-level CAP lifecycle, route, and event callbacks receive synthetic module:<file>#callback:<line> owners only when their body contains a supported outbound call or event subscription. Supported helper edges include same-file functions, this.method() calls, and exactly mapped relative imports/exports that resolve to an indexed executable symbol. Proxy-member calls keep factory/import evidence and avoid resolving by repository-wide member name alone when the target is ambiguous. Calls from unrelated functions in the same source file are not included merely because the file path matches.
Local CAP calls through cds.services.<Service>.<operation>(), bracket service lookups, and simple aliases are indexed as local operation calls. Linking first stays within the same repository and matches the target operation by exact qualified CDS service name, exact simple service name, exact service path, or an unambiguous service-path suffix. If no same-repository service exists, the linker can use implementation-context evidence to resolve model-package operations for helper packages: a resolved/ambiguous implementation candidate, registration package, or dependency/import edge must tie the caller repository to the model operation. Name-only global matches are preserved as unresolved candidate evidence rather than guessed links. Entity accessors such as cds.services.db.entities(...) are treated as entity metadata access, not operation calls.
Conservative local symbol traversal intentionally excludes decorators, built-ins such as JSON.parse, collection methods, third-party APIs, and arbitrary property chains unless the callee can plausibly resolve to an indexed local symbol. Named export lists such as export { loadTemplate as publicLoadTemplate } are indexed with the public exported name so relative imports can resolve. One-level object-literal helpers are indexed as symbols named like cacheHelper.getConfiguration; nested object literals are not yet expanded beyond the first helper level. parseGeneratedConstants remains a public low-level parser export for callers that need it, but generated constants are not persisted as graph facts in this patch; linking uses the deterministic decorator normalizer described above.
Detailed JSON output includes typed nodes for calls, operations, database entities,
external destinations, and unresolved/dynamic candidates when edges exist. Chained CAP DB queries inside cds.run(...) and direct supported builders are parsed with TypeScript AST evidence for SELECT, INSERT, UPSERT, UPDATE, and DELETE forms. A direct builder needs both a recognized CAP root and a proven execution context: direct await, return from an async or syntactically guaranteed-Promise callable, or a static element of awaited Promise.all([...]). Plain query factories and unrelated methods named from, where, or set are not promoted to database facts. When the query target is genuinely dynamic, graph status remains terminal and JSON retains parserWarning evidence, while table and Mermaid render the target as Entity: unknown rather than a numeric call id.
service-flow trace --workspace /path/to/workspace --repo facade-service --operation doWork
service-flow trace --workspace /path/to/workspace --service /FacadeService --path /doWork --format json
service-flow trace --workspace /path/to/workspace --service /FacadeService --path /doWork --include-async --format compact-json
service-flow trace --workspace /path/to/workspace --handler EntryHandler --depth 1 --format json
service-flow trace --workspace /path/to/workspace --service /FacadeService --path /doWork --depth 2
service-flow trace --workspace /path/to/workspace --repo facade-service --operation doWork --var objectCode=xx --var objectType=Thing
service-flow trace --workspace /path/to/workspace --repo facade-service --operation doWork --dynamic-mode candidates --max-dynamic-candidates 20
service-flow trace --workspace /path/to/workspace --service /FacadeService --path /doWork --implementation-hint service=/TargetService,operation=/runTask,repo=target-helper
| Flag | Description |
|---|---|
--workspace <path> | Workspace to read |
--repo <name> | Start from a repository |
--operation <name> | Start from an operation/action/function name |
--service <path> | Start from a CAP service path such as /FacadeService |
--path <operationPath> | Start from an operation path such as /doWork |
--handler <name> | Start from a handler class or handler-like selector |
--depth <n> | Maximum executable/service scope depth; defaults to 25. Implementation hops are rendered at the current scope depth, while downstream handler bodies consume the next depth. The step field never exceeds the requested depth. |
--format <format> | Exactly table, json, mermaid, or compact-json; defaults to table |
--include-external | Include external HTTP/destination edges in traversal output |
--include-db | Include local DB query edges in traversal output |
--include-async | Include async publish/subscribe edges in traversal output |
--implementation-repo <name> | Select one implementation repository for every ambiguous hop; retained for backward compatibility |
--implementation-hint <scope> | Select one implementation for a matching hop; repeatable fields are service, operation, package, repository, family, and required repo |
--var <key=value> | Apply runtime values to dynamic destinations/service paths; repeatable |
--dynamic-mode <mode> | strict, candidates, or infer; defaults to strict |
--max-dynamic-candidates <n> | Cap viable/rejected suggestions, exploratory branches, variable sets, and nested candidate evidence; defaults to 5 |
🗺️ service-flow graph
Render a deeper architecture graph from the same selector model used by trace. Graph output includes DB, async, and external edges by default and uses depth 100.
service-flow graph --workspace /path/to/workspace --service /FacadeService --path /doWork
service-flow graph --workspace /path/to/workspace --repo facade-service --operation doWork --format json
service-flow graph --workspace /path/to/workspace --repo facade-service --operation doWork --format compact-json
| Flag | Description |
|---|---|
--workspace <path> | Workspace to read |
--repo <name> | Filter/start by repository |
--operation <name> | Filter/start by operation name |
--service <path> | Filter/start by service path |
--path <operationPath> | Filter/start by operation path |
--format <format> | Exactly mermaid, json, or compact-json; defaults to mermaid |
--implementation-repo <name> | Select one implementation repository for every ambiguous hop |
--implementation-hint <scope> | Apply a repeatable scoped implementation selection |
--var <key=value> | Apply repeatable runtime substitutions |
--dynamic-mode <mode> | strict, candidates, or infer; defaults to strict |
--max-dynamic-candidates <n> | Cap viable/rejected suggestions, exploratory branches, variable sets, and nested candidate evidence; defaults to 5 |
📚 service-flow list ...
Inspect indexed facts as JSON.
service-flow list repos --workspace /path/to/workspace
service-flow list services --workspace /path/to/workspace --repo facade-service
service-flow list operations --workspace /path/to/workspace --repo facade-service --service /FacadeService
service-flow list calls --workspace /path/to/workspace --repo facade-service --operation doWork
# `--operation` filters outgoing call paths/payloads; use trace/graph `--operation` for handler-origin traversal.
| Command | Description |
|---|---|
list repos | Print discovered repositories with kind and package name |
list services | Print indexed CDS services, optionally filtered by repo |
list operations | Print indexed actions/functions/events, optionally filtered by repo and service |
list calls | Print indexed outbound calls, optionally filtered by repo and operation/path |
Operation selector resolution
Operation-based trace starts first resolve indexed CDS operations, then follow the persisted OPERATION_IMPLEMENTED_BY_HANDLER graph edge to the exact handler method symbol. Generated decorator constants such as ActionPublishRecord.name and FuncLookupRecord.name, local string constants, string enum members, and const-object string properties are normalized with structured resolution evidence. Unsupported decorator expressions remain visible and do not contradict a matching method name. If the same operation name exists in multiple services or repositories, service-flow returns trace_start_ambiguous with copyable --service <path> --path <operation-path> selectors; add --repo when repository scope is also ambiguous. Unique operation selectors emit the initial operation node and implementation hop exactly once before traversing handler-owned calls.
Ambiguous operation starts include repository, service path, operation path, source file, and source line candidates. When service paths are unique, JSON diagnostics also include copyable --service <path> suggestions. A scoped implementation hint changes only the matching implementation hop; guided traversal uses the selected handler repository even when the operation model belongs to another repository.
External HTTP targets
External HTTP facts use semantic terminal nodes instead of outbound-call row ids. Literal destinations render as External destination: ANALYTICS_API; static absolute or relative URLs render as redacted External endpoint labels; dynamic URL expressions render as External endpoint: dynamic URL; unavailable target evidence renders as External endpoint: unknown. URL user information, query-string values, credentials, tokens, cookies, headers, and payload bodies are not stored in labels. Run service-flow link after schema migration so legacy numeric targets are rebuilt from the current parser evidence. service-flow doctor --strict reports strict_external_http_target_quality with semantic, dynamic, unknown, numeric, and malformed-evidence counts.
Troubleshooting resolution accuracy
- If a remote edge is unresolved, run
service-flow list calls --operation <name>andservice-flow inspect operation <name>to compare the captured call path with indexed CDS operations. Operation-path-only matches are shown as ambiguous/unresolved with candidate counts instead of high-confidence cross-repo links. - Service bindings are matched to outbound calls only by the parser-carried
repository, source file, variable name, and complete binding-site
[start,end)identity, then validated against the complete lexical-scope proof. Line, nearest declaration, shared owner, and source-order proximity are never selection fallbacks. If a helper-returned client is not linked, export the helper from a relative import target and ensure it returnscds.connect.to(...)directly or returns an object property backed by a local connected-client variable. Supported helper shapes include function declarations, arrow-function variables, function-expression variables, named export lists, and aliased exports such asexport { connectCatalog as createCatalogClient }. Shorthand returns likereturn { client }and explicit returns likereturn { serviceClient: client }are followed only when the returned value is a concrete client. Trace evidence includes the caller variable, returned property, imported helper, source file, exported symbol, placeholders, and transaction alias steps. - Direct
SELECT.one.from(Entity),SELECT.from(Entity),INSERT.into(Entity),UPSERT.into(Entity),UPDATE(Entity), andDELETE.from(Entity)statements are indexed when directly awaited, returned by anasyncor guaranteed-Promise callable, or supplied as static elements to awaitedPromise.all([...]); equivalentcds.run(...)queries retain their wrapper classification. Plain non-promise query factories stay out of local database facts. doctorreports silent quality problems such as services without operations, handler repositories without CDS service facts, and an empty search index.
🔬 service-flow inspect ...
Inspect raw indexed records for a repository or operation selector.
service-flow inspect repo facade-service --workspace /path/to/workspace
service-flow inspect operation doWork --workspace /path/to/workspace
service-flow inspect operation /doWork --workspace /path/to/workspace
| Command | Description |
|---|---|
inspect repo <name> | Print one repository database record or { "error": "repo not found" } |
inspect operation <selector> | Print operations whose name or path equals the selector |
🩺 service-flow doctor
Print stored diagnostics. Default output suppresses high-noise entity-only service checks; --strict includes them. Without --format, doctor keeps the legacy-compatible behavior: JSON when diagnostics exist and No diagnostics recorded for a clean workspace. Deterministic JSON mode prints [] when clean.
service-flow doctor --workspace /path/to/workspace
service-flow doctor --workspace /path/to/workspace --strict
service-flow doctor --workspace /path/to/workspace --strict --detail
service-flow doctor --workspace /path/to/workspace --strict --format json
service-flow doctor --workspace /path/to/workspace --strict --format table
--format json always returns a JSON array, including [] for clean workspaces. --format table prints a concise human-readable table with capped copyable hint lines; use JSON when automation needs structured fields, then use a scoped selector or implementation hint for a precise follow-up. Omit --format when relying on the pre-0.1.48 compatible output contract.
Strict output keeps stable category, count, severity, and capped example fields. --detail adds useful fields but keeps candidate-like and example arrays capped; use the provided scoped selectors or implementation hints for a precise follow-up.
Strict binding diagnostics group remote sends into direct_binding_missing, contextual_binding_recoverable, ambiguous_binding_candidates, unrecoverable_binding, and missing_symbol_parameter_metadata. Candidate chains remain as bounded JSON projections with count metadata, while table output stays capped and copyable.
🧹 service-flow clean
Remove generated service-flow state.
service-flow clean --workspace /path/to/workspace --db-only
service-flow clean --workspace /path/to/workspace
| Flag | Description |
|---|---|
--db-only | Remove only the configured SQLite database |
| (default) | Remove the marker-owned .service-flow state directory; custom/unowned or dangerous parent directories are refused |
🧱 What Gets Indexed
service-flow favors explainable static facts with source-file evidence and confidence scores.
| Area | Examples |
|---|---|
| Repository metadata | nested Git repos, package name/version, dependency graph, repository kind |
| CAP model facts | .cds services, service paths, actions, functions, events, parameters, return types |
| Handler facts | cds-routing-handlers decorators, handler classes/methods, server registrations |
| Service bindings | cds.connect.to("alias"), aliases from package.json#cds.requires, destination/service path expressions |
| Outbound calls | remote.send({ method, path }), remote.send({ query }), cds.services.Service.operation(), service wrapper calls |
| Local data access | directly executed CAP query builders, cds.run(SELECT...), and local entity query evidence |
| Async channels | Event Mesh-style emit, publish, and on facts |
| External calls | Cloud SDK-style HTTP/destination calls and external edge evidence |
| Generated constants | low-level parseGeneratedConstants parser output for integrations; not persisted as first-class graph facts in this patch |
🧠 Dynamic Edges
Runtime-dependent destinations and paths are preserved as parameterized evidence instead of being discarded.
destination: svc_${objectCode}_process
servicePath: /${objectType}ProcessService
operationPath: /getPaths
Pass runtime values during trace:
service-flow trace --workspace /path/to/workspace --repo facade-service --operation doWork --var objectCode=xx --var objectType=Thing
When a concrete target exists after variable substitution, the trace shows both the parameterized evidence and the resolved match. When it does not, service-flow keeps the edge as a dynamic candidate or unresolved edge so the missing link remains visible.
When runtime values are missing, strict trace output includes the missing variable names plus bounded candidate guidance when indexed operations and routing metadata can derive values. Detailed JSON includes dynamicTargetExploration, dynamicTargetCandidates, dynamicTargetCandidateSuggestions, suggestedVarSets, and dynamicTargetInference; compact JSON retains only allowlisted counts and missing variable names, while table output shows compact candidate counts and copyable --var examples. Use --dynamic-mode candidates to render capped exploratory branches, or --dynamic-mode infer to continue only through a unique fully-derived candidate.
Direct sends and same-file or imported wrappers share one path-candidate analysis. Literals and immutable aliases resolve; conditional or branch-assigned static alternatives remain ambiguous with bounded raw and normalized candidate projections plus counts; dynamic reassignments remain dynamic with the exact runtime identifier. Wrapper definition sends are treated as templates when a concrete caller-site edge can be indexed.
Service binding evidence distinguishes a directly persisted binding from caller-to-callee contextual recovery. A call with a selected binding uses that exact binding as its routing context, including helper-return provenance, rather than combining unrelated client definitions from the repository. When no binding is selected, fallback references remain marked as fallback; multiple distinct fallback bindings are not combined into one inferred route. Persisted binding selection never uses a declaration after the call and does not choose among different mutable client assignments. Contextual trace evidence carries caller and callee sites, argument/property and parameter/local names, original binding location, routing expressions, candidate ties, and selection status.
OData evidence preserves the raw path, query-free path, normalized invocation path, invocation arguments, placeholder keys, classifier reason, indexed operation candidate count, and entity-versus-operation precedence decision. Entity keys, navigation paths, media/property paths, and query reads remain entity access unless indexed operation evidence and strong service context prove an operation.
Service-binding evidence keeps these fields distinct: service alias, alias expression, destination expression, service-path expression, operation-path expression, and runtime placeholders. Helpers that return concrete connected clients inside object properties are followed through destructuring, simple identity aliases, transitive same-file aliases, and transaction aliases while preserving helper-chain evidence. This is important for common CAP helpers such as cds.connect.to(remote_{code}`, { credentials: { destination: `remote_{code}, path: /${entityType}ProcessService } }), where the alias is not the service path.
By default, production traces should be built from production source files. Keep generated credentials and local state out of git, and use explicit fixture/test workspaces when validating test-only mocked service clients so they do not pollute production graph interpretation.
📁 Workspace State
By default, state is stored below the selected workspace:
/path/to/workspace/.service-flow/service-flow.db # SQLite fact and graph database
/path/to/workspace/.service-flow/config.json # saved workspace configuration
Use a custom database path when the workspace is read-only or when you want to keep generated state elsewhere:
service-flow init /path/to/workspace --db /custom/path/service-flow.db
Important
Generated state is derived from source code and may reveal internal repository names, service names, endpoints, entity names, and call paths. Do not commit
.service-flow/or attach the database to public tickets.
🔐 Security & Redaction
- The analyzer reads static source files and package metadata only.
- It does not execute CAP services, apply
.envvalues to the analyzer process, call SAP BTP, or connect to remote systems. It reads only configured event-environment keys from supported static declaration files as bounded facts. - Persisted summaries and CLI output redact keys that look like credentials, including
authorization,cookie,token,secret,password,key, andcredential. - Payload bodies are summarized for traceability; runtime payload values are not required for indexing.
- Compact JSON is projected through a field-by-field allowlist. It omits raw parser/outbound evidence, candidate and score bodies, payloads, call arguments, helper-chain bodies, supplied variable values, and arbitrary diagnostic/remediation text. Query metadata contains supplied variable names only;
runtimeValuesOmittedis alwaystrue, so exact replay requires the original supplied values retained by the caller.
📤 Output Examples
Table
Start: facade-service /FacadeService doWork
Step Type From To Evidence
1 local_db_query facade-service:srv/functions/Entry Entity: Template srv/functions/EntryHandler.ts:8
2 remote_action facade-service:srv/functions/Entry /IdentityService/resolveAccess srv/functions/EntryHandler.ts:10
Detailed JSON
--format json remains the complete, pretty-printed authoritative audit artifact for raw evidence, candidate inspection, locations, and effective versus persisted decisions. Version service-flow/detailed-trace@3 makes every edge from and to a canonical id present in nodes; fromLabel and toLabel are resolved endpoint captions. A label-only lookup that matches multiple nodes keeps the caption clean and reports fromLabelAmbiguousMatches or toLabelAmbiguousMatches separately. This intentionally large contract supersedes the pre-0.1.75 label-valued endpoint shape and the v2 raw-label semantics.
{
"schema": "service-flow/detailed-trace@3",
"start": {
"repo": "facade-service",
"servicePath": "/FacadeService",
"operation": "doWork"
},
"nodes": [],
"edges": [],
"diagnostics": []
}
Compact JSON
--format compact-json projects the same traversal into the minified, newline-terminated service-flow/compact-graph@1 contract. It is a lossy AI-oriented semantic topology and bounded decision summary; use the detailed JSON companion whenever exact evidence is required.
{"schema":"service-flow/compact-graph@1","start":{"repo":"facade-service","servicePath":"/FacadeService","operation":"doWork","operationPath":null,"handler":null},"query":{"depth":25,"includeAsync":true,"includeDb":true,"includeExternal":true,"dynamicMode":"strict","maxDynamicCandidates":5,"suppliedVariableNames":[],"runtimeValuesOmitted":true,"implementationRepo":null,"implementationHints":[]},"source":{"schemaVersion":15,"analyzerVersion":"0.1.80-facts.1","graphGeneration":7},"summary":{"completeness":"complete","fullTraceNodes":2,"fullTraceEdges":1,"fullTraceDiagnostics":0,"nodes":2,"edges":1,"collapsedEdges":0,"statusCounts":{"resolved":0,"terminal":1,"inferred":0,"dynamic":0,"ambiguous":0,"unresolved":0,"cycle":0},"projection":{"evidence":"summary-only","syntheticEndpoints":0,"omittedUnreferencedFullNodes":0}},"repos":["facade-service"],"files":["srv/EntryHandler.ts"],"nodeColumns":["id","kind","label","repo","file","line"],"nodes":[["n0","symbol","EntryHandler.doWork",0,0,8],["n1","database_entity","Template",null,null,null]],"edgeColumns":["id","traceOrdinals","step","type","from","to","status","confidence","count","details"],"edges":[["e0",[0],1,"local_db_query","n0","n1","terminal",0.95,1,null]],"diagnosticColumns":["fullDiagnosticIndex","severity","code","message","file","line","details"],"diagnostics":[]}
The nodeColumns, edgeColumns, and diagnosticColumns arrays define fixed-width tuples; absent cells are explicit null. Any breaking change to those columns or to the declared v1 top-level/query/source/summary/status/aggregation/diagnostic semantics requires a new @N schema. Repository and file dictionaries are sorted, and dense n0.../e0... IDs are assigned after canonical sorting. Those dense IDs are output-local and are not stable database identifiers.
Each compact edge's traceOrdinals contains the zero-based detailed edge index or indexes from the exact corresponding trace invocation. Aggregated equivalent observations retain their multiplicity in count and their complete sorted ordinal list. These ordinals are valid only for the same database generation, selector, traversal options, implementation hints, and runtime inputs. Bounded details.refs can also carry graph, call, operation, symbol, or handler IDs for exact drill-down in the declared source.graphGeneration; those database references are generation-scoped rather than long-lived IDs.
The edge tuple's status and to cells are the canonical effective decision. Optional details.decision.effectiveResolutionStatus and effectiveTarget are omitted only when they were derived from those exact same canonical values; label equality is insufficient. Differing persisted status/target decisions, counts, reason codes, and references remain. Each reference group retains at most 5 values with total, shown, and omitted; missing-variable projection retains at most 8 safe names, each at most 160 UTF-16 code units.
An ambiguous implementation decision with more than one candidate may include tiedCandidateRepos only when more than one uniquely attributable repository exists. Compact start diagnostics may likewise include bounded selectorSuggestions, a short selectorKind, or bounded invalidFactCategories. Each of these optional reference groups uses the same 5-value cap and truthful total/shown/omitted counts. They contain only allowlisted codes or safe identifiers; detailed JSON remains authoritative for full candidate and lifecycle evidence. Implementation-hint remediation lists the accepted service, operation, package, repository, family, and required repo keys.
Detailed/runtime artifacts retain the arbitrary full trimmed placeholder key. Compact output accepts an ASCII identifier ([A-Za-z_$][A-Za-z0-9_$]*), followed by zero or more direct/optional ASCII identifier members or non-negative decimal numeric element accesses, and optionally one final zero-argument toLowerCase, toUpperCase, or trim transform. Raw control characters are rejected before surrounding-space normalization, so a leading or trailing control cannot be trimmed into a displayable name. Unsafe or overlong keys remain counted in missingVariableCount and omittedMissingVariableCount and are available through the correlated detailed edge/diagnostic; compact never executes or partially evaluates them.
source.analyzerVersion is the one persisted analyzer value for the selected scope. It is none when no repositories exist, mixed when multiple analyzer versions are present, and legacy_unknown when the persisted value is absent. Reindex before relying on topology whenever the source reports a sentinel rather than the current analyzer.
For identical database state and inputs, canonical dictionaries, semantic endpoints, aggregation, and ordering make compact output byte-deterministic. Database rebuilds can legitimately change graph generation, trace ordinals, and generation-scoped references, so byte identity is not promised across arbitrary rebuilds. Representative large traces are regression-tested against both pretty and minified detailed JSON size budgets; compact output does not achieve this by weakening detailed JSON.
Write the compact graph and its detailed audit companion through ordinary stdout redirection:
service-flow graph \
--workspace /path/to/workspace \
--repo facade-service \
--service /FacadeService \
--operation doWork \
--format compact-json > order-flow.graph.json
service-flow graph \
--workspace /path/to/workspace \
--repo facade-service \
--service /FacadeService \
--operation doWork \
--format json > order-flow.trace.json
Mermaid
⚠️ Limitations
- Static analysis cannot know every runtime branch, feature flag, or environment-specific destination.
- Exact event-name matching is a workspace-scoped static inference. It does not establish broker, destination, channel, tenant, payload compatibility, ordering, deployment, or runtime delivery.
- Dynamic service names and paths may need
--var key=valuevalues to resolve concrete targets. - OData path punctuation is structural only outside balanced opaque placeholders and quoted values. A top-level query after a placeholder remains a query, while
?,/, or parentheses inside its expression do not classify the path. A placeholder overlapping the first classification head remains runtime-dependent until exact substitution and indexed evidence prove the target. - Highly customized frameworks can still appear as unresolved edges until parser support is added.
- Parse failures are stored as diagnostics and reported by
service-flow doctor. - The resolver prefers source evidence and confidence scores over speculative matches.
❓ FAQ
Does service-flow run my CAP application?
No. It is a static analyzer. It reads source files, .cds models, package.json, and TypeScript AST information, then stores derived facts in SQLite.
When should I run index and link again?
Run service-flow index after source, CDS, package metadata, or helper-package code changes. Run service-flow link after indexing so cross-repository edges are rebuilt from the latest facts.
Why is an expected call unresolved?
Check service-flow doctor, then inspect the facts with service-flow list services, service-flow list operations, and service-flow list calls. Dynamic destinations may need --var key=value; the key is the full trimmed expression inside ${...} and is matched literally without JavaScript evaluation. Operation-path-only ambiguous remote actions usually mean the call had no service binding id; same-file identity aliases are propagated, while property/call-expression aliases and ambiguous wrapper flows remain conservative; inspect list calls, inspect operation, and doctor --strict to determine whether helper-return propagation or wrapper support is missing. Contextual implementation selection only continues into a handler when static evidence such as caller repository, resolved service path, destination/alias expression, dependency edges, registration package, and local service ownership makes exactly one candidate stronger; ties remain ambiguous with reasons.
Default doctor output is intended to focus on actionable indexing or trace-impacting issues; use --strict when you need exhaustive model-shape diagnostics for entity-only or extension-heavy CDS models. Strict mode also reports normalized OData invocation ambiguity, remote-action target quality, likely missed identity aliases, no-binding remote actions, contextual implementation stops, and wrapper dynamic-path candidates, including whether unresolved unknown/dynamic paths are semantic instead of numeric call ids. GET OData entity/query reads such as /Books?$filter=contains(title,'A'), /Books(ID='1000'), and navigation queries are terminal remote query/entity edges unless strong indexed CDS operation evidence resolves them; placeholders inside query strings are preserved as query evidence rather than dynamic operation selectors.
Is the SQLite database safe to commit?
No. It should not contain runtime secrets by design, but it can expose internal topology, service names, paths, repository names, and source evidence. Keep .service-flow/ out of git.
🛠️ Development
From the monorepo root:
pnpm install
pnpm --filter @saptools/service-flow build
pnpm --filter @saptools/service-flow typecheck
pnpm --filter @saptools/service-flow lint
pnpm --filter @saptools/service-flow test:unit
pnpm --filter @saptools/service-flow test:e2e
The e2e tests use fixture CAP workspaces and fake-backed flows. They do not need live SAP BTP credentials.
📄 License
MIT
Made with ❤️ to make your work life easier!
History
Other 1
Notes
- Self-updating. Every command now checks npm for a newer
@saptools/service-flow(at most once an hour) and, when one exists, installs that exact version and re-runs the command on it, announcing both steps on stderr.SAPTOOLS_AUTO_UPDATE=on|notify|offcontrols it (see the README's Updates section); it is off by itself in CI, in tests, from a source checkout and inside the re-run. Newservice-flow self-update [--check]forces the check and install now.--versionnow readspackage.jsonat runtime. Both come from the private, build-time-bundled@saptools/corepackage; the published tarball gains no runtime dependency.
Other 4
Notes
- Publishes each canonical implementation decision into the per-database cache during implementation linking, including operations with no candidates. Later ownership checks reuse the exact decision instead of deriving it again; the existing phase-start reset keeps same-process relinks fresh.
- Remote
.send({ query })facts now record the structural CQLqueryRootwhen available. An absentmethodproperty is disclosed separately asmethodDefaulted: true; a present but unresolved method retainsdynamicMethodDefaulted: true. - Table traces surface
proof=<strategy>(<receiver>)only fordispatchCertainty=receiver_unproven, making the existing JSON receiver evidence visible without changing other certainty tiers. - Package/CLI is
0.1.80; SQLite remains schema15, compact remainsservice-flow/compact-graph@1, and detailed JSON remainsservice-flow/detailed-trace@3. Analyzer compatibility advances to0.1.80-facts.1; existing workspaces requireindex --forcefollowed bylink --force.
Other 7
Notes
- Added covering graph-edge lookup indexes and three repository/name symbol indexes with the measured prefix order. Existing schema-15 databases receive them on their next read-write open without a schema migration.
- Runs full SQLite
ANALYZEafter a successful link transaction so the new indexes and every implementation-candidate join table have planner statistics. Runlink --forceonce after upgrade to install the indexes and statistics; no fact re-index is required. - Memoizes canonical implementation evidence per database and operation during call linking, with an explicit reset before every implementation-link phase so same-process relinks cannot reuse stale decisions.
- Reserved the
scope:caption namespace for structural scope tuples and restored flat-array rendering for invalid four-element tuple candidates. - Renamed the doctor branch counter to
deploymentComparisonAssessmentsand addeddeploymentComparisonEdgeCount, making one-edge/many-deployment expansion explicit. - Read-only commands accept
SERVICE_FLOW_DBas an operational database-path override. Index and link continue to mutate only the database named by the workspace configuration. - Package/CLI is
0.1.79; SQLite remains schema15, analyzer compatibility remains0.1.77-facts.1, compact remainsservice-flow/compact-graph@1, and detailed JSON remainsservice-flow/detailed-trace@3.
Other 6
Notes
- Restored the always-array
inspect operationJSON shape while retaining a non-zero missing-selector exit, and made trace/graph return exit 1 in every format only when the requested start itself is refused. Runtime-variable, hidden-candidate, and omission diagnostics remain successful trace results. - Versioned detailed JSON as
service-flow/detailed-trace@3.fromLabel/toLabelare resolved endpoint captions; ambiguous label-only fallback is represented separately byfromLabelAmbiguousMatchesortoLabelAmbiguousMatchesinstead of embedding diagnostic prose in a data field. Canonicalfrom/tonode ids remain unchanged. - Preserved the injective structural-scope grammar with executable round-trip/collision checks, restored readable flat-array identifiers after the scope attempt, and added non-zero fixtures for typed Node-parameter exclusion, development-environment comparisons, Mermaid metacharacters, and strict unproven subscriptions.
- Extended branch reachability with deployment-comparison reason populations and removed the unused incremental-index and depth-filter modules.
- Corrected after release: the stored
package_import_provenance_missingreduction from four site rows to two repository rows occurred in 0.1.76, when diagnostics became one bounded row per affected repository. Pristine 0.1.76 and later builds already store two rows for the measured workspace. - Package/CLI is
0.1.78; SQLite remains schema15, analyzer compatibility remains0.1.77-facts.1, compact remainsservice-flow/compact-graph@1, and detailed JSON isservice-flow/detailed-trace@3. This output-only patch does not require re-indexing or relinking a workspace already on analyzer0.1.77-facts.1.
- Restored the always-array
Other 6
Notes
- Restored terminal dispatch for statically named publications whose receiver
propagation remains unproven, while retaining the receiver reason and
dispatchCertainty=receiver_unproven. Unproven subscriptions remain dynamic candidates, making the precision-for-recall boundary explicit and asymmetric. - Applied one receiver-level compatibility decision to every listener on an
unproven
streamidentifier. Affirmative CAP evidence still wins, so a CAP-connected receiver namedstreamremains an event client. - Replaced the inert flat-tuple formatter with an injective structural-scope grammar, normalised scope labels before endpoint closure, deduplicated synthetic nodes by scope, and shared one id-first endpoint caption resolver across table, Mermaid, and detailed JSON. Mermaid now escapes labels independently.
- Replaced spelling-derived sibling-operation refusal with exact resolved decorator values from sibling methods, added the supporting handler-method index, and memoized lexical binding indexes by TypeScript source-file identity.
- Strengthened schema-15 migration validation with direct foreign-key target checks, fixed multi-scope dynamic-candidate totals, made doctor deduplication executable, exposed branch and publication-certainty populations, and made unknown operation inspection a non-zero diagnostic.
- Package/CLI is
0.1.77, SQLite remains schema15, analyzer compatibility is0.1.77-facts.1, compact remainsservice-flow/compact-graph@1, and detailed JSON remainsservice-flow/detailed-trace@2. Parser facts changed, so existing workspaces requireindex --forcefollowed bylink --force.
- Restored terminal dispatch for statically named publications whose receiver
propagation remains unproven, while retaining the receiver reason and
Other 7
Notes
- Guarded the schema-15 repository-table rebuild with explicit
foreign_keys=OFFandlegacy_alter_table=ONassertions and direct child-row count preservation. The migration now refuses before mutation when either prerequisite is absent. - Made the sibling-operation fallback guard reachable for dotted generated
decorator expressions through exact persisted decorator identity, without
substring matching. Spelling-only decisions are labelled
expression_spelling_fallback, and candidate evidence now includes method names. - Proved typed Node stream/event parameters before generic parameter flow,
populated
thisroots, and made unproven event receivers dynamic candidates rather than terminal dispatch edges. Proven CAP receiver facts and single-hop helper returns retain their existing behavior. - Made environment comparison evidence say whether values were compared, mismatched non-authoritatively, or could not be compared and why. Shape candidate visibility and cap counts now describe the rows actually rendered.
- Resolved Mermaid endpoints by canonical id before labels, disclosed ambiguous label fallbacks, preserved full doctor locations, rendered tuple-shaped internal ids readably, and deduplicated repeated diagnostics with an explicit multiplicity.
- Versioned detailed JSON as
service-flow/detailed-trace@2;fromandtoare canonical node ids whilefromLabelandtoLabelretain display text. Compact remainsservice-flow/compact-graph@1and now computes omitted detailed nodes from the current canonical endpoint scheme. - Package/CLI is
0.1.76, SQLite remains schema15, and analyzer compatibility is0.1.76-facts.1. Event receiver facts changed, so existing workspaces requireindex --forcefollowed bylink --force.
- Guarded the schema-15 repository-table rebuild with explicit
Other 7
Notes
- Restored exported enum-member identity through dotted TypeScript namespaces. Generated decorator constants now use their declared string value across files, while private nested namespaces remain refused.
- Added an explicit implementation
selectionBasisto detailed, table, Mermaid, and compact output. Method-name fallback refuses a handler whose unresolved decorator names a sibling operation, and rejected alternatives are disclosed instead of rendering as an unexplained bare resolution. - Preserved canonical node identity throughout trace closure, added explicit detailed-JSON endpoint references, repository-qualified synthesized targets, and full-width table identifiers. Every rendered detailed JSON edge now references nodes present in the same document.
- Collapsed duplicate environment-derived shape candidates into bounded deployment groups, removed the false same-repository scope assertion, and made dynamic display caps report effective, shown, and omitted counts.
- Excluded imported Node stream/event receivers structurally and followed chained pipe receivers to their root. Unknown receiver bindings remain visible and proven CAP helper-return receivers remain unchanged.
- Persisted explicit event-environment keys, removed non-actionable placeholder
--varsuggestions, rejected unbounded trace invocations, and made missing operation/detail-mode CLI requests diagnostic. - Package/CLI is
0.1.75, SQLite schema is15, analyzer compatibility is0.1.75-facts.1, and compact remainsservice-flow/compact-graph@1. Schema 15 restores the historical schema-14 default and normalizes repository defaults without editing the applied migration. Parser facts changed, so existing workspaces requireindex --forcefollowed bylink --force.
Other 6
Notes
- Recovered CAP event publications reached through a later assignment from a single-hop helper-return binding. Missing receiver bindings remain visible as explicitly unproven facts, while proven socket, stream, pipe, desktop, realtime, and CAP CRUD receivers remain excluded.
- Contained every structured prepared-snapshot validation failure at the individual fact boundary during normal indexing. Package factory-return and package-instance calls persist as unresolved provenance gaps; other unrepresentable facts are omitted with one site-aware diagnostic each, without discarding valid sibling facts or their repository.
- Reused repository-scoped TypeScript analysis for member-write, stable-value, import-binding, and string-constant lookups so identifier-dense frontend sources terminate without changing parser facts. Decorator operation names now come from exact relative or
#cds-modelsexported nested constants rather than source-text naming guesses. - Removed unregistered third-party package calls and decorator applications from trace edges while retaining bounded omission diagnostics. Every rendered edge target now has a canonical node, shape-candidate nodes are repository-qualified, and strict output discloses hidden event-shape candidates with a copyable mode hint.
- Scoped environment-derived shape pairings to equal values of a shared deployment key, collapsed missing consumer declarations into one bounded refusal, and refuses excessive expansion with a diagnostic instead of truncating it. The environment-key default is empty,
index --event-environment-keyis repeatable, and doctor reports configured and unmatched keys. - Package/CLI is
0.1.74, SQLite remains schema14because all new state uses existing evidence and diagnostic carriers, analyzer compatibility is0.1.74-facts.1, and compact remainsservice-flow/compact-graph@1. Parser facts changed, so existing workspaces requireindex --forcefollowed bylink --force.
Other 5
Notes
- Repaired the schema-14 writer/validator contract: resolved empty string constants remain legal facts, empty event topics fail closed at use, and const-alias templates retain complete signed skeletons. Lifecycle failures now use the actual workspace path and expose bounded repository/file/line/predicate examples without replacing the last good graph.
- Separated event-name resolution from receiver proof. Static names retain canonical event identity while receiver confidence remains disclosed; single proven CAP-connect assignments inside branches are accepted, affirmative non-CAP proof wins over name compatibility, and comments or string contents cannot seed receiver evidence. Known socket, stream, desktop, and CAP CRUD handler calls stay outside EventMesh aggregates.
- Refused partial folding for spread/computed/getter/shorthand constant containers, accepted type-position references as immutable uses, materialized complete loop registrations, bounded link-time skeleton fan-out, deduplicated equal dispatch decisions, and completed bounded doctor reason-bucket counts.
- Made event environment keys configurable through
eventEnvironmentKeysin.service-flow/config.json, while preserving the default, grammar/cap, provenance, transform, ambiguity, fingerprint, and neighbouring-key exclusion guarantees. Repository inspection omits stored declaration values and environment-file fingerprints hash only allowlisted derived facts. - Package/CLI is
0.1.73, SQLite remains schema14, analyzer compatibility is0.1.73-facts.1, and compact remainsservice-flow/compact-graph@1. Parser facts changed, so existing workspaces requireindex --forcefollowed bylink --force.
Other 1
Notes
- Published the completed event-surface provenance, constant-topic, candidate-subscriber, environment-declaration, and doctor-quality work as a package-only patch. SQLite remains schema
14, analyzer compatibility remains0.1.71-facts.1, and compact remainsservice-flow/compact-graph@1; no additional reindex is required after applying the schema-14 fact release.
- Published the completed event-surface provenance, constant-topic, candidate-subscriber, environment-declaration, and doctor-quality work as a package-only patch. SQLite remains schema
Other 6
Notes
- Replaced event-receiver name gating with structural CAP-connect and lexical reaching-assignment proof for declarations, later assignments, destructuring, and formatter-split chains. Property and non-CAP receivers now persist bounded fail-closed facts instead of disappearing; compatibility names remain explicit
name_fallbackevidence. - Folded proven local, relative-module, and public package enum/const-object event topics without evaluating expressions. Mutable, non-string, unexposed, duplicate, computed, or incomplete containers retain typed unresolved reasons, and package constant facts are invalidated and re-resolved when their target surface changes.
- Added schema 14 carriers for canonical event skeletons, allowlisted repository environment declarations, and generated string-constant provenance. Shape-equivalent subscriptions are opt-in dynamic candidates only when the identical hole layout has a literal span of at least eight characters; one canonical positional
--varkey can resolve both source-name variants. - Added per-repository
SHARD_CODEdeclarations from bounded dev/deployment metadata, with explicit provenance, onlytoUpperCase/toLowerCasetransforms, duplicate-value ambiguity, dependency-aware consumer expansion, fingerprint invalidation, and exclusion of every non-allowlisted neighbouring environment key. - Added strict doctor aggregates for unresolved names, dynamic candidates, unmatched publications/subscriptions, receiver proof outcomes, skeleton candidates, and environment ambiguities. Loop registrations retain enumerable multiplicity evidence, payload-routed sites remain non-authoritative candidates, and table/compact evidence exposes workspace-name-only delivery certainty.
- Package/CLI was
0.1.71, SQLite is schema14, analyzer compatibility is0.1.71-facts.1, and compact remainsservice-flow/compact-graph@1. The writer-only migration leaves legacy event skeleton/environment provenance null and marks old facts stale; existing workspaces requireindex --forcefollowed bylink --force.
- Replaced event-receiver name gating with structural CAP-connect and lexical reaching-assignment proof for declarations, later assignments, destructuring, and formatter-split chains. Property and non-CAP receivers now persist bounded fail-closed facts instead of disappearing; compatibility names remain explicit
Other 1
Notes
- Removed numeric ordering prefixes from every tracked source and test filename and updated all package-local imports. This is a source-layout-only release: SQLite remains schema
13, analyzer compatibility remains0.1.69-facts.1, and the compact schema remainsservice-flow/compact-graph@1.
- Removed numeric ordering prefixes from every tracked source and test filename and updated all package-local imports. This is a source-layout-only release: SQLite remains schema
Other 7
Notes
- Corrected one-hop derived import identity: conventional relative default-class instances use target-side default-export evidence, package-derived members cannot leak into same-file exact-name resolution, and package-backed proxy aliases fail closed with an honest unsupported reason. Unresolved and ambiguous local/package symbol-call facts now remain visible as non-traversable trace edges.
- Contained invalid prepared-repository snapshots with typed, site-aware diagnostics and repository savepoints. Healthy repositories in the same index invocation publish normally; partial and total failures return bounded summaries and non-zero CLI status, while the final cross-repository invalidation/materialization phase remains atomic.
- Persisted template-literal
.emit()/.on()topics as runtime-dependent event facts. Missing exact keys produce dynamic candidates and compact-safe missing-variable diagnostics; supplying every emit/subscription key traverses only an exact case-sensitive substituted event-name match. - Recognized multi-line
cds.run(...)structurally and recorded boundedhasForUpdate: trueevidence on both wrapped and directly executed fluent CQL chains. - Added the fail-closed
single_hop_helper_returnbinding strategy for a direct connect-derived helper return inside a try block whose catch has no value return or connect. Branching, value-returning/connect-producing catch/finally blocks, and second-hop helpers remain unresolved. - Kept
service-flow/compact-graph@1while rejecting raw control characters before trimming, completing tuple-equivalent status/target elision for non-database-backed target kinds, exposing bounded tied implementation repositories and selector/lifecycle categories, and sourcing safe reason codes from parser warnings. Tuple columns, refs, redaction, and detailed JSON remain unchanged. - Package/CLI is
0.1.69, SQLite remains schema13, analyzer compatibility is0.1.69-facts.1, and the compact schema remainsservice-flow/compact-graph@1. Existing workspaces requireindex --forcefollowed bylink --force; no schema migration is required.
Other 7
Notes
- Replaced line-based call and binding ownership with deterministic UTF-16 full-span containment, exact subscription-registration identity, durable handler-reference status, and parser-carried lexical binding-site provenance. Unsupported or ambiguous ownership and reaching assignments now fail closed instead of selecting a convenient same-line row.
- Upgraded SQLite to schema 13 with binding-site span/owner columns, an exact-site uniqueness backstop, and a repository package-public-surface carrier. Legacy rows keep null provenance and
legacy_unknown; upgrading requiresindex --forcefollowed bylink --force, while invalid prepared lexical proofs and failed lifecycle preflight preserve the last good facts and graph. - Preserved typed ESM/CommonJS binding shape, imported versus local names, requested package/subpath, exact relative-module scope, public exposure completeness, and executable-body eligibility. Package resolution no longer guesses from repository-wide exported-name uniqueness; aliases and namespace members resolve only through a proven public entry, proxy target module ambiguity fails closed, and package identity changes stale dependency-only helper edges as well as matching symbol calls. Mutable, reassigned, or escaped public values and mutated CommonJS namespace objects fail closed instead of selecting a stale executable body.
- Strengthened current-fact lifecycle validation for link-consumed JSON, event origin/status/cardinality, exact owners, binding references, package provenance/state, and resolution matrices. Trace, doctor, and link return bounded
reindex_requireddiagnostics before JSON-dependent work or graph replacement; doctor reports valid pre-link package rows as pending/relink work instead of terminal unresolved quality. - Made
${...}scanning balanced and non-evaluating, and made OData query, segment, quote, and invocation analysis treat accepted placeholders as opaque tokens. Optional chaining, calls, strings, regular expressions, and slashes inside a placeholder no longer become path syntax; exact complex--varkeys can recover dynamic GET operation resolution. - Kept
service-flow/compact-graph@1while restoring complex missing-variable names and authoritative shown/omitted counts under the documented safe grammar. Compact edges omit only effective status/target summaries proven identical to their canonical tuple cells; differing persisted decisions and every bounded drill-down reference remain. This recovers the representative compact budgets of at most 15% of pretty and 20% of minified detailed JSON without weakening the detailed artifact or deleting references. - Re-exported the public fact and trace type contracts from the package root. Package/CLI
0.1.68, schema13, analyzer0.1.68-facts.1, and compact schemaservice-flow/compact-graph@1remain independent compatibility values.
Other 1
Notes
- Published the completed event-subscriber and compact-graph release as an output/package-only patch. SQLite remains schema 12 and
ANALYZER_VERSIONremains0.1.66-facts.1, proving package version changes no longer force an unnecessary fact reindex.
- Published the completed event-subscriber and compact-graph release as an output/package-only patch. SQLite remains schema 12 and
Other 9
Notes
- Made event-subscription handler identity durable with explicit
event_subscribe_handlersymbol-call roles, resolver-independentfactOriginprovenance, and exact zero-based UTF-16 outer-call spans on outbound and symbol-call facts. Ordinary calls useordinary_call; migrated facts remain fail-closed aslegacy_unknowninstead of receiving heuristic roles or spans. - Upgraded SQLite to schema 12 with indexed call-site/role columns, stale-analyzer lifecycle checks, bounded read-only upgrade diagnostics, and a link preflight that refuses legacy or incomplete facts before replacing the existing graph. Upgrading from 0.1.65 requires
index --forcefollowed bylink --force. - Persisted one bounded
EVENT_SUBSCRIPTION_HANDLED_BYedge per subscription after final symbol-call resolution, using the exact workspace/repository/file/full-span association and stable resolved, ambiguous, unresolved, or missing-association targets. Link never falls back to caller, line, start offset, labels, or case-folded event names. - Continued async traces from an emitted event through every exact, case-sensitive workspace-name subscriber registration into each distinct resolved generic handler symbol and its downstream scope. Preserved registration fan-out and duplicate bridges, isolated subscriber context, enforced causal depth, convergence, and structural cycle safeguards, and kept ambiguous/unresolved/missing associations non-traversable. This is explicitly
static_name_onlyinference, not proof of runtime broker/channel/tenant/payload delivery. - Prevented event-handler-role and legacy symbol calls from leaking through ordinary synchronous local-symbol traversal when async traversal is disabled.
- Added versioned
compact-jsonoutput to trace and graph.service-flow/compact-graph@1uses canonical semantic endpoints, sorted dictionaries, fixed-width tuples, dense output-local IDs, bounded allowlisted decision/diagnostic summaries, deterministic aggregation, and invocation-scopedtraceOrdinalsplus generation-scoped audit references. - Kept existing pretty detailed JSON as the authoritative complete evidence/candidate artifact, along with existing table and Mermaid output. Compact JSON is intentionally lossy and omits raw evidence, candidate bodies and scores, payload/call expressions, helper chains, supplied runtime values, and arbitrary diagnostic/remediation text; it is deterministic only for identical database state and inputs.
- Made invalid trace/graph formats fail before database access with non-zero status, no stdout, and an explicit accepted-format list instead of silently falling back.
- Decoupled package/CLI version
0.1.66, SQLite schema version12, and analyzer compatibility0.1.66-facts.1, allowing future output-only package patches to retain the analyzer without forcing a reindex.
- Made event-subscription handler identity durable with explicit
Other 3
Notes
- Captured event-subscription handler references from
.on('Event', handlerRef)as resolvable symbol-call facts using the existing import-binding-aware machinery for relative imports, package imports, namespace members, same-file symbols, and static accessors. - Covered bare identifiers,
Class.memberreferences, and one level of single-argument wrapper-call unwrapping; inline callbacks, multi-argument wrappers, nested wrappers, and other unsupported expressions remain unrepresented and fail closed. - Kept the SQLite schema, event-registration and outbound-call facts, linkers, trace engine, renderers, and output shapes unchanged.
- Captured event-subscription handler references from
Other 2
Notes
- Stopped recording CAP runtime lifecycle activity on the
cdsfacade (bootstrap,loaded,connect,serving,served,listening, andshutdown) and supported-receiveron('error')hooks as asynchronous domain-event facts or derived event edges, while preserving custom facade events, lifecycle-named service events, domain messaging events, error emissions, and synthetic event-registration symbols. - Made object-form
send({ method, ... })calls with a dynamic method default toPOSTwith boundeddynamicMethodDefaultedparser evidence instead of storing a property name or expression as the HTTP verb, without a schema, linker, trace-engine, or output-shape change.
- Stopped recording CAP runtime lifecycle activity on the
Other 2
Notes
- Assigned every distinct Mermaid trace/graph node its own stable per-render identifier instead of truncating normalized endpoint strings to 60 characters, preventing nodes with long shared prefixes from collapsing into one.
- Preserved full node labels, edge order and types, table/JSON output, trace behavior, and the SQLite schema.
Other 2
Notes
- Recorded public static arrow-function and function-expression properties on exported classes as exported qualified symbols, matching static method declarations and enabling unique cross-package and relative
Class.memberresolution withlocal_symbol_calltrace descent. - Kept instance properties, private/protected static properties, and members of non-exported classes fail-closed as unexported, with no schema, linker-edge, trace, CLI, or output-shape change.
- Recorded public static arrow-function and function-expression properties on exported classes as exported qualified symbols, matching static method declarations and enabling unique cross-package and relative
Other 2
Notes
- Fixed RC-A by resolving remote
send({ query: ... })entities from the original query initializer AST and its real lexical scope, removing the isolated text reparse that fabricated parameter, mutable-local, runtime-const, runtime-destructured, and shadowed entity names while preserving genuine static entities and top-level query aliases. - Fixed RC-B by assigning the existing query warning to query-present remote calls whose entity stays dynamic, matching local query diagnostics without changing the
remote_querycall type, confidence, schema, linker, trace, or output shape and completing the remote identifier-reparsing follow-up documented in 0.1.59.
- Fixed RC-A by resolving remote
Other 3
Notes
- Fixed RC-A by making local DB query entities lexical-binding-aware: parameters, simple mutable locals, runtime const aliases, destructuring from runtime or reassigned sources, and direct
this.<field>targets now remain dynamic at the existing low-confidence warning path instead of becoming fabricated entities. - Preserved concrete imported/global and string entities, bounded immutable aliases, literal element access,
<x>.entities.Name, and P1/P2/P3 entity destructuring from CAP entity collections or#cds-models; aliased destructuring records the public source property. Directthis.<field>entity caches deliberately pay the fail-closed compatibility cost, including in isolated remote-query hints, while remote identifier reparsing remains a known follow-up. - Fixed RC-B by recognizing
SELECT.distinct.fromandSELECT.distinct.one.fromas CAP query-builder roots for both direct andcds.run(...)execution, retaining one fact per fluent statement without schema, linker, trace, or output-shape changes.
- Fixed RC-A by making local DB query entities lexical-binding-aware: parameters, simple mutable locals, runtime const aliases, destructuring from runtime or reassigned sources, and direct
Other 3
Notes
- Completed RC5 with a deterministic link-phase workspace pass that resolves package-import symbol calls to uniquely matching exported symbols in uniquely identified indexed sibling packages, including package subpath imports and receiver-qualified member calls.
- Kept external packages, same-repository imports, missing or non-exported names, unsafe aliases/default imports, deeper member chains, duplicate package mappings, and duplicate exported targets fail-closed as unresolved or ambiguous.
- Recomputed owned package-import rows on every link without a schema, edge, trace-engine, or CLI output change, preserving bounded evidence and cross-package
local_symbol_calltrace descent across repeated link and force-index/relink runs.
Other 3
Notes
- Made local executable symbol-call resolution import-binding-aware: relative helper shadowing now bypasses same-file methods (RC1), namespace members resolve within their imported module (RC2), duplicate exports use fail-closed path disambiguation while barrels remain ambiguous (RC3), and singleton-accessor chains reach non-exported instance methods in the imported class module (RC4).
- Recorded non-relative package-import calls with
package_importevidence instead of silently dropping them (RC5 minimal-safe); they remain deterministically unresolved until a post-publication workspace pass can resolve sibling-package symbols without index-order or stale-ID risk. - Persisted bounded module-path and candidate-strategy evidence without a schema migration or trace/rendering shape change, with neutral parser, SQLite, trace-edge, and repeated force-index/relink coverage.
Other 2
Notes
- Completed direct CAP query-builder execution-context indexing for builders returned by async or syntactically guaranteed-Promise callables and for static builder elements in awaited
Promise.all(...), while retaining one fact per logical statement and the existing conservative unknown-entity behavior. - Recorded stable direct execution-context evidence (
await,async_return,promise_return, orpromise_aggregate) without changing the SQLite schema, graph edge type, trace filters, or wrapper ownership.
- Completed direct CAP query-builder execution-context indexing for builders returned by async or syntactically guaranteed-Promise callables and for static builder elements in awaited
Other 2
Notes
- Indexed directly awaited CAP query-builder statements as local database calls, preserving exact builder-root evidence, fluent statement offsets, dynamic-entity warnings, method ownership, and the existing single
cds.run(...)fact. - Made normal CLI stdout pipeline-safe: an early downstream pipe close stops further output without an unhandled
EPIPEstack trace, while unrelated stream failures remain non-zero diagnostics.
- Indexed directly awaited CAP query-builder statements as local database calls, preserving exact builder-root evidence, fluent statement offsets, dynamic-entity warnings, method ownership, and the existing single
Other 3
Notes
- Made contextual dynamic trace diagnostics runtime-current after
--varsubstitution while retaining labelled pre-substitution binding state and distinct structural blockers. - Added explicit selected-handler provenance to resolved implementation hops, so table, JSON, and Mermaid trace output follow the graph target rather than a rejected candidate.
- Preserved semantic candidate order through bounded evidence projections, retaining raw discovery rank alongside selection-aware display rank and existing count metadata.
- Made contextual dynamic trace diagnostics runtime-current after
Other 3
Notes
- Validated dynamic routes from the outbound call's selected service binding, including helper-return provenance, exact template matches, and explicit alias/destination conflicts that now fail closed with structured no-match evidence.
- Kept deterministic identity inference attached to the concrete service route owner while allowing a separately validated helper implementation for inherited operations.
- Bounded persisted call, implementation, parser-alternative, trace, selector, and strict-doctor candidate projections with stable count and omission metadata while retaining canonical SQLite facts for decisions and scoped hints.
Other 4
Notes
- Narrowed dynamic targets before ranking, scoped strong evidence by exact repository IDs, required workspace-wide identity uniqueness, refused conflicting/incomplete/margin-bound inference, shell-quoted suggestions, and bounded all candidate projections.
- Indexed supported zero-argument CAP lifecycle decorators with runtime import provenance, kept lifecycle/event methods out of operation implementation matching, and added fail-closed repository, operation, handler, and no-executable-method diagnostics.
- Coordinated index and clean writers with recoverable owner metadata and actionable lock failures while preserving atomic last-good publication, including backward-safe index creation during migration.
- Reused immutable package/source text and TypeScript AST snapshots during sequential preparation, and added deterministic table/JSON, workspace-scope, integrity, concurrency, migration, and parse-failure coverage.
Other 4
Notes
- Added trace and graph
--dynamic-mode strict|candidates|inferwith capped--max-dynamic-candidatesexploration for runtime-dependent remote targets. - Ranked dynamic operation candidates with derived
--varsuggestions from service-path, alias, destination, package require, and implementation evidence while keeping strict mode fail-closed. - Added infer-mode traversal only when all required placeholders are uniquely derived above threshold; tied candidates remain unresolved with explicit evidence.
- Normalized dynamic candidate score reasons to arrays and added neutral regression coverage for explicit vars, strict suggestions, candidate branches, and infer ties.
- Added trace and graph
Other 4
Notes
- Resolved local string, enum-member, and const-object decorator arguments with persisted evidence while keeping unsupported expressions conservative.
- Prevented handler registrations with exact class ids from producing cross-repository class-name candidates; genuine duplicate implementations remain ambiguous with scoped hints.
- Added copyable service-and-path trace-start selectors and strict doctor aggregates for decorator resolution and registration pairing.
- Added a neutral implementation-resolution workspace with SQLite, trace, hint, and runtime-variable regression coverage.
Other 4
Notes
- Unified direct and wrapper operation-path analysis with deterministic static, ambiguous, and dynamic candidate evidence while preserving lexical scope and source order.
- Prevented future or mutable service-client assignments from becoming persisted bindings; trace and strict doctor now distinguish direct, contextual, ambiguous, unrecoverable, and missing-parameter-metadata cases.
- Added explicit OData entity-versus-operation precedence evidence, richer trace-start remediation, capped implementation hint alternatives, and repository-correct guided traversal.
- Added a neutral seven-package CAP workspace covering runtime substitutions, contextual clients, imported wrappers, OData/entity paths, duplicate helper implementations, SQLite evidence, and CLI quality gates.
Other 3
Notes
- Added deterministic
doctor --format json|tableoutput while preserving legacy-compatible default doctor output; JSON mode returns[]for clean workspaces and table mode renders concise diagnostic rows with capped hint lines. - Documented the 0.1.47 audit follow-up evidence fields for service-client ownership chains, normalized OData operation paths, wrapper path candidates, and implementation hint suggestions.
- Bumped the package patch version for the service-flow audit follow-up release.
- Added deterministic
Other 4
Notes
- Improved ambiguous implementation diagnostics with ready-to-copy scoped hint suggestions for each blocked helper hop.
- Persisted deterministic service-client ownership across helper boundaries, object and tuple destructuring, returned clients, and transaction aliases while preserving dynamic evidence for ambiguous flows.
- Tightened OData operation-invocation normalization and dynamic wrapper-path evidence without promoting entity addressing paths to operation calls.
- Kept strict doctor output concise by default with actionable categories and detail-mode evidence expansion.
Other 4
Notes
- Added repeatable scoped implementation hints with explicit selection and mismatch evidence while preserving conservative automatic and legacy repository selection.
- Propagated proven CAP clients and literal or runtime wrapper paths across relative imports, including object, array, returned-client, and transaction contexts.
- Kept OData invocation arguments, routing placeholders, and compact candidate scores distinct while preserving terminal entity paths.
- Added compact strict-doctor summaries with actionable detail mode and expanded publication atomicity recovery coverage.
Other 4
Notes
- Separated trace-time effective resolution evidence from persisted graph resolution, including runtime substitution details and missing
--varsuggestions. - Added duplicate package-name implementation ambiguity evidence plus trace-time implementation repo hints for guided traversal.
- Aggregated strict doctor implementation candidate noise into actionable categories with capped examples.
- Tightened contextual binding, nested wrapper, OData invocation, and incremental publication atomicity regression coverage.
- Separated trace-time effective resolution evidence from persisted graph resolution, including runtime substitution details and missing
Other 4
Notes
- Reconciled inherited CDS operation search rows from the exact effective-operation set during extension materialization.
- Invalidated concrete extension repositories when derived inherited operations or base resolution semantics change, while preserving no-op generations.
- Tightened lexical binding scope so nested block declarations inside catch and loop bodies do not escape their real blocks.
- Classified proven CAP
send(operationName, payload)calls, including immutable aliases, without applying the rule to genericsend()receivers.
Other 4
Notes
- Continued inherited CDS extension operations into the selected base implementation while retaining concrete routing evidence.
- Reconciled materialized inherited operations on reindex so renamed or removed base operations do not leave stale effective rows.
- Kept ambiguous or unresolved extension bases without a selected base id and ignored commented CDS
usingdeclarations. - Added positional remote CAP
Service.send(method, path, ...)classification for proven CAP clients and tightened catch/loop lexical constant resolution.
Other 4
Notes
- Added imported CDS extension provenance and materialized inherited operations at concrete extension paths without guessing by simple service name.
- Resolved TypeScript identifier expressions through lexical bindings so module constants work and inaccessible shadowed block values are excluded.
- Classified positional CAP
Service.send(...)dispatch only for proven CAP service receivers while leaving genericsendcalls untouched. - Bumped the SQLite schema to persist extension/base and operation provenance metadata.
Other 3
Notes
- Hardened operation path expression analysis to respect lexical scope, declaration order, aliases, and bounded branch candidates.
- Kept external URL and destination templates with substitutions dynamic with sanitized labels.
- Fixed CDS path annotation parsing for prefix/suffix annotations and supported service extension syntax.
Other 4
Notes
- Hardened OData path precedence so entity key, navigation, and media/property paths with placeholders remain terminal entity evidence instead of dynamic operation candidates.
- Preserved separate evidence for service-routing placeholders, operation invocation argument placeholders, and entity key placeholders.
- Render operation-resolved parser entity calls as operation calls in traces while retaining the original parser call type for auditability.
- Added strict doctor coverage for dynamic remote-entity false positives without indexed operation evidence.
Other 3
Notes
- Prefer indexed CDS operation evidence over heuristic remote-entity classification for service-client operation invocations, while keeping true collection, entity, delete, navigation, and media paths terminal.
- Added strict doctor collision diagnostics for terminal remote entity edges that look like operation invocations with indexed operation candidates.
- Persist repository fact analyzer versions and warn during link/strict doctor when force reindex is required after an analyzer upgrade.
Other 5
Notes
- Preserved persisted graph decisions and call-site evidence during trace and graph rendering while keeping contextual runtime resolution as enrichment.
- Classified OData entity reads, mutations, deletes, navigation, media streams, and uppercase entity candidates as terminal remote entity edges instead of unresolved operation candidates.
- Kept dynamic external HTTP destinations dynamic with stable synthetic ids, neutral labels, bounded safe candidates, and sanitized URL evidence.
- Bumped the SQLite schema capability to version 7 and added strict doctor diagnostics for legacy schema drift and reindex-required external metadata.
- Standardized terminal trace-start diagnostics so non-traversable starts return zero graph nodes and edges by default.
Other 4
Notes
- Hardened operation-first trace starts to fail closed on ambiguous, rejected, or non-executable implementation evidence.
- Made decorator normalization explicit and conservative for unsupported expressions.
- Populated queryable external HTTP target metadata with sanitized labels and kept CAP candidates distinct from HTTP endpoints.
- Removed accidental fresh-schema external-target columns from symbols and documented migration/re-index expectations.
Other 4
Notes
- Resolve operation and path trace selectors from indexed CDS operations and persisted implementation edges before conservative handler fallback, including generated
Action<Name>andFunc<Name>decorator constants whose method names differ from public operation names. - Emit the selected start operation and initial implementation hop once, with structured start-resolution evidence and ambiguity/not-found diagnostics that point to the operation, implementation edge, or handler-scope stage.
- Replace numeric external HTTP terminal targets with semantic external destination and endpoint nodes, preserving redacted structured target evidence for destinations, static URLs, dynamic URL expressions, and unknown calls.
- Add schema version 6 migration columns for queryable external target metadata and a strict doctor aggregate for external HTTP target quality.
- Resolve operation and path trace selectors from indexed CDS operations and persisted implementation edges before conservative handler fallback, including generated
Other 4
Notes
- Normalize balanced OData operation invocations when multiline template placeholders appear inside function/action argument lists.
- Preserve invocation argument placeholders as non-routing evidence instead of treating them as missing operation-target runtime variables.
- Reuse the shared OData invocation normalizer during contextual trace resolution so persisted links and trace-time helper propagation handle the same path shapes.
- Keep GET entity key reads, navigation reads, and collection queries terminal unless strong indexed operation evidence resolves them.
Other 4
Notes
- Classify GET OData entity/query paths with query strings, filter functions, key predicates, navigation reads, and query placeholders as terminal remote query/entity edges when no strong indexed CDS operation candidate resolves them.
- Preserve raw path, entity segment, query-string presence, query placeholders, method, and classifier reason in link evidence without creating dynamic operation edges from query parameters.
- Keep balanced top-level OData action/function invocation normalization for real operation imports while avoiding truncation at parentheses inside query strings.
- Document the conservative entity-query versus operation-invocation distinction for neutral CAP service-client calls.
Other 4
Notes
- Propagate contextual service-client bindings through one-level object-parameter destructuring aliases, including renamed and assignment destructuring in neutral CAP helpers.
- Prefer caller-site higher-order wrapper remote-action evidence for literal wrapper paths while keeping dynamic wrapper path diagnostics on the caller edge.
- Refine contextual implementation selection evidence for duplicate helper candidates and report structured duplicate-candidate ties when selection is unsafe.
- Fix strict doctor contextual opportunity metrics so aggregate totals, capped examples, and actionable severity remain internally consistent.
Other 4
Notes
- Enrich trace-time contextual service-client bindings from package-level CAP
cds.requiresaliases so helper-internal sends resolve through require-derived service paths and destinations. - Preserve contextual binding attempt evidence for unresolved helper sends, including effective service path, destination, candidate counts, and resolution reasons.
- Refine strict doctor diagnostics for contextual helper sends to distinguish resolved contextual opportunities, missing
cds.requiresrows, require-backed unresolved sends, remaining runtime variables, and workspaces with no contextual opportunity. - Expand neutral CAP coverage for positional helper arguments, destructured object helper parameters, renamed properties, late assignments, table output, Mermaid output, and require-derived target operations.
- Enrich trace-time contextual service-client bindings from package-level CAP
Other 4
Notes
- Resolve helper-internal remote sends contextually in trace output when service clients are passed through positional arguments or one-level destructured object parameters.
- Add auditable contextual binding evidence for propagated helper client receivers, including caller argument, caller object property, callee parameter, callee receiver, and propagation source.
- Keep nested
this.<property>.<method>()symbol-call resolution conservative unless explicit same-file or relative-import helper instance evidence exists. - Update strict doctor diagnostics for trace-time contextual propagation opportunities and nested
thisreceiver quality signals.
Other 4
Notes
- Make class-instance symbol-call indexing conservative so only same-file and relatively imported helper classes are traced, while built-in collection, date, URL, error, typed-array, promise, and abort-controller instances are ignored.
- Persist identifier and one-level destructured object parameter metadata for executable symbols, including class methods, so contextual service-client binding propagation can map helper arguments to callee receiver names.
- Propagate contextual service bindings through positional helper arguments and destructured object helper parameters with auditable caller and callee evidence.
- Refine unresolved operation diagnostics when indexed candidates exist but service context is absent or the resolution score is below threshold.
Other 4
Notes
- Add conservative class instance method symbol-call resolution for same-file and relatively imported helper classes, with auditable class-instance evidence.
- Propagate service-client binding context through resolved local symbol calls for explicit positional and object-literal helper arguments during trace rendering.
- Harden one-hop higher-order send wrapper literal path propagation for returned async closures and expose wrapper definition evidence.
- Refine strict doctor categories for no-binding remote actions and split ambiguous versus unresolved implementation diagnostics with capped examples.
Other 2
Notes
- Retrospective note: preserve same-file late-assignment service binding extraction for connected clients,
cds.connect.to(...)assignments, identity aliases, and simple transaction aliases before outboundsend(...)calls. - Retrospective note: preserve explicit object destructuring from safe helper-returned client objects so binding rows remain available for downstream remote action linking.
- Retrospective note: preserve same-file late-assignment service binding extraction for connected clients,
Other 5
Notes
- Propagate conservative same-file identity aliases of connected service clients, including typed,
as,satisfies, helper-returned, and transitive aliases, while preserving.tx()alias evidence. - Link outbound
send(...)calls through identity aliases to the closest same-file binding row so destination, service-path, placeholder, and helper-chain evidence remains available. - Add narrow same-file wrapper literal-path propagation for wrappers that pass a client parameter directly to
send({ path }); dynamic wrapper paths remain semantic dynamic targets with parser warnings. - Improve trace-time contextual implementation selection evidence and suppress stale ambiguous unresolved reasons when a unique contextual handler is selected.
- Expand
doctor --strictwith alias-binding, no-binding remote-action, contextual implementation, and wrapper path propagation quality aggregates.
- Propagate conservative same-file identity aliases of connected service clients, including typed,
Other 3
Notes
- Propagate helper-returned connected clients from function declarations, arrow-function variables, function-expression variables, named export lists, and aliased exports into caller destructuring and transaction aliases.
- Preserve conservative object-return analysis by binding only returned properties backed by local
cds.connect.to(...)client variables, while ignoring unrelated metadata fields. - Document contextual implementation selection evidence and strict doctor checks for operation-path-only remote actions.
Other 4
Notes
- Preserve service-binding evidence when helpers return connected clients inside object properties and callers destructure or create simple transaction aliases.
- Track full template-expression placeholder keys such as
${domainInfo.serviceName},${domainInfo.shortName?.toLowerCase()}, and${items[0].service}for runtime--varsubstitution without evaluating JavaScript. - Render unresolved remote actions with unknown or dynamic paths as semantic targets instead of numeric call ids, with parser evidence for shorthand
pathidentifiers. - Expand strict doctor aggregates for normalized OData invocation ambiguity and remote-action unresolved target quality.
Other 4
Notes
- Normalize balanced top-level OData action/function invocation paths before remote operation resolution, including namespace-qualified invocation lookup, while preserving raw and normalized evidence.
- Model remote service-client queries as terminal remote-query edges with stable semantic targets instead of unresolved operation calls.
- Add strict doctor aggregates for remote-query target quality and OData invocation resolution quality.
- Document remote query terminal semantics and normalized invocation resolution behavior.
Other 5
Notes
- Applied conservative CAP receiver eligibility to
.emit()and.publish()so generic realtime, socket, DOM, and EventEmitter-style calls no longer become CAP async event facts without explicit CAP messaging/service evidence. - Added structured parser evidence for local CAP service calls, including source offsets, service lookup/name, operation, and alias chain.
- Propagated outbound parser evidence into call-derived graph and JSON trace evidence under
outboundEvidencefor remote, local, DB, external, and async edges. - Clarified graph-level dynamic flags so terminal DB, external, and event edges stay static while dynamic binding details remain in edge evidence.
- Expanded
doctor --strictwith outbound evidence, graph evidence propagation, event receiver classification, and dynamic terminal-edge consistency aggregates.
- Applied conservative CAP receiver eligibility to
Other 4
Notes
- Replaced remaining raw-text outbound call detection with TypeScript AST classification so comments and strings do not create outbound facts or graph edges.
- Added AST parser/range evidence for outbound calls and conservative CAP/service event registration ownership.
- Tightened synthetic callback and async subscription classification to avoid generic response
.send()and non-CAP event listener noise. - Expanded strict doctor ownerless source categories and examples.
Other 6
Notes
- Index class property arrow/function members as executable symbols so outbound calls inside handler helper properties receive precise source-symbol ownership.
- Add conservative synthetic callback symbols for top-level CAP lifecycle, route, and event callbacks that contain supported outbound calls.
- Harden proxy-member symbol-call resolution with richer evidence and avoid ambiguous repository-wide member-name fallback.
- Split
service-flow linkoperation-call output into remote resolved, local resolved, unresolved, ambiguous, dynamic, and terminal buckets. - Extend strict doctor ownership diagnostics with ownerless outbound-call details by call type and syntactic gap.
- Keep
OutboundCallFact.sourceSymbolQualifiedNamealigned with persistence by preferring explicit qualified names before line-range fallback.
Other 5
Notes
- Fixed
symbol_calls.evidence_jsonpersistence so inserted rows store the parser evidence object instead of a numeric repository id, with explicit initial resolved/unresolved statuses. - Added strict doctor diagnostics for non-object symbol-call evidence, documented unresolved/unknown parser-quality thresholds, and outbound-call source-symbol ownership ratios.
- Made exported public static class methods addressable through relative imports while keeping non-exported, private/protected, and package-imported class member calls conservative.
- Indexed exported shorthand object maps as alias symbols and added conservative proxy-variable evidence for
const worker = ExportedClass.staticFactory(); worker.method()flows when the factory comes from a relative import. - Preserved 0.1.14 symbol-call noise filtering, semantic unknown DB targets, terminal transport-client classification, and strict parser-quality aggregates.
- Fixed
Other 6
Notes
- Made local symbol-call indexing opt-in: CAP DSL, request helpers, package namespace/CommonJS calls, global runtime APIs, and service-client transport helpers are filtered unless indexed local or relative-import evidence makes the edge actionable.
- Expanded local DB query entity extraction for
SELECT.one(Entity),UPSERT.into(Entity),UPDATE.entity(Entity), static element access such asthis.model['Books'], and clearer dynamic-query warning reasons. - Persisted unknown DB query graph targets as semantic
db_entity:unknownterminal nodes with sourcecallIdand parser-warning evidence, so fresh relinks no longer store numeric call ids as DB targets. - Classified unresolved local service-client
.send,.emit,.publish, and.oncalls as terminal transport/client calls when the model does not declare a matching operation, while preserving real declared operations. - Tightened doctor default local-service warnings and added compact
doctor --strictsymbol-call and DB-query quality aggregates with capped top unresolved examples. - Added neutral regression fixtures for symbol-call noise, DB query forms, and local service-client methods.
Other 7
Notes
- Added implementation-context fallback for local
cds.services.*calls so helper packages can resolve model-package operations only when handler/dependency/registration evidence ties the caller repository to the target operation. - Preserved same-repository local service resolution as the strongest path and added explicit evidence/reasons for implementation-context ownership, rejected candidates, and candidate-without-caller-ownership cases.
- Replaced fragile CAP DB query string extraction with AST traversal for chained
cds.run(SELECT/INSERT/UPDATE/DELETE...)forms, including multilinecolumns(...).where(...)chains andthis.EntityNametargets. - Kept genuinely dynamic DB queries terminal while exposing parser-warning evidence and rendering unknown targets as
Entity: unknowninstead of raw numeric call ids in table/Mermaid output. - Further reduced symbol-call noise by filtering built-in collection/string methods, logger calls, global built-ins, third-party package property calls, and unindexed
this.container.method()calls while retaining indexed local helpers and relative object-literal helper imports. - Added doctor aggregate visibility for local service calls resolved by implementation context and calls left unresolved because candidates lack caller ownership.
- Updated README and technical notes to clarify generated constants remain low-level parser output rather than persisted graph facts.
- Added implementation-context fallback for local
Other 7
Notes
- Resolved same-repository local CAP service calls by qualified CDS name, simple service name, and service path, with explicit local transport and alias-chain evidence.
- Made implementation matching decorator-aware so generated
Func*/Action*constants outrank method-name fallback and contradictory decorators are rejected without making edges ambiguous. - Cleared stale unresolved reasons from resolved symbol calls and suppressed false trace unresolved reasons for symbol edges with concrete callee ids.
- Made local symbol-call collection conservative, added named export-list support, and indexed one-level object-literal helper methods as executable symbols so traces can reach helper database queries.
- Added first-class symbol nodes and readable symbol labels/locations to JSON, table, and Mermaid trace output.
- Deduplicated implementation candidates by method identity while retaining multiple registration rows as nested evidence, and kept default doctor output from failing on explainable source-ownership gaps.
- Documented the generated-constant decision: this patch uses deterministic decorator normalization for linking while
parseGeneratedConstantsremains a low-level parser export rather than a persisted graph fact.
Other 7
Notes
- Added repository-owned executable symbols, source-symbol ownership for outbound calls, and local symbol-call facts so traces can follow reachable same-file/imported helpers without including unrelated calls from the same file.
- Replaced local CAP service call extraction with AST alias tracking for
cds.serviceslookups, ignored entity accessors, and linked local calls to exact local operations with explicit local transport evidence. - Scoped service/path trace starts by repository, reports ambiguous starts instead of choosing the first row, and queues resolved implementation handlers by their handler repository and symbol identity.
- Deduplicated operation implementation candidates by logical identity while preserving distinct registration evidence in nested arrays.
- Link output now reports implementation-unresolved counts, table evidence falls back to nested implementation source locations, and depth
stepvalues remain within the requested scope depth. - Generated-constant claims were removed from runtime documentation until persistence and resolution are fully integrated.
- Doctor now reports aggregate analyzer-quality diagnostics for systematic local-service, source-symbol, and trace-scope problems.
Other 5
Notes
- Persist unresolved
OPERATION_IMPLEMENTED_BY_HANDLERaudit edges when implementation candidates exist but all are rejected, including ranked candidate evidence and rejected reasons. - Added a conservative helper-owned implementation path for unique registered helper handlers that implement model-oriented CDS operations without direct package dependency evidence, while keeping multiple helper matches ambiguous and local service-path contradictions rejected.
- Trace output now includes operation-to-handler implementation hops and terminal handler nodes in JSON/table/Mermaid-compatible edge data, including runtime-resolved operation targets.
- Doctor now reports rejected implementation candidates and strict remote-target implementation coverage gaps without making entity-only services noisy by default.
- Updated Node compatibility errors, package metadata, and README wording so current behavior is not described with stale release-specific version strings.
- Persist unresolved
Other 4
Notes
- Fixed implementation dependency matching by binding graph repository ids as text when comparing against
graph_edges.from_idandgraph_edges.to_id, restoring cross-package application-to-model and application-to-handler evidence undernode:sqlite. - Improved implementation candidate ranking with model/application/handler package ownership, exact local service-path evidence, and cross-package dependency/import signals so duplicate operation names in different services resolve to the correct registered handler without false ambiguity.
- Expanded
OPERATION_IMPLEMENTED_BY_HANDLERevidence with candidate ranks, scores, accepted/rejected reasons, package identities, service/operation paths, and dependency/ownership signals for auditability. - Added neutral regression coverage for cross-package app/model/handler registration, duplicate same-name service operations, and graph-id string comparison behavior.
- Fixed implementation dependency matching by binding graph repository ids as text when comparing against
Other 8
Notes
- Replaced regex-only handler registration extraction with TypeScript AST evidence for direct handler arrays, identifier arrays, spreads, imported arrays, default exports, aliases, and safe relative re-exports.
- Persisted class-level
handler_registrationsrows with registration file/line and import evidence so registered handlers can be resolved across same-repository and cross-package layouts. - Relaxed implementation linking to support same-repository registrations, handler-package-owned registrations, and application registrations while preserving ambiguous candidate evidence.
- Continued trace traversal from static and runtime-resolved operations into registered implementation handlers via
OPERATION_IMPLEMENTED_BY_HANDLERedges. - Moved source discovery, reads, and fingerprinting into the protected repository indexing flow so failed reads preserve last-good facts/fingerprints and produce doctor-visible diagnostics.
- Added a legacy-schema doctor warning when migrated stores lack fresh foreign-key metadata for key tables.
- Suppressed the known
node:sqliteexperimental warning for normal supported-runtime database commands without suppressing application errors. - Changed
index_run_abandoneddoctor policy to report only running index runs older than the documented 60-minute threshold, including run id and start time.
Other 5
Notes
- Scoped runtime variable resolution to eligible dynamic, ambiguous, or unresolved remote edges with matching placeholders, preserving terminal and static resolved edge status, target, reason, and confidence.
- Clamped operation-resolution confidence to the
[0, 1]range and retained original runtime expressions alongside effective substitutions and missing-variable evidence. - Resolved helper/package dependency graph edges primarily by indexed package name, persisted ambiguous dependency candidates with evidence, and marked inserted helper edges with explicit statuses.
- Expanded repository fingerprints to include normalized package metadata, full
cds.requires, scripts, package file content, and the analyzer version so metadata-only changes invalidate stale facts. - Replaced the hard-coded CLI version with package metadata as the release source of truth and documented selector, graph variable, SQLite, fingerprint, freshness, and parser-warning semantics.
Other 7
Notes
- Runtime
--varsubstitution now re-runs operation resolution in memory for trace and graph output, clears stale unresolved reasons on exact matches, and traverses into the matched downstream handler without mutating the persisted graph. - Operation nodes now use target CDS provenance while call-site evidence remains on edges.
- Graph edges now carry explicit status values for resolved, terminal, dynamic, ambiguous, and unresolved cases; normal DB, external HTTP, and event terminals no longer receive remote-resolution failure text.
- Replaced per-statement SQLite shelling with a persistent
node:sqliteconnection, bound parameters, transactions, read-only query openings, WAL, busy timeouts, and connection-local foreign-key enforcement. - Added schema user-version migration support for edge status and repository fingerprints.
- Repository indexing now skips unchanged repositories unless
--forceis supplied and reports indexed/skipped counts. service-flow --versionnow matches package metadata for the 0.1.5 release.
- Runtime
Other 5
Notes
- Resolved helper-returned service bindings exported through named export lists, including aliased exports, while preserving helper-chain evidence.
- Parsed
cds.connect.to(alias, options)as separate alias, destination, and service-path evidence so dynamic service paths can resolve with runtime variables. - Improved repository discovery so valid workspace-root Git repositories no longer prevent nested repository discovery, and empty
.gitmarkers are ignored. - Added explicit SQLite foreign-key constraints for fresh databases and recorded alias-expression evidence for service bindings.
- Documented clearer operator guidance for test-file hygiene, doctor severity, and list/trace selector semantics.
Other 6
Notes
- Propagated imported helper-returned
cds.connect.to(...)bindings to caller-local service variables with helper-chain evidence. - Changed operation resolution to prefer ambiguous/dynamic/unresolved graph edges over confident operation-path-only links.
- Improved chained CAP query entity extraction and table/JSON source-location evidence.
- Hardened trace traversal with repo-aware downstream scopes, stable visited keys, edge de-duplication, and cycle markers.
- Added safer clean behavior that only recursively removes marker-owned state directories.
- Added
doctor --strictto separate entity-only service noise from action-oriented default diagnostics.
- Propagated imported helper-returned
Other 6
Notes
- Hardened SQLite CLI access with busy timeouts, retries, and larger read buffers for large graph/link runs.
- Improved CDS service extraction for annotation placement/whitespace variants and materialized inherited operations for
extend servicedeclarations. - Scoped service binding resolution by source file and added helper-returned binding propagation to prevent cross-file variable false positives.
- Improved outbound call extraction for
SELECT.one.fromand common CAP query builders. - Reworked trace traversal to consume linked graph edges recursively and populate JSON nodes.
- Added doctor health checks for silent index-quality issues.
Other 1
Notes
- Initial
@saptools/service-flowCLI package for indexing and tracing SAP CAP service flows.
- Initial
Others providing
Compare all 8 on the Debugging page →- com.sap.cds:cds-feature-consolecom.sap.cds:cds-feature-consoleA CAP Java plugin that connects a running application to CAP console, which consumes its remote monitoring data; CAP console itself is installed separately and still being developed.sap official, capConnects a running CAP Java app to CAP console for monitoring
- com.sap.cds:cds-feature-dev-dashboardcom.sap.cds:cds-feature-dev-dashboardA browser dashboard for a running CAP Java service that shows and manages its messaging, resilience and multitenancy state while developing, open to users with the cds.Developer role.sap official, capBrowser dashboard for a running CAP Java service during development
- @cap-js/console@cap-js/consoleConnects a running CAP application to the CAP console.sap official, capConnects a running CAP application to the CAP console
- @cap-js/data-inspector@cap-js/data-inspectorInspects database entities and their contents at the CAP service level.sap official, capInspects database entities and contents at CAP service level
- sap-cap-debuggersap-cap-debuggerAttaches a debugger to a CAP application already running on Cloud Foundry — enables the Node inspector inside the container, assigns a port and hands you a Chrome DevTools or VS Code attach.independent, capAttaches a debugger to a CAP app running on Cloud Foundry
- @sap/cds-dk@sap/cds-dkThe command line client and development toolkit for CAP projects, with the commands cds build, cds deploy, cds watch and cds add among them.sap official, capCommand line client and development toolkit for CAP
- @saptools/cf-request-runner@saptools/cf-request-runnerDiscovers every API endpoint of a CAP service already deployed to Cloud Foundry by walking its service documents, $metadata and endpoints catalog — falling back to container SSH to parse .cds files.independent, capEndpoint discovery for CAP services deployed to Cloud Foundry
Details
Sources
Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.
Compatibility and provenance
History in the atlas
- First seen.
For your agent
- Ref
artifact:saptools-service-flow- JSON
- /artifacts/saptools-service-flow.json
- MCP
- Connecting an agent