Skip to content

Read the framework however the project file spells it - #56

Merged
peopleworks merged 1 commit into
mainfrom
fix/target-framework-spellings
Aug 29, 2026
Merged

Read the framework however the project file spells it#56
peopleworks merged 1 commit into
mainfrom
fix/target-framework-spellings

Conversation

@peopleworks

Copy link
Copy Markdown
Owner

Closes #50.

ExtractProjectMetadata read <TargetFramework> and no other spelling. A project from before the SDK format declares <TargetFrameworkVersion>v4.8</TargetFrameworkVersion> and has no <TargetFramework> at all, so it reported an empty framework — not a wrong one, none.

Reproduced on the six real applications before anything was changed:

PWPresupuesto.Module      tfm=[]        <- before
pwControlVisita.Module    tfm=[]        <- before
ComisionSOS.Module        tfm=[net9.0]

Read it however it is spelled

DeclaredTargetFramework handles all three spellings, in the same shape DeclaredDevExpressVersion already uses for the DevExpress version — this is the framework getting the treatment #45 gave the version.

Spelling Example Reported
<TargetFramework> net9.0 net9.0
<TargetFrameworks> net8.0;net9.0 net8.0;net9.0
<TargetFrameworkVersion> v4.8.1 net481

The multi-targeting spelling was invisible to the old regex too, and it is reported as the project declared it rather than reduced to one entry: picking one would be inventing a fact, and the list is what an agent needs, because code has to compile on every framework named there.

v4.8net48 and v4.8.1net481 are different frameworks, not a typo of each other, and stay distinguishable. Null stays distinct from a value, because "no project file was read" and "it targets net48" must never render the same way.

All six real applications after:

pwLegalOffice                net7.0     PWPresupuesto      net48   <- was blank
PeopleWorksCopilotBackOffice net9.0     pwControlVisita    net481  <- was blank
XafAiExtensionsDataAnalysis  net9.0     ComisionSOS        net9.0

The wiki went from four framework pills to six.

The part the issue is actually about

Naming the framework in a summary is not the same as forbidding what it forbids. AGENTS.md now carries a ground rule, in the same numbered list as the ORM rule and gated the same way — written from a reading, never from a default, so a modern application is never handed it.

The rule is deliberately narrow, and that was the hard part. Most modern C# syntax does compile on net48 once LangVersion is set: file-scoped namespaces, raw string literals and top-level statements are fine there. A rule that forbade all of it would be false, and a false half would cost the true half its credibility — which is the same argument this project already makes about reporting "no reports" for an application that has forty.

So it names only what no compiler switch can supply:

  • the C# language version defaults to 7.3 unless the project sets LangVersion
  • System.Text.Json, IAsyncEnumerable<T>, Index and Range are absent without a package
  • default interface methods cannot work at all
  • record, init-only setters and required members compile only where a polyfill attribute is supplied

Three render sites printed the empty value instead of omitting it

The Markdown overview emitted a Framework: label with nothing after it, the detail page did the same, and the MCP overview told an agent Target framework: . — which is not a smaller answer than naming one, it is an unreadable one, and that string is what an agent reasons from. All three now omit the field.

The fixture

New LegacyFrameworkSolution in the pre-SDK project format, with C# written the way that framework compiles: block namespaces and backing-field properties, no auto properties. A fixture that declared a legacy framework while writing modern C# would prove the project file was read and nothing else.

It also reaches DevExpress through assembly references rather than PackageReference, so both project-file readings are pinned against the same file — a change that broke the version while fixing the framework would otherwise look green.

Verification

  • 548 tests, up from 525.
  • The document assertions run through the generated AGENTS.md, not the extraction result, because extraction being right is not the same as the agent being told.
  • net10.0 has its own test. It is the one moniker where a rule matching net followed by a low digit gets the answer exactly backwards — and it is the framework this tool is built on, so it would have been wrong about its own repository first.

🤖 Generated with Claude Code

https://claude.ai/code/session_01W45tzJFX3NoSrk7svtQeKT

Closes #50.

ExtractProjectMetadata read <TargetFramework> and no other spelling. A project
from before the SDK format declares <TargetFrameworkVersion>v4.8 and has no
<TargetFramework> at all, so it reported an empty framework -- not a wrong one,
none. Two of the six real applications came out of `xaflogic wiki` with no
framework beside four reporting net7.0 and net9.0.

DeclaredTargetFramework reads all three spellings and normalises the pre-SDK one
to its moniker: v4.8 becomes net48 and v4.8.1 becomes net481, which are
different frameworks and stay distinguishable. A multi-targeting
<TargetFrameworks> list, which the old regex could not see either, is reported
as the project declared it rather than reduced to one entry -- code has to
compile on every framework named there. Null stays distinct from a value,
because "no project file was read" and "it targets net48" must never render the
same way.

Verified on all six real applications: PWPresupuesto net48, pwControlVisita
net481, and the four SDK-style ones unchanged. The wiki went from four framework
pills to six.

AGENTS.md now carries a ground rule for a .NET Framework application rather than
only naming the framework in its summary, which is what the issue is actually
about: an agent told nothing assumes a modern framework and reaches for APIs
that are not there. The rule is deliberately narrow. Most modern C# syntax does
compile on net48 once LangVersion is set, and a rule forbidding all of it would
be false -- which would cost the true half its credibility. It names only what
no compiler switch supplies: the C# 7.3 default, the absent System.Text.Json,
IAsyncEnumerable<T>, Index and Range, default interface methods being impossible
at all, and the polyfill attributes record and required need. Written from a
reading and never from a default, the same discipline the ORM rule keeps, so a
modern application is never handed it.

Three render sites printed the empty value rather than omitting it: the Markdown
overview and detail page emitted a Framework label with nothing after it, and
the MCP overview told an agent "Target framework: ." -- which is not a smaller
answer than naming one, it is an unreadable one, and that string is what an
agent reasons from.

New fixture LegacyFrameworkSolution in the pre-SDK project format, with C#
written the way that framework compiles: block namespaces and backing-field
properties, no auto properties. A fixture that declared a legacy framework while
writing modern C# would prove the project file was read and nothing else. It
also reaches DevExpress through assembly references rather than PackageReference,
so it pins both project-file readings against the same file.

548 tests, up from 525.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W45tzJFX3NoSrk7svtQeKT
@peopleworks
peopleworks merged commit 49be58c into main Aug 29, 2026
6 checks passed
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.

Legacy projects report no target framework, so an agent is never told it is on .NET Framework

1 participant