stonewall.dev
Back to Blog
code-to-spec engineering

Why Your Specs Don't Know About Your Code

Stonewall · · 5 min read

Every product team has lived this moment: a PM writes a spec, engineering reads it, and within fifteen minutes someone says, "This doesn't account for how our system actually works."

The spec described a "simple" user invitation flow. The codebase has a permission system with role-based access control, a multi-tenant organization model, three different email providers with fallback logic, and an invitation table with twelve foreign key relationships. The spec mentioned none of this. It assumed a world where inviting a user means inserting a row into a table and sending an email.

This is the spec-code gap. And it's the single largest source of friction in product development.

The Gap Is Structural, Not Personal

PMs don't ignore code complexity on purpose. The problem is structural: the tools we use to write specs have zero awareness of the codebase they're describing changes to.

Google Docs doesn't know your schema. Notion doesn't know your API routes. Linear doesn't know that the "simple" feature a PM described requires modifying a service that seven other modules depend on.

When a PM writes "add a field to the user profile," they're describing intent. When an engineer reads that spec, they're mentally translating it into: which entity, which migration, which DTOs need updating, which API endpoints return this data, which frontend components render it, and which tests need to change.

That translation happens in the engineer's head. Every single time. For every single spec.

Three Ways the Gap Kills Velocity

1. Specs That Are Impossible as Written

A real example: a product spec called for "adding a status field to projects with values: active, archived, completed." Reasonable on paper.

The codebase already had a project_status enum used in three different contexts, a status history table tracking every transition with timestamps and actor IDs, and a state machine plugin that enforced valid transitions. The spec's "simple enum field" would have conflicted with an existing system that took two months to build.

The engineer caught this in sprint planning. But the spec had already been reviewed, approved, and estimated. Two weeks of planning work, invalidated by a five-minute code review.

2. Estimates That Are Fiction

When specs don't reference the actual codebase, estimation becomes guesswork. A PM asks "how long to add search to the dashboard?" and gets a range of 2-8 weeks from the same team, because each engineer is making different assumptions about what "search" means given the current architecture.

Does the app already have a search service? Is there an existing index? Does the current query pattern support full-text search, or would this require a new infrastructure dependency like Elasticsearch? The spec doesn't say, because the spec doesn't know.

3. Engineers Who Stop Reading Specs

This is the most damaging outcome. After enough specs that don't reflect reality, engineers learn to treat specs as "rough vibes" rather than actionable plans. They skim the intent, ignore the details, and build what they think is right based on their understanding of the code.

Sometimes they're right. Often they're not — because they're missing the product context that the PM had but failed to connect to the technical reality.

The result: PMs feel ignored, engineers feel micromanaged, and the product drifts from the plan in ways nobody tracks.

What a Code-Aware Spec Looks Like

Consider the difference between these two spec excerpts for the same feature:

Traditional spec:

"Allow users to archive projects. Archived projects should not appear in the default project list but should be accessible via a filter."

Code-aware spec:

"Add an archived_at timestamp column to the projects table (entity: src/projects/entities/project.entity.ts). The existing findAll() method in ProjectRepository already filters by deletedAt IS NULL via the TypeormBaseRepository base class — add an analogous default filter for archivedAt. The GET /projects endpoint currently returns PaginatedResponse<ProjectResponseDto> — extend the query params to accept include_archived: boolean. Frontend: the ProjectList component at src/components/projects/ProjectList.tsx uses useProjects() hook — add a toggle to the existing filter bar."

The second spec isn't longer because it's verbose. It's longer because it's accurate. An engineer reading it knows exactly which files to touch, which patterns to follow, and which existing abstractions to use. No guesswork. No "what did the PM actually mean?" conversations.

The Fix: Make Specs Read Code

The spec-code gap exists because spec-writing tools don't have access to codebases. That's the root cause. Everything else — the bad estimates, the ignored specs, the planning waste — is downstream.

Stonewall closes this gap by reading your codebase before generating specs. When you describe a feature, Stonewall knows your schema, your API routes, your component tree, and your existing patterns. It doesn't guess what "add a field" means — it tells you which entity to modify, which migration to write, and which endpoints will need updating.

Specs should describe changes to a system. To do that, they need to know what the system looks like. That's not a nice-to-have. It's the baseline for specs that actually work.

Related Posts