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

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

Grounding is hybrid: each section states the general principle and the industry evidence, then an In practice note shows how mdb-context-hub (and the wider TAM tooling) implements it. Code shapes are illustrative sketches unless attributed to a specific file.


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 600-file 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.

In practice — mdb-context-hub

The repo runs a deterministic sync pipeline (scripts/sync-skill-pack.mjs, invoked as npm run sync:skills) that pulls upstream docs plus repo-local sources, normalizes them, and writes JSON registries and generated Markdown. Every generated output carries the literal banner:

<!-- Generated by scripts/sync-skill-pack.mjs. Do not edit by hand. -->

The boundary is documented as a first-class convention in CLAUDE.md (“Generated files stay generated. If a file already has the sync banner, update the generator instead of editing the output”), and the source roots are environment-configurable (MDB_CASE_ASSISTANT_DOCS_DIR, MDB_TAM_DOCS_DIR) so the generator is portable rather than pinned to one machine.

The staleness gate is real and it is the most instructive part. .github/workflows/ci.yml boots the server, calls buildToolInventory() against the live tool set, and diffs it against the committed docs/tool-inventory.json. If the counts or the tool names disagree, the build fails with “tool-inventory.json is stale.” The doc cannot drift from the code without turning CI red. (Worth noting the limit of the technique: the gate covers exactly what it checks. As of this writing, README.md and docs/ARCHITECTURE.md still read “122 tools across 22 domains” while the gated tool-inventory.json correctly reports 123 across 23 — the prose docs drifted precisely because no gate watched them. A generated-doc strategy is only as good as the set of facts it actually gates.)


2. Documentation as architecture

Principle

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 “new tools must be registered in three files together,” 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.

In practice — mdb-context-hub

CLAUDE.md is not a README; it is an enforced contract. It states invariants like “new tools must be reflected in three places together — service.ts, server.ts, and tests/mcp-server.test.ts,” “use instrumentedRegisterTool (never server.registerTool directly) so every call is logged,” and “keep package.json and mcp-server/src/constants.ts versions aligned.” Each of these is backed by something that breaks on violation: the tool-inventory CI gate catches an unregistered tool, the telemetry wrapper is the only registration path the server exposes, and a version mismatch surfaces in tam_status.

The repo also ships docs/ARCHITECTURE.md (the canonical C4-style diagrams the project briefing reproduces), docs/high_signal_file_index.json (a machine-readable orientation map), and docs/external-calls.md (the audited list of every outbound network call). The last is a good example of doc-as-contract: it is not “here are some calls we make,” it is the authoritative register against which the operations audit runs. Add an outbound call without registering it there and you are out of compliance with a documented standard, not merely under-documented.


3. Indexes to expand LLM coding capacity

Principle

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 600-file 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?”

In practice — mdb-context-hub

The repo is, end to end, an index built so that LLMs can do more than their raw context allows — that is its entire reason to exist. It is worth reading as a worked catalog of index types:

The architectural claim worth stealing: the hub treats “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

Principle

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.

In practice — mdb-context-hub

This is the repo’s strongest pattern. Every tool is registered through instrumentedRegisterTool (from telemetry.ts), and CLAUDE.md makes “never call server.registerTool directly” an enforced invariant — so instrumentation coverage is total by construction. The wrapper records status, duration, redacted arguments, captured console output, and suggested fixes, and persists them to file-analysis/telemetry.jsonl. That history is queryable in-band through tam_get_call_history, tam_get_call_card, and tam_resync_call, and feeds dashboard call-cards.

Two things make this more than logging. First, redaction lives in the middleware, so it is a property of every call rather than a discipline each tool author must observe. Second, the captured “suggested fixes” plus a dedicated error-log domain and the operations registry’s safe/escalate remediation dispositions close a loop: a failed call produces a classified, structured record that downstream tooling (and agents) can act on. The log is not the end of the story; it is the input to the next decision.


5. Testing

Principle

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:

In practice — mdb-context-hub

The suite (eight files under tests/) is split exactly along the “test the core, not the transport” line. tests/mcp-server.test.ts exercises the service layer directly — and it can, because service.ts contains no MCP types (more on why in §6). tests/skill-pack.test.ts covers the sync generator. The scoring and semantic-retrieval logic gets its own dedicated suites (score-core, semantic, semantic-rerank), which matters because retrieval quality is a behavior, not an incidental — if you claim “relevant skills surface first,” that claim needs a test.

The executable-invariant idea shows up in CI rather than the suite: embed:check asserts the committed embeddings match the skill text, and the tool-inventory gate asserts the doc matches the live tools. Neither is a “unit test,” but both are tests in the only sense that counts — a structural claim the build refuses to let you violate.


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.

Principle

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.

In practice — mdb-context-hub and the TAM tooling

The hub is a clean instance of the pattern. service.ts is the pure core — it implements every operation (search, recommend, bundle, optimize, save, analyze) and, by deliberate rule, contains no MCP types at all, so it is testable in isolation (which is exactly what tests/mcp-server.test.ts does). server.ts is the thin registration adapter. index.ts (stdio) and http.ts (loopback HTTP) are two transports over that one server — the same capability, dual-moded, with no second implementation to keep in sync.

The wider TAM tooling extends the same spine to a third and fourth surface: the operations registry (40 registered operations, invoked through tam_ops_run / tam_ops_audit / tam_ops_remediation) is a uniform invocation layer designed so the same operation can be driven from MCP, from an HTTP call, and from a CLI, with the dashboard as the application surface on top. That parity — one operation, many front doors — is the design goal that makes the whole system scriptable in CI, callable by an agent, and clickable by a human without three divergent implementations. The MCP transport is, in effect, the agent’s CLI: the same honest, scriptable surface that makes the core testable is the one that 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.


Companion reading in this repo: docs/ARCHITECTURE.md (the C4 diagrams and ADRs), docs/external-calls.md (the audited outbound-call register), and the project briefing at 05 - MDB Context Hub — Project Briefing.md.