Call graph
ALbuild builds the call graph of your AL apps on every build: which method calls which, across your apps and into Microsoft's. From it, every build reports what it contains, what no test reaches, what is dead or calls obsolete code, and what a change affects, compared with the previous build. A short architecture file turns the graph into a map of the app: its parts, the flows it exists for and a tour for new developers. Agents query the same map instead of searching the code.
Built on the AL Tool
The graph comes from Microsoft's al graph command, part of the AL Tool from version 18.0.39 (Business Central 29). ALbuild picks the AL Tool exactly as for compiling, from the BC version you build against. For BC 28 and older the AL Tool has no al graph, so the step is skipped with a warning.
The PowerShell module does the work; the Azure DevOps tasks, the CLI and the MCP server are thin surfaces over it.
How the graph is built
All AL projects of the workspace are compiled together with the source of their dependencies (al graph extract-whole). That matters: compiled one by one, the 365 business Banking test app lost all 6,342 of its calls into the app.
- Microsoft's apps (Base Application, System Application, Business Foundation, ...) come from the BC artifact. Their source is unpacked once per app and version and cached on the agent.
- Other dependencies are compiled along when their package ships source. A dependency without source is listed in the report: calls into it are missing, and so are calls that merely pass one of its types, because the compiler cannot bind them.
- The whole-corpus graph is large (Banking: 141,340 nodes, 573,151 calls), almost all of it Microsoft-internal. It is pruned to your apps and their boundary: everything in your apps, plus the Microsoft methods and events they touch (Banking: 13,911 nodes, 78,541 calls).
On a warm agent this costs about a minute for an app the size of Banking; the first build of a BC version also unpacks Microsoft's source once.
What every build reports
| Figure | Meaning |
|---|---|
| Objects, methods, triggers, calls | The size of your apps. Calls are split by kind: direct, event, interface, trigger. |
| Calls into dependencies | How much your code relies on Microsoft's and other apps. |
| Event publishers / subscribers, interface implementations | The extension points your apps offer and use. |
| Test methods | [Test] procedures found in the source. |
| Not reached by tests | Product methods and triggers that no [Test] method reaches along the call graph. |
| Obsolete calls | Product code calling a method or object marked obsolete. Test code is not counted. |
| Dead methods | Non-public methods nothing calls. |
| Unassigned / described objects | Own objects no part claims, and objects with a description. See the map of the app. |
With a baseline every figure also shows its change against the previous build.
Static, and honest about it
"Not reached by tests" is static reachability, not measured coverage: events the platform raises (table triggers such as OnAfterModifyEvent) are not followed. Use it next to code coverage, which measures what actually ran.
Dead code without false alarms
A call graph alone would report far too much dead code. ALbuild counts a method as dead only when nothing reaches it in any of these ways:
- A call in the graph.
- An event subscription:
[EventSubscriber]methods are entry points. - Interface dispatch.
al graphresolves interface calls to one overload only, so every overload of an interface method counts. A codeunit that implements an interface from an app without source keeps all its non-local methods. - A call by name: a string literal with the method name, e.g. a notification action.
- A call the compiler could not bind: the name is called somewhere in the source text.
On Banking these rules took the count from 301 to 36 dead methods, each one checked against the source.
AL checks
The report also lists, for information only:
- own objects with inherent permissions or entitlements,
- Microsoft objects with inherent permissions or entitlements that your code calls,
[NonDebuggable]product methods.
These lists never fail a build and have no gate.
Why cloud-to-OnPrem is not checked
A check for cloud code that reaches OnPrem-only members was measured and left out. On Banking, OnPrem members are reached only through Microsoft's own calls (53 nodes, none called directly), so the check would raise nothing but false alarms.
Impact: what a change affects
Every build compares its graph with a baseline: a pull request build with the last successful build of its target branch, any other build with the last successful build of its own branch. The baseline is the callgraph artifact of that build.
| List | Meaning |
|---|---|
| Added / removed | Methods and triggers that appeared or disappeared. |
| Modified | Members whose calls changed, or whose body text changed (per-member source hashes). |
| Affected callers | Everything that transitively calls a changed member, or called a removed one. |
| Tests reaching the change | The [Test] methods that reach a changed member: the tests to run first. |
The map of the app
A graph of thousands of nodes does not explain an app. The map groups your objects into parts, describes them and shows how they connect.
The parts come from callgraph.architecture.json in the repository root. A person or an agent writes it locally and commits it. The pipeline never calls AI: it only reads the file.
- Without the file, the parts come from the folders: one part per folder of each app's source (below an optional
src/), split one level further when a folder holds many objects. Test apps get one part each. Every repository gets a readable map this way. - Descriptions: the
///summary above an object's declaration always describes it. The file only adds descriptions for objects without one, or overrides them. - A broken file does not break the report: the map falls back to the folders and says why.
The file
An excerpt from 365 business Banking, with shortened texts:
Code
| Key | Meaning |
|---|---|
project | Name and summary of the app. |
parts | id, label, summary; parent nests a part in another. kind is one of app, feature, service, integration, data, ui, setup, library, test, external (default feature). |
parts[].paths | Globs on the source path: ** any folders, * within one segment, ? one character, {a,b} alternatives. A pattern without a wildcard also matches as a folder. |
parts[].objects | Patterns on Type Name or Type Id, same globs, e.g. Codeunit bdev.BNK API*. |
objects | Descriptions by Type Name, for objects without a /// summary or to override it. |
flows | What the app exists for, step by step. Each step names a node (an object or a part id), a title and a note. |
tour | The stops shown on the report's first open, in the same form as flow steps. |
An object belongs to the deepest part that claims it. Between parts of the same depth, an objects pattern beats a paths pattern, and the more specific pattern wins.
Banking in figures
The Banking file has 51 parts, 5 flows and an 8-stop tour. All 935 objects are assigned; 628 are described, 130 of them in the file and the rest by ///.
Write and check it
Code
graph describe writes a starting file without AI: the folder-based parts, empty flows and an empty tour. With --worksheets it also writes one Markdown worksheet per part: its objects, their members and what they use. Write the summaries, flows and tour from them, by hand or with an agent.
graph describe --check validates the file against the graph and exits 1 on problems (--no-fail reports only):
- unknown parts and nodes, with a suggestion,
- patterns that match nothing,
- flow steps that do not call each other.
In PowerShell: New-BcCallGraphArchitecture and Test-BcCallGraphArchitecture.
In the pipeline
Code
AnalyzeCallGraph@0 is BuildCallGraph@0 followed by ReportCallGraph@0. Both pick up callgraph.architecture.json from the workspace root; there is no input for it. Use the two separately to build and report in different steps or jobs. Every build then gets:
- a Call graph tab: the interactive report. It follows the Azure DevOps theme, light or dark.
- Map: the parts as cards with their calls between them. Open a part down to its objects and methods, with breadcrumbs and search; select a card for its summary, what it calls and what calls it. A lens decides the only colour on the map: test reach, structure (the kind of part), changes since the baseline, or dead code. Solid lines are calls out of the selection, dashed lines calls into it. The arrow keys follow the calls.
- Pages: everything a page, report, query or xmlport reaches. Test doubles are left out.
- Flows: each flow step by step. The guide (the tour) opens on first use.
- Impact, Findings (not reached by tests, dead code and obsolete calls in one tab), AL checks, and Apps with any architecture problems.
- the figures, the impact and the top findings in the build summary.
- the
callgraphartifact: the graph, the analysis and the report, and the next build's baseline.
Building or reporting the call graph never fails a build. Only a gate you set does:
| Input | Fails the build when |
|---|---|
maxNotReachedPercent | more than this percentage of product methods is not reached by tests |
maxDeadMethods | more dead methods are found |
failOnObsoleteCalls | product code calls obsolete code |
The central ci.yml template runs AnalyzeCallGraph@0 after the compile (parameter callGraph, on by default) and sets no gate.
Locally
Code
The graph is written to .albuild/callgraph in the workspace. graph report --open opens the same interactive report the build tab shows.
Lookups for agents
An agent that needs to know where something is can ask the graph instead of searching the code. The lookups are read-only and need neither the AL Tool nor a container.
Code
| Lookup | Answers |
|---|---|
overview | The parts, what each is for, and the strongest connections between them. |
find | Parts, objects and methods whose name or description contains all the words. |
show | One part, object or method: description, part, members, what it uses and what uses it. |
path | The shortest call chain between two objects or parts. On Banking the example returns Bank Access → Banking User → API Client. |
The MCP server offers the same as graph-overview, graph-find, graph-show and graph-path, plus graph-describe, graph-check and graph-hint. In PowerShell it is Get-BcCallGraphInfo.
albuild graph hint (Set-BcCallGraphAgentHint) writes a marked section into CLAUDE.md that points agents at these lookups. The rest of the file is left alone. --file picks another file such as AGENTS.md; --remove takes the section out again.
