01
WK-01 · Enterprise documentation automation
Analyze the code deterministically. Only then let a model write about it.
A large .NET estate — 300+ programs across roughly 100 repositories — with documentation that had drifted years away from the source. Writing it by hand is a multi-quarter effort that is stale the week it ships.
- Status
- POC · pilot-oriented architecture
- Role
- Architecture · Engineering · Estimation
- Stack
- C# · .NET · Roslyn · JSON · Azure OpenAI · Azure DevOps
- Scale
- 300+ programs · ~100 repositories
- Focus
- Deterministic extraction before generation
Context
The obvious approach is to paste source files into a chat model and ask for documentation. It fails at estate scale for three separate reasons, and it is worth separating them: the code does not fit in a context window, the model has no ground truth to check itself against, and nothing about the process is repeatable when the code changes next week.
So the interesting question was never "which model." It was "what does the model need to be handed."
Approach
A Roslyn analyzer walks each solution and emits structured metadata as JSON: types, members, signatures, inheritance, call relationships, dependency edges, entry points, attributes. That pass is deterministic. Run it twice on the same commit and you get the same file.
Generation then works from that structured representation rather than from raw repository sprawl, one unit at a time, against prompt templates written per document type. The whole thing runs as an Azure DevOps pipeline step, so documentation is produced from the current commit instead of from someone's memory of the current commit.
Why the order matters
The model is not the source of the parameter count. The analyzer is, and its output is a diffable artifact — so when the prose and the code disagree, the disagreement is findable rather than a matter of opinion. Facts and language are produced by different components, which means they fail differently and can be checked differently.
Three consequences fall out of that split. The expensive part becomes cacheable, because metadata only changes when code changes, which makes regeneration incremental rather than a full re-run. When output is wrong you can tell which stage was wrong — extraction or prompt — instead of re-rolling the whole thing and hoping. And the pipeline gets a natural place to hang review, because structured metadata is diffable in a way prose is not.
A single-stage "read the repository and explain it" design gives you none of those seams. It gives you one opaque step whose only debugging tool is asking again.
Validation & status
An external technical review of the architecture found no major red flags and considered the approach sound. The impact figures below were built during the POC from hour counts and rates. They are estimates used to size the opportunity, and they have not been audited against realized spend.
It remains a POC with a pilot-oriented architecture. What is left is the unglamorous part: coverage across repositories that do not look alike, a review workflow for generated output, and a named owner for the pipeline.
| Measure | Value | Basis |
|---|---|---|
| Estate in scope | 300+ programs · ~100 repos | Counted scope of the analysis |
| Initial engineering hours potentially avoided | 600+ | Estimate — hour counts developed during the POC |
| Estimated annual value | $25K – $50K | Estimate — not audited or realized savings |
| External architecture review | No major red flags | Third-party technical review of the design |
Tradeoff
The analyzer is the expensive component and it is language-specific. It buys reliability at the cost of portability: a Python or COBOL estate needs a new extractor before any of this works. I would make that trade again for a homogeneous .NET estate. I would not make it for a polyglot one without first checking whether the documentation problem was actually worth an extractor per language.