From Spec to Ship: Tracking Implementation Status with GitHub
How to connect your repository, map code to specs, and always know what's built versus what's still planned.
Every team that writes specs runs into the same problem eventually: the spec says one thing, the code says another, and nobody knows which is true. Requirements drift. Features get partially built. Engineers ship something close to the spec but not quite matching it, and the gap goes unnoticed until a PM opens the app and asks, "wait, where's the bulk delete?"
This is the visibility problem. It's not that teams don't care about specs. It's that there's no automated way to know whether the code actually reflects what was planned. Specs live in one tool, code lives in another, and the mapping between them exists only in people's heads.
This article covers how to close that gap by connecting your GitHub repository to your specs and using implementation tracking to get a real-time view of what's built, what's partial, and what hasn't been started.
The Visibility Problem
Consider a typical product spec for a task management feature. It describes five API endpoints, three data models, and a set of UI screens. The engineering team picks it up, works on it for two weeks, and marks the ticket as done.
But "done" is ambiguous. Maybe four of the five endpoints are built. Maybe the data model is missing a field that the spec called for. Maybe the UI exists but doesn't handle the error states described in the acceptance criteria.
Without comparing the spec against the actual code, you're relying on manual checks and human memory. That works for small teams and simple features, but it breaks down fast.
The result is a slow accumulation of gaps between what's planned and what exists. Specs become untrustworthy. Teams stop reading them. And the planning process loses its connection to reality.
Connecting a GitHub Repository
The first step is giving your spec tool access to the codebase. In Intent, this means connecting a GitHub repository to your project.
The connection uses GitHub's OAuth flow. You authorize access, select the repository, and Intent indexes the relevant parts of the codebase: file structure, API route definitions, data model schemas, and key patterns in the code.
Project Settings → Integrations → GitHub → Connect Repository
Once connected, Intent pulls metadata from the repo. It doesn't copy your entire codebase into a database. Instead, it analyzes the structure to understand what exists: which endpoints are defined, which models are created, which routes are registered.
A few things to keep in mind during setup:
- Choose the right branch. Most teams connect their main or production branch, since that represents what's actually shipped. Connecting a development branch can work too, but you'll see in-progress work mixed with completed features.
- Monorepo support. If your repo contains multiple services, you can scope the connection to specific directories. For example, pointing to
apps/apifor backend analysis andapps/webfor frontend analysis. - Private repositories. The OAuth token is scoped to the repositories you explicitly select. Intent doesn't request broad organization access.
How Implementation Tracking Works
Once the repository is connected, Intent can analyze your code against the entities defined in your spec. This isn't a simple text search. It's a structured comparison between what the spec describes and what the code contains.
Here's what the analysis looks at for each type of spec entity:
API Endpoints
For each endpoint in your spec, the analyzer checks whether a matching route exists in the codebase. It looks at:
- The HTTP method and path pattern
- Request and response schemas
- Whether the handler contains meaningful logic or is a stub
For example, if your spec defines:
endpoint:
method: POST
path: /api/v1/projects/{project_id}/tasks
request_body:
- title: string (required)
- description: string (optional)
- assignee_id: uuid (optional)
response: Task object (201 Created)
The analyzer will search for a route matching POST /projects/{id}/tasks, check if the handler accepts the specified fields, and verify that a response model exists.
Data Models
For data models, the comparison checks:
- Whether a model or table with the expected name exists
- Which fields are present and their types
- Relationships to other models (foreign keys, joins)
model:
name: Task
fields:
- id: uuid (primary key)
- title: string
- description: text (nullable)
- status: enum (todo, in_progress, done)
- assignee_id: uuid (foreign key → User)
- created_at: timestamp
If the code has a Task model with id, title, and status but is missing assignee_id, the analysis flags the model as partially implemented.
Logic Flows
Logic flows are harder to analyze automatically since they describe behavior rather than structure. The analyzer looks for evidence of the described flow: the relevant functions, the sequence of operations, and whether key conditions are handled.
This part of the analysis is less precise than endpoint or model checking, so it tends to be more conservative, flagging flows as "partial" when it can confirm some steps exist but can't verify the full sequence.
Reading Implementation Status
After analysis runs, each entity in your spec gets one of three statuses:
Implemented -- The code contains a clear match for the spec entity. The endpoint exists with the right method and path. The model has the expected fields. The core logic is present.
Partial -- Some elements exist but the implementation is incomplete. Maybe the endpoint exists but is missing fields from the spec. Maybe the model is there but a relationship is absent. Partial status means work has started but doesn't fully match the spec.
Not Started -- No matching code was found. The entity exists only in the spec.
These statuses roll up to give you a feature-level view. If a feature has ten spec entities and seven are implemented, two are partial, and one hasn't started, you get a clear picture of where things stand.
Feature: Task Management
├── POST /tasks ✓ Implemented
├── GET /tasks ✓ Implemented
├── GET /tasks/:id ✓ Implemented
├── PUT /tasks/:id ~ Partial (missing assignee update)
├── DELETE /tasks/:id ✗ Not Started
├── Task model ~ Partial (missing assignee_id field)
├── TaskComment model ✓ Implemented
├── Task assignment flow ~ Partial
├── Task filtering logic ✓ Implemented
└── Bulk task operations ✗ Not Started
Overall: 70% complete (4 implemented, 3 partial, 2 not started)
This isn't a vanity metric. It's a working checklist that updates automatically when code changes.
Using Status to Prioritize Work
Implementation status becomes most useful during sprint planning and prioritization. Instead of asking "what's left to build?" and getting vague answers, you can look at concrete data.
A few patterns that work well:
Finish partial items before starting new ones
Partial implementations are debt. They're features that are close to done but not quite shippable. Finishing them is usually faster and higher-value than starting something new.
Sort your spec entities by status. If you have a cluster of partial items in a feature, that's probably a one-day cleanup, not a week-long project.
Use "not started" to scope milestones
When planning a release, filter for entities that are not started. This gives you the honest scope of remaining work. Combine it with priority tags on your spec entities to figure out what's actually needed for the milestone versus what can wait.
Track progress over time
Re-running the analysis after each sprint gives you a velocity metric that's tied to spec completion, not just ticket closure. You might close twenty tickets but only move implementation status by 10%. That's a signal worth paying attention to.
Keeping Specs and Code in Sync
Connecting your repo and running analysis once is helpful. Keeping things in sync over time is where the real value lives.
Update specs when requirements change
This sounds obvious, but most spec drift happens because the spec didn't get updated when the team decided to change the approach during implementation. If you add a field during development that wasn't in the spec, add it to the spec too. The spec should reflect the current intended state, not just the original plan.
Re-run analysis regularly
Set a cadence for re-running implementation analysis. After each sprint is a natural rhythm. Some teams run it before every planning session so they walk in with an accurate picture.
Handle intentional divergence
Sometimes the code intentionally diverges from the spec. Maybe the team decided during implementation that an endpoint wasn't needed, or that a data model should be structured differently. That's fine, but the spec should be updated to reflect the decision.
If the spec says one thing and the code says another, one of them is wrong. Figure out which and fix it.
Use analysis results in code reviews
When reviewing a PR that claims to implement a spec feature, run the analysis to see if the implementation actually covers what the spec describes. This catches gaps that are easy to miss in manual review, like a missing field or an endpoint that handles the happy path but not the error cases.
A Practical Workflow
Here's what the end-to-end workflow looks like for a team using spec-to-code tracking:
- Write the spec. Define the feature with data models, endpoints, and acceptance criteria.
- Connect the repo. One-time setup. Point it at your main branch.
- Build the feature. Work as normal. Commit code, merge PRs.
- Run analysis. After the feature is merged, run implementation tracking.
- Review gaps. Look at partial and not-started items. Decide what needs to be finished before calling the feature complete.
- Update the spec. If anything changed during implementation, update the spec to match.
- Ship with confidence. You know what's built because you checked it against what was planned.
This loop takes a few minutes per feature. The payoff is that your specs stay useful, your team has a shared understanding of what's done, and you stop discovering gaps in production.
What This Doesn't Replace
Implementation tracking isn't a substitute for testing, code review, or QA. It tells you whether the code exists, not whether it works correctly. A fully "implemented" endpoint might still have bugs.
Think of it as a structural check. It answers "did we build what we said we would build?" not "does what we built work properly?" You still need tests for that.
But knowing what's built and what isn't is the foundation for everything else. You can't test what you don't know is missing. You can't prioritize what you can't see. And you can't ship with confidence if your spec and your code are telling different stories.