Other People's Code in My Repos, and Mine in Theirs

business-central-mcp ·CentralGauge ·LethAL ·tree-sitter-al
Other People's Code in My Repos, and Mine in Theirs

Last post was July 10. Since then the commit log went a bit sideways, so here is a catch-up.

Two things happened this summer that had not happened before. Other people started sending real code to my repos. And I started sending AL support to other people’s repos, ten of them in one day. This post is about both directions, with the usual pile of releases in between.

bc-mcp: BC Online support, delivered by someone else

The business-central-mcp server (the one that drives the BC web client the same way your browser does) has been on-prem only since day one. My setup is mainly based on on-prem containers. SaaS was on the list, somewhere below everything else.

Then Flemming Bakkensen opened a PR adding SaaS support. 94 files, 12,500 lines. It started with an apology for going straight to code without filing an issue first, and ended with “take it whole, cherry-pick parts, ask for a rework, or close it. We’re running it ourselves either way.”

BC SaaS support shipped today as v1.6.0. Point it at your portal URL and the first tool call opens the sign-in window. Before that, v1.3 through v1.5 added an OData query tool, MCP prompts, multi-row selection and file downloads.

CentralGauge: a bug hunter showed up

CentralGauge got its most valuable contributions as issues, not PRs. Greg Kaupp from ArcherPoint filed three of them in June, each with a reproducer attached.

The big one: he found that every heavy BC operation on BC28 was going through a slow PowerShell path, and measured it properly instead of just complaining. A six-task bench run took 67 minutes. After the fix, the same run took 7 minutes with identical results. Way faster now.

Two bars: before, 67 minutes. After, 7 minutes. Same six-task bench run.

While this was going on I rebuilt the task set. The old tasks were mostly “make this”. The new ones are trap tasks: debugging, performance, cross-object changes, sourced from real Azure DevOps PRs at Continia, plus a batch inspired by Volodymyr Dvernytskyi. Not “write a codeunit”, but “this posting routine is slow and here is why you think it is slow, and you are wrong”.

One thing I did not expect: eleven of those tasks had to be reworded because Fable 5’s safety classifier refused to read them. Tasks about data corruption and borrowed identities apparently look like something else if you squint.

Honesty note: the published leaderboard is still on the old task set and missing a lot of newer models! Sorry! That is on me and is getting fixed as soon as possible.

LethAL: mutation testing for AL

LethAL went public in late July. And what is it? Well mutation testing for AL. So it introduces small deliberate breakages in your code (flip a boundary, remove a SetRange, drop a TestField, turn Modify(true) into Modify(false)) and checks whether your tests notice. If they do not, you have a test that passes and proves nothing. So we are testing the tests, not the code.

Diff of the Redeem procedure from LethAL's gift card example. Line 40, the balance guard, changed from less-than to less-than-or-equal. 8 tests passed. Mutant survived, no test redeems the exact balance.

The trick that makes it feasible in BC at all: every mutant ships in one build, one big glorious build to rule them all. Activating a mutant is a table write, not a recompile. Without that, a run would take days.

Stefan Maroń ran it on Linux three days after it went public and found it never could have worked there: the tool paths were hardcoded to Windows. He fixed it and sent the first benchmark numbers from a plain container.

It is alpha, and as of today there are prebuilt binaries for Windows, Linux and macOS.

bc-dev-mcp: half of it is not mine

bc-dev-mcp is the development-side MCP server (test runner, debugger, coverage, profiles) I wrote about in July. Since then Ian Ray has opened eleven PRs and had nine merged. Entra login, targeted debugger attach, coverage gap analysis against your git diff, break-on-record-write, passthrough to BC28’s own MCP tools, test orchestration, symbol package retrieval.

By line count that is roughly half the repo. Thanks for all the PRs, Ian!

AL everywhere: one day, ten upstream PRs

The .al file extension collides with Perl’s AutoLoader. So vim thought AL files were Perl. So did GitHub for a chunk of them, and cloc counted them as Perl. Every “add AL support” PR in the tooling world is really a “stop calling it Perl” PR.

A .al file, before: Perl (AutoLoader), crossed out. After: AL (Business Central). Merged in vim, neovim, linguist, cloc, scc. Open in tokei, helix, pygments, shiki, highlight.js.

On August 8 I sat down and did all of them:

ProjectWhatStatus
vim.al filetype detectionApplied as patch 9.2.0926
neovimPort of the vim patchMerged
linguist (GitHub)Fix the .al vs Perl heuristicMerged
clocCount AL, disambiguate from PerlMerged in 72 minutes
sccCount ALMerged
tokeiCount ALOpen
helixAL language + tree-sitter grammarOpen
pygmentsFull AL lexerOpen
shiki (textmate-grammars-themes)AL TextMate grammarOpen
microsoft/ALFix highlightjs_al for Highlight.js v11Open

Five in, five waiting. The neovim one bounced me to vim first, vim applied it the same evening, neovim ported it the next night. Whole loop under 25 hours. The open ones have green CI and no comments yet. Waiting is part of the job.

tree-sitter-al v4: what “0 errors” was hiding

The grammar behind all of this got two big updates this summer.

The parse gate had said 0 errors for months. It was lying: a file that never got opened counted as clean. Once I compared bytes instead of error counts, about twenty defects fell out. Operator precedence wrong in three places, a #if condition that swallowed the next line, half a million places where bytes were lexed and thrown away. v4 is lossless. Then v4.0.1 three days later, because three users hit things in configurations I never run. Each fix came with a gate so it stays fixed.

Terminal output: Parsed OK 36, Errors 4, Success rate 90 percent. Annotation: files actually opened, zero.

al-perf v4: measuring instead of assuming

al-perf v4 added a lot more detectors, as I call them, and fixed a few bugs that had sneaked into two existing ones. I put a BC28 container to work and measured what things actually cost instead of assuming. Some of the old advice turned out to be wrong, and a couple of findings got demoted from critical because the platform handles them better than I gave it credit for. The findings that remain are the ones I can back with a number.

Smaller things

al-sem (the whole-program AL analysis engine) hit 1.0. al-lsp-for-agents got a string of releases, mostly fixes for the ways Microsoft’s AL language server hangs, crashes the client, or returns an empty result before its index is built.

DevOpsWorker, the Azure DevOps pipeline from the May post, is on GitHub now. Its PR review grew a monthly reflection agent that proposes changes to its own prompts based on how humans reacted to its findings. A human approves. It does not get to edit itself unsupervised.

Side quests

Two things that have nothing to do with AL tooling and I did anyway.

I got Business Central running on a Snapdragon laptop. Both Ubuntu 26 LTS ARM and Windows on ARM, Docker, x64 emulation, one script, BC boots in a couple of minutes with the web client. SQL Server under emulation dies roughly one start in three, yeah I know, still WIP. Experimental, works on my machine, and the notes from the investigation say a native arm64 service tier is closer than you would think.

A Snapdragon X arm64 chip, an arrow to an x64 emulation box (Docker, FEX), an arrow to a Business Central web client window. Boots in about 150 seconds, SQL Server dies one start in three, experimental.

And I wrote a Linux driver for a Kensington VeriMark fingerprint reader. Because why not? Well I’m switching to Fedora from Windows on my dev machine.

Thanks

Flemming, Greg, Stefan, Ian, and the maintainers who merged an AL PR from someone they had never heard of. The whole point of putting this stuff on GitHub was that it stops being mine alone. This summer it did.