Skip to content

feat: provision ALCops analyzers from NuGet, matched to the DevTools target framework - #25

Open
Arthurvdv wants to merge 3 commits into
mainfrom
feat/alcops-analyzer-provisioning
Open

Arthurvdv wants to merge 3 commits into
mainfrom
feat/alcops-analyzer-provisioning

Conversation

@Arthurvdv

Copy link
Copy Markdown
Member

Summary

  • ALCops analyzer DLLs are now provisioned by the server itself from NuGet, matched to the installed DevTools' target framework
  • ${analyzerFolder}ALCops.*.dll specs resolve from the provisioned folder first, falling through to the tools directory and project-local paths
  • The child almcp receives the provisioned analyzer paths via --codeanalyzers, so al_compile and get_fixes agree on which rules run

What changed

New services:

  • TargetFrameworkMoniker — detect DevTools TFM from the loaded assembly, find the best lib/<tfm> folder in a NuGet package
  • NuGetVersions — parse NuGet flat-container index.json for latest stable / highest overall version
  • AlcopsAnalyzerProvisioner — download ALCops.Analyzers nupkg, extract matching TFM folder, cache under ~/.alcops/analyzers/<tfm>/<version>/, with offline fallback to newest cached version
  • AlcopsAnalyzerProvisionerStartupIHostedService that runs provisioning on a background task (never blocks initialize)

Modified services:

  • ProxyOptions — new --alcops-analyzers <latest|prerelease|x.y.z|off> CLI option (consumed, not forwarded to almcp)
  • ExternalAnalyzerLoader — provisioned folder is probed first for AnalyzerFolderRelative specs; WellKnownBcCop specs (Microsoft cops) are unchanged
  • ProjectAnalyzerResolver — awaits provisioner Ready before resolving analyzers
  • WorkspaceStartupResolver — split into sync project-discovery (Config) and async full-config (GetConfigAsync/BuildAlMcpArgsAsync) that awaits the provisioner
  • AlMcpProxyStartCoreAsync awaits BuildAlMcpArgsAsync before launching the child
  • McpHost — registers provisioner, wires it into loader/resolver/workspace, active under --no-proxy too

Breaking changes

None. The --alcops-analyzers option defaults to latest, which is new behaviour (automatic download from NuGet on every startup). Pass --alcops-analyzers off to restore the previous behaviour where no analyzers are downloaded.

Test plan

  • dotnet build --configuration Release — 0 warnings, 0 errors
  • dotnet test --configuration Release — 102 tests pass, 0 skipped
  • TargetFrameworkMonikerTests — ShortName conversions, FindBestLibFolder exact/fallback/netstandard/none cases
  • NuGetVersionsTests — latest stable vs prerelease, malformed entries, edge cases
  • AlcopsAnalyzerProvisionerTests — extracts correct TFM, writes manifest, cache hit on second run, network failure with warm/cold cache, off mode, pinned mode (all with fake HttpMessageHandler, no real network)
  • ExternalAnalyzerLoaderTests — provisioned folder wins for ${analyzerFolder} specs, falls through when absent, not used for ${CodeCop}
  • ProxyOptionsTests--alcops-analyzers parsed and consumed
  • All existing tests continue to pass (proxy tests, apply-fix tests, workspace resolver tests)

Manual verification (3 runs with --no-proxy, from the ApplyFixProject fixture)

Run 1 — default (latest): fresh download from NuGet

info: ALCops.Mcp.Services.AlcopsAnalyzerProvisioner[0]
      DevTools target framework: net10.0
info: ALCops.Mcp.Services.AlcopsAnalyzerProvisioner[0]
      ALCops analyzers: resolved version 1.2.0
info: ALCops.Mcp.Services.AlcopsAnalyzerProvisioner[0]
      Downloading https://api.nuget.org/v3-flatcontainer/alcops.analyzers/1.2.0/alcops.analyzers.1.2.0.nupkg
info: ALCops.Mcp.Services.AlcopsAnalyzerProvisioner[0]
      ALCops analyzers: v1.2.0 (net10.0) provisioned to C:\Users\arthur.vandevondervo\.alcops\analyzers\net10.0\1.2.0

Run 2 — cache hit: same command, no download

info: ALCops.Mcp.Services.AlcopsAnalyzerProvisioner[0]
      DevTools target framework: net10.0
info: ALCops.Mcp.Services.AlcopsAnalyzerProvisioner[0]
      ALCops analyzers: resolved version 1.2.0
info: ALCops.Mcp.Services.AlcopsAnalyzerProvisioner[0]
      ALCops analyzers: v1.2.0 (net10.0) from C:\Users\arthur.vandevondervo\.alcops\analyzers\net10.0\1.2.0

Run 3 — --alcops-analyzers off: provisioning disabled, no download

info: ALCops.Mcp.Services.AlcopsAnalyzerProvisioner[0]
      ALCops analyzer provisioning disabled (--alcops-analyzers off)

Design notes

The provisioner detects the DevTools target framework from the TargetFrameworkAttribute on the loaded Microsoft.Dynamics.Nav.CodeAnalysis assembly (the same assembly BcToolsLocator already resolved). It fetches the latest stable ALCops.Analyzers version from the NuGet flat-container index, downloads the nupkg, and extracts the best matching lib/<tfm>/ folder into ~/.alcops/analyzers/<tfm>/<version>/. A .alcops-manifest.json tracks the extracted files so cache validity can be checked without re-downloading. When the network is unreachable, the provisioner falls back to the newest cached version for the same TFM. The version can be pinned (--alcops-analyzers 1.2.0), set to prerelease, or disabled (off). The cache path can be overridden via ALCOPS_ANALYZERS_CACHE.

The provisioner runs on a background task via AlcopsAnalyzerProvisionerStartup (an IHostedService), so it never blocks the MCP initialize handshake. Downstream consumers (ExternalAnalyzerLoader, ProjectAnalyzerResolver, WorkspaceStartupResolver) await AlcopsAnalyzerProvisioner.Ready which completes with the provisioned folder path (or null on failure/off). When the provisioned folder is available, ExternalAnalyzerLoader.ResolveDllPath probes it first for ${analyzerFolder}ALCops.*.dll specs; Microsoft cops and non-ALCops third-party analyzers are unaffected.

This PR is the prerequisite for a follow-up PR that drops the AL VS Code extension bin/ probe from BcToolsLocator, since users who relied on the extension solely for its analyzer DLLs now get them provisioned automatically.

🤖 Generated with Claude Code

Arthurvdv and others added 3 commits September 13, 2026 17:31
…target framework

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant