Legible by Construction — Automated Docs, Indexes, Logging, and Test-Centric Design

Published

Project article; see sources and editorial standards.

A technical review for practitioners. As of 2026-06-17.

Each section states the general principle and the industry evidence behind it. Code shapes are illustrative sketches.


0. Thesis

For most of software’s history, four classes of artifact were treated as secondary to “the real code”: documentation, indexes, logs, and tests. You shipped the feature; you wrote the docs if there was time, added logging when something broke, and backfilled tests under duress. That ordering was always a mistake, but you could survive it because the only consumer of the system was a human who could read the source.

That assumption no longer holds. A large language model now reads, writes, and operates code alongside the human, and it does so under a hard constraint the human does not share: a bounded context window. An agent cannot hold a large repository in its head. It sees what you index for it, reasons over what you document for it, recovers from what you log for it, and is trusted only as far as what you test for it. The secondary artifacts have become the primary interface.

This review argues one position across six topics:

Documentation, indexes, logs, and tests are load-bearing architecture. Their job is to make a system legible (understandable without reading all of the source) and verifiable by construction (correct because of how it is built, not because someone remembered to check). Dual-moding a single core across CLI, API, and application surfaces is the structural keystone that makes legibility and verifiability cheap instead of aspirational.

The six core sections build on each other: automated docs, then docs as the contract, then the index (the agent’s view of the docs), then logs (the runtime feedback signal), then tests, then the design pattern that ties testing to dual-moding. Section 7 is the honest bill of costs; Section 8 ties the six together.


1. Automated documentation

Principle

Treat documentation as a build artifact, not a hand-maintained sibling of the code. Anything derivable from a source of truth — an API surface, a CLI’s --help, a config schema, a dependency table, an inventory of capabilities — should be generated from that source on every change, never transcribed by hand. Hand-transcription has a half-life: the moment a human copies a fact from code into prose, the two begin to diverge, and the divergence is invisible until a reader is burned by it.

The discipline that makes this work is a hard generated-vs-handwritten boundary. Every file is unambiguously one or the other. Generated files carry a banner that forbids hand-editing and name the generator. Handwritten files never contain a fact that a generated file could own. The two never mix in the same file, because a file that is “mostly generated but with a few manual tweaks” is a file that will be silently clobbered or silently stale, and you won’t know which.

The industry has converged here over two decades: OpenAPI/Swagger generating client SDKs and reference docs from a spec, TypeDoc/JSDoc/Sphinx autodoc lifting docs out of type signatures, the “docs as code” movement (treat docs like source: version them, review them, build them in CI). Diátaxis (Daniele Procida) adds the orthogonal insight that type matters — tutorials, how-tos, reference, and explanation have different jobs and should not be blended — which tells you what to generate versus what to write by hand: reference is generable, explanation rarely is.

The failure mode that matters: staleness

Automated docs do not fail by being wrong on day one. They fail by drifting. The single most valuable thing you can build is not the generator — it is the staleness gate: a CI check that fails the build when a generated doc no longer matches its source. Without that gate, “generated” is a comforting label on a file that quietly rots.

A gate is also only as good as its coverage: it guards exactly the facts it checks, and a prose document that no gate watches can drift while the gated artifact stays correct.


2. Documentation as architecture

The previous section made docs track the code. This section inverts the arrow: the best documentation constrains the code. It is not a description of the system written after the fact — it is the contract the system is built to satisfy, and it is enforced.

Three established forms of this:

What ties these together, and what is new, is the agent-facing spec. Files like CLAUDE.md, AGENTS.md, and .cursorrules are documentation that an LLM reads as operating instructions. When such a file says “every new tool must be registered in all the places that have to agree,” that sentence is not describing the system — it is a constraint on every future change, read and obeyed by both humans and agents. Documentation has become an interface with enforcement semantics.

The litmus test for whether a doc is architecture: if you change the code in a way that violates the doc, does something break? If yes, the doc is load-bearing. If no, it is commentary — useful, perhaps, but not architecture.


3. Indexes to expand LLM coding capacity

This is the section where the agent’s constraint becomes the architecture’s problem. An LLM coding agent is bounded by its context window and degrades as that window fills — the “context rot” effect where relevant facts get buried in irrelevant ones. The entire discipline of agentic coding is, at bottom, the management of what enters that window and when. An index is the data structure that answers “what is the relevant slice of this large repo for the task in front of me right now?”

The field has produced several concrete approaches, and the tension between them is the interesting part:

The debate (precomputed retrieval vs. agentic grep) is not settled and probably shouldn’t be — they trade off freshness against latency, and infrastructure cost against per-query cost. The practitioner takeaway is not “pick the winner.” It is: decide, explicitly, what your agent’s index is, how it stays fresh, and what happens when it’s stale — because there is always an index, even if it’s just “the agent greps.” An unmanaged index is the one that hurts you.

A second, underrated point: indexes are not only for retrieving source. An index of capabilities — the tools, operations, and commands a system exposes — is what lets an agent act, not just read. A tool inventory is a retrieval target exactly like a repo map; it answers “what can I invoke?” instead of “what is defined?”

The architectural claim worth stealing: treat the context an agent needs as a first-class, file-backed, version-controlled, freshness-gated asset — the same way a database treats an index. Retrieval quality is a property you build and test, not a prompt you hope works.


4. Automated logging

Logging earns the word “automated” only when it is a structural property of the system, not a habit you rely on developers to remember. The two design commitments that get you there:

Instrument at the boundary, not in the body. Cross-cutting concerns — timing, status, argument capture, error classification — belong in a single wrapper that every operation passes through, not sprinkled by hand into each handler. A decorator/middleware that wraps every registered operation guarantees coverage: a new operation is instrumented because it cannot be registered any other way. Hand-placed log.info() calls guarantee the opposite — the one path nobody instrumented is the one that pages you at 3 a.m.

Logs are structured data, not prose. Emit events with fields (operation, status, duration, correlation id), not human sentences. This is the “canonical log line” / wide-event discipline (Brandur; Honeycomb’s observability model, Charity Majors) — one structured event per unit of work, queryable after the fact. Prose logs are write-only; structured logs are a dataset. And redaction is a default, not a step: the wrapper strips secrets and PII before anything hits disk, because a log pipeline that depends on every caller remembering not to log a token will eventually log a token.

The new dimension is that logs are now a feedback signal for two consumers. A human reads them to debug. An agent reads them to self-correct — a failed tool call whose structured error says “connection refused, retryable” is a recovery instruction, not just a record. Designing logs as data with explicit failure classification turns your observability layer into the substrate for automated remediation.


5. Testing

The reflexive image of testing is the test pyramid (Mike Cohn): many unit tests, fewer integration tests, fewest end-to-end. The more useful modern framing is Kent C. Dodds’s testing trophy (and Spotify’s honeycomb): weight toward the integration band, because tests that exercise real collaborations catch the bugs that matter while staying fast enough to run constantly. The shape matters less than the principle underneath both: test where the behavior lives, and test it through a stable interface.

That second clause is the one teams get wrong. Tests coupled to implementation (private methods, internal call order) break on every refactor and train the team to distrust the suite. Tests coupled to a stable contract survive refactors and actually enable them. So:


6. Test-centric design via dual-moding (CLI, API, application)

This is the keystone. Everything above gets cheaper — or stays expensive — depending on one structural decision.

Build the system as a single pure core surrounded by thin adapters. This is Ports and Adapters / Hexagonal Architecture (Alistair Cockburn, 2005), the same idea at the heart of Clean Architecture: the domain logic depends on nothing outward; the outward surfaces (a CLI, an HTTP API, an RPC transport, a GUI) depend inward on the core through well-defined ports. The core has no idea whether it was invoked from a terminal, a socket, or a button.

The payoff is usually sold as flexibility. The deeper payoff is testability and anti-drift:

Then comes the move that makes this test-centric: the CLI is the most honest test harness you have. A core that is fully exercisable from the command line is a core you can script, diff, fuzz, and drive in CI without any test-framework ceremony. kubectl is a thin CLI over the same API the Kubernetes dashboard uses; the Stripe CLI drives the same API as the SDKs. The Unix tradition of composable programs over text streams was test-centric design before the term existed, because a program you can pipe is a program you can assert against. If a capability is awkward to invoke from a CLI, that awkwardness is a design smell in the core, surfaced early and cheaply.

The ordering inverts the usual one. Instead of “build the app, bolt on an API, maybe add a CLI,” you build the core, expose it through the cheapest-to-test surface (the CLI) first, and treat the API and app as additional adapters. Testability stops being something you add and becomes the shape of the thing.

                 ┌─────────────────────────┐
   CLI  ───────► │                         │
   HTTP API ───► │   pure core (domain)    │ ◄─── unit tests call here directly,
   MCP/RPC ────► │   no I/O, no transport  │      no transport to boot
   App/UI  ────► │                         │
                 └─────────────────────────┘
   each adapter: parse → call core → format.  thin, contract-tested, can't drift.

For a system that agents operate, an MCP server over the same core is, in effect, the agent’s CLI: the same honest, scriptable surface that makes the core testable also makes it agent-operable.


7. Costs and anti-patterns

A practitioner review that only sells the upside is marketing. The honest bill:


8. Synthesis

The six topics are not six best practices. They are one architecture seen from six angles.

A system built this way generates its reference docs and gates their freshness (1); treats its key docs as enforced contracts rather than commentary (2); maintains explicit, fresh indexes so a bounded-context agent can find what it needs (3); instruments every operation at the boundary and emits structured, classified, redacted events that humans and agents both act on (4); tests its core through stable contracts and promotes structural claims to executable CI invariants (5); and achieves all of the above cheaply because it is one pure core behind thin, dual-moded adapters (6).

The unifying property is the one in the title. Such a system is legible: a human or an agent can understand it without reading all of it, because the docs, the index, and the logs are designed to be read. And it is verifiable by construction: its correctness comes from structure (the boundary, the gate, the contract, the single core) rather than from anyone’s vigilance.

That distinction is the whole argument. Vigilance does not scale, does not survive turnover, and emphatically does not survive an agent that has never met your team’s unwritten rules. Structure does. As more of our code is read and written by systems that cannot hold the whole repository in their heads, the artifacts that make a system legible and self-verifying stop being good hygiene and become the architecture itself.