Version: plugin 2.9.4 · Skill: /understand · File: skills/understand/extract-import-map.mjs
Summary
resolveCSharpImport resolves a C# using by reusing the Java/Kotlin dotted-FQN strategy, which assumes the file-per-public-type naming convention. C# has no such rule — a using names a namespace, which can span many files and need not mirror the directory layout. The result is that C# import resolution silently produces nothing on any project whose folders do not exactly match its namespaces.
// extract-import-map.mjs:1266
export function resolveCSharpImport(rawImport, _file, ctx) {
return resolveDottedFqn(rawImport, '.cs', ctx.csIndex);
}
// extract-import-map.mjs:1149
function resolveDottedFqn(fqn, ext, suffixIndex) {
const filePart = trimmed.replace(/\./g, '/') + ext; // "MegaPDF.Core.Engine" -> "MegaPDF/Core/Engine.cs"
const matches = suffixIndex.get(filePart);
return matches ? [...matches] : [];
}
using MegaPDF.Core.Engine; probes for a file at MegaPDF/Core/Engine.cs. The actual sources are src/MegaPDF.Core/Engine/IPdfEngine.cs, Geometry.cs, and so on — a directory, not a file. Nothing matches, so the import resolves to [].
Impact, measured on a real repo
A 180-file polyglot repo (52 C# files, ~67 internal using directives — MegaPDF.Core.Engine ×22, .Engine.Pdfium ×17, .Recovery ×11, .Editing ×10, .Services ×7) produced an import map with 2 edges across all 180 files, and neither was C#.
This is not merely missing edges. compute-batches.mjs clusters on the import graph, so with no edges it degraded to:
Info: compute-batches: merged 136 small batches (140 files) into 6 misc batches — singletons and orphans consolidated
Half the codebase then went to file-analyzer in arbitrary 25-file groups rather than semantic ones. Worse, the agent contract tells analyzers to use batchImportData and not re-resolve imports from source, so the honest outcome is a graph where the dominant language appears structurally uncoupled — 52 disconnected nodes. I only got a usable graph by explicitly overriding that instruction and having the analyzers map namespaces to directories by hand; all 141 imports edges in the final graph are hand-derived.
There is a related trap for downstream agents: file-analyzer's Step E self-validation checks emitted edges against batchImportData and neighborMap. When both are empty, its criteria are unsatisfiable — every correct hand-derived edge registers as a failure.
Repro
- Any C# project where namespaces do not mirror directories — e.g. a project folder
src/Foo.Core/ declaring namespace Foo.Core.Engine in src/Foo.Core/Engine/*.cs.
- Run
/understand.
.ua/intermediate/scan-result.json → importMap has no entries for the C# files.
Suggested fix
Index C# files by declared namespace, not by path shape: parse namespace X.Y.Z; (file-scoped) and namespace X.Y.Z { … } (block) during extraction, build namespace → [files], and resolve using X.Y.Z to that set. Optionally narrow to files declaring types actually referenced in the consumer, which is what avoids fan-out to every file in a large namespace.
The same assumption may not hold for other namespace-based languages routed through resolveDottedFqn — worth a look at Scala, and at C++ if namespaces are ever added.
Version: plugin 2.9.4 · Skill:
/understand· File:skills/understand/extract-import-map.mjsSummary
resolveCSharpImportresolves a C#usingby reusing the Java/Kotlin dotted-FQN strategy, which assumes the file-per-public-type naming convention. C# has no such rule — ausingnames a namespace, which can span many files and need not mirror the directory layout. The result is that C# import resolution silently produces nothing on any project whose folders do not exactly match its namespaces.using MegaPDF.Core.Engine;probes for a file atMegaPDF/Core/Engine.cs. The actual sources aresrc/MegaPDF.Core/Engine/IPdfEngine.cs,Geometry.cs, and so on — a directory, not a file. Nothing matches, so the import resolves to[].Impact, measured on a real repo
A 180-file polyglot repo (52 C# files, ~67 internal
usingdirectives —MegaPDF.Core.Engine×22,.Engine.Pdfium×17,.Recovery×11,.Editing×10,.Services×7) produced an import map with 2 edges across all 180 files, and neither was C#.This is not merely missing edges.
compute-batches.mjsclusters on the import graph, so with no edges it degraded to:Half the codebase then went to
file-analyzerin arbitrary 25-file groups rather than semantic ones. Worse, the agent contract tells analyzers to usebatchImportDataand not re-resolve imports from source, so the honest outcome is a graph where the dominant language appears structurally uncoupled — 52 disconnected nodes. I only got a usable graph by explicitly overriding that instruction and having the analyzers map namespaces to directories by hand; all 141importsedges in the final graph are hand-derived.There is a related trap for downstream agents:
file-analyzer's Step E self-validation checks emitted edges againstbatchImportDataandneighborMap. When both are empty, its criteria are unsatisfiable — every correct hand-derived edge registers as a failure.Repro
src/Foo.Core/declaring namespaceFoo.Core.Engineinsrc/Foo.Core/Engine/*.cs./understand..ua/intermediate/scan-result.json→importMaphas no entries for the C# files.Suggested fix
Index C# files by declared namespace, not by path shape: parse
namespace X.Y.Z;(file-scoped) andnamespace X.Y.Z { … }(block) during extraction, buildnamespace → [files], and resolveusing X.Y.Zto that set. Optionally narrow to files declaring types actually referenced in the consumer, which is what avoids fan-out to every file in a large namespace.The same assumption may not hold for other namespace-based languages routed through
resolveDottedFqn— worth a look at Scala, and at C++ if namespaces are ever added.