Skip to content

Tutorial 2: a change that needs a design

Tutorial 1 skipped Understand almost entirely — the bug was small and unambiguous. This tutorial covers the other case: a change that touches something other code depends on, where getting it wrong is expensive to unwind later. By the end you’ll know exactly which properties of a change trigger design-solution, what it produces, and how that feeds into Build.

You’ve already done Tutorial 1 or know the four-step shape from the Onboarding Guide, and you want to see what changes when a fix isn’t small.

Continuing the descriptor example: product wants a second descriptor computed through the same pipeline — TPSA (topological polar surface area) alongside the existing molecular weight — and other code already calls compute_molecular_weight(mol, exclude_salts) in three places. This isn’t a bug fix; it’s a new shape for an existing function every caller depends on. That’s the signal.

From the Onboarding Guide: Design is conditional depth inside Understand, triggered by real properties of the change — not by size. This change qualifies because:

  • It changes a shared contractcompute_molecular_weight’s signature, called from three other places.
  • Getting the descriptor representation wrong (a bare float? which units for which kind? what happens to a caller that doesn’t know about TPSA yet?) is exactly the kind of decision that’s cheap to get right once and expensive to unwind after three call sites depend on the wrong shape.

A one-line salt-stripping fix didn’t have either property. This one has both.

State the outcome to your assistant – there is no separate entry point to switch to for design work, on any platform (ADR-0044). The same conversation reads the situation and invokes design-solution itself:

We need to compute TPSA alongside the existing molecular weight in the screening
pipeline. Three call sites use compute_molecular_weight today.

The assistant investigates the actual call sites first — not just the function definition — and, because this is a shared-contract change, proposes a brief before touching design: outcome, who’s affected, what’s explicitly out of scope (e.g. “no change to how salts are excluded” if that’s true), and success criteria. For a feature this size, that’s docs/codev/features/descriptor-support/brief.md. Review it, correct anything wrong about the framing, and accept it.

With the brief accepted, design-solution drafts docs/codev/features/descriptor-support/design.md, grounded in the actual current code (not an idealized rewrite). For a change this size, expect it to settle:

  • A unified entry pointcompute_descriptor(mol, kind, exclude_salts=True) returning a Descriptor(kind, value, units) result, instead of one hardcoded function per property.
  • Every call site, named explicitly, with what changes at each one — not “update callers as needed.”
  • What happens at the boundary values — an unknown kind, whether exclude_salts even applies the same way to TPSA as it does to molecular weight. These are exactly the questions worth settling on paper before three call sites each guess differently.
  • Test strategy — one test per call site’s actual usage, not just the new function in isolation.

This is where a real decision belongs to you, not the AI: which representation to use, and whether existing callers get migrated in this change or a follow-up. The assistant proposes an option with trade-offs; you decide. Once you accept the design, it becomes the authority the builder works against — matching Tutorial 1’s rule that later documents link to earlier ones rather than repeating them.

From here it’s the same loop as Tutorial 1 — codev task start, codev git branch, build in bounded rounds, codev task record, codev task check — except the builder now treats the accepted design as authority it doesn’t get to redesign to make coding easier. If the implementation reveals the design missed a case (a fourth call site nobody remembered), that’s a stop condition, not something to quietly patch around: the builder returns BLOCKED with exact evidence, and the design gets corrected before implementation continues.

Risk and coordination decide how much of Understand you need — not the size of the diff. A five-line change to a permission check gets exactly this same design treatment even though the diff is tiny, because the property that matters (a security boundary) is present regardless of line count. A three-hundred-line change that’s purely additive, with no shared contract and no risk property, can skip straight to Build. Check the properties, not the size.

  • Taking the resulting PR through outer-loop review: Tutorial 3
  • Coordinating this kind of change across two developers: Tutorial 4