October 12, 2025·4042 words
I get to finally write a bit about a project I was knee-deep in for the past month - GitHub Spec Kit. It’s a project born out of John Lam’s research in how to help steer the software development process with Large Language Models (LLMs) and make it just a tiny bit more deterministic. After all, we all know that vibe coding into production is just a monumentally shortsighted approach, but what’s the alternative?
All of this thinking and more got encapsulated in an experiment around the concept of Spec-Driven Development (SDD) that we collectively shipped in the open on GitHub, called GitHub Spec Kit.
Why specs #
The first question I usually get around Spec Kit is - “Why bother?” If LLMs are essentially “roll of the dice” kind of technology, can’t you just vibe code a bunch of stuff together and call it a day?
If you’ve used LLMs to vibe code anything, no matter how small, you know that the output can vary wildly. The prompt that you ran can produce a completely different result from the exact same prompt that your friend ran on a different computer that same day. LLMs are built like that - every time you ask it to do things, the little bits of sand in your computer start interacting with electrical signals and some complex math way out in the datacenter somewhere (unless you’re running things locally, of course) to predict the output. That means, by design, that said output will be, shall we say, variable.
That’s not necessarily a bad thing - we came to terms with the fact that this is how the technology works. Yet, within our team we still posed the question - “Can we harness this tech to produce deterministically good software?” We think one potential route there is through specifications. We are actually not alone in this thinking.
For those of you who are not product managers, specifications might mean different things. You also might’ve heard different terms thrown around in the same vein - PRDs, BRDs, CRDs, specs, and other fancy ways Silicon Valley tried everyone to adopt as a way to describe what I can best describe as “detailed descriptions for software”. Lots of oxygen has been spent trying to outline what a good spec is, but it’s always about being very clear about the intent.
The gist of the specification document is simple - it’s an outline of the “what” and the “why” of your product, feature, or bug fix. Think of it as an artifact responsible for outlining the reasoning behind why something needs to be built and what exactly will be built to match customer expectations.
Specifications are also intentionally detached from technical details. A spec document is not the place where you go and define all the inner workings of your technical stack of choice. Your customers couldn’t care less if you wrote the code in C# or Python, or whether you used d3.js or Recharts for your interactive graphs. Unless, of course, you have super-technical customers, in which case - kudos. Most of the time customers care about very concrete solutions and how they will be brought to life through the power of software. That’s the lens you should be looking through when it comes to specifications.
If you focus on specifications and ignore the technical details for a moment, you also unlock a myriad of possibilities that were previously not there if you would keep all of the requirement types in the same “bundle” or right away start with code.
Say, if you decide to write an app in Swift, you could theoretically use a spec you put together for that app and have every little tidbit captured in one massive document. But what if you are curious if the Objective-C version will be more performant (not that it would be, but hey)? Well, if you had one “hybrid” document that intermingles all those details - functional and technical - now you need to untangle the mess to get to the crux of your product requirements. If you have a clean spec that focuses on the “what” and the “why,” you can have an LLM (or multiple) implement independent versions of your app. Then, you can compare the experience for yourself. It’s all built on top of the same set of requirements.
Using LLMs with specs may feel a bit annoying - you see all these random YouTube influencers showing you how they just used Claude Code to vibe code their new SaaS business over the weekend and here I am, asking you to just sit down and write your requirements first before you toss your project into a LLM chat box to implement. There’s a fairly significant upside to this approach - just like in the real world, if you are able to clearly articulate your requirements, you will get better outcomes.
If we look at LLMs as over-eager junior engineers that just can’t wait to go and sling hundreds of lines of code, wouldn’t you say that the crisper the requirements and guidance we give them, the better likelihood we have to get actually decent code? I strongly believe so.
In this arena, specs become the executable artifacts. The code becomes somewhat (not altogether, don’t yell at me just yet) fungible - you still need to make sure that you review it and spot issues, but ultimately it becomes the “compiled specification.” You can quickly iterate on it and even re-create it from the spec with the help of an LLM. As long as you have crisp requirements, of course.
How Spec Kit fits here #
Instead of reading many words in the post below, I would actually recommend you can start with an overview of GitHub Spec Kit that I recorded not that long ago:
GitHub Spec Kit is, at its core, not a product. That’s the fun part about working on this project - it’s an experiment designed to test how well the methodologies behind SDD actually work.
If you look under the hood, Spec Kit has a few fairly basic components:
- A number of pre-determined slash commands (stored in Markdown files):
- /speckit.constitution - define the project constitutional guardrails. These are the non-negotiable principles that must be respected at all times.
- /speckit.specify - build a specification document based on the user prompt.
- /speckit.clarify - ask the user for clarification questions on the spec to help prevent ambiguities.
- /speckit.plan - create a technical plan for the specification.
- /speckit.tasks - break down the technical plan and the spec into a set of individual tasks that a LLM can tackle.
- /speckit.analyze - analyze the spec, plan, and task breakdown for any inconsistencies.
- /speckit.checklist - create “unit tests for English” for your specification, enabling you to quickly find blind spots in your domain thinking (e.g., UX, security, accessibility, etc.).
- /speckit.implement - implement the project based on all of the combined artifacts.
- The specify CLI, that is used to scaffold the project with all the prompts for supported agents.
That’s… it. No, really - GitHub Spec Kit mostly revolves around prompts and a CLI that helps bring in all of the relevant project packages locally.
Let’s start with the CLI, since it’s the first piece of the experience here.

Using the CLI is entirely optional because all it does is download an existing package from the GitHub repository that contains the pre-baked prompts (that will be used as custom slash commands) and some Spec Kit-specific metadata and templates. You can even download and extract them yourself directly from the GitHub Spec Kit repo.

Each package is specifically crafted for your agent and platform of choice - on POSIX-compatible systems you can use shell scripts, and on Windows (or Linux too, really) you can use PowerShell.
Once the project is instantiated, you can effectively just go through the flow-chart of commands:
flowchart TD A["/speckit.constitution"] --> B["/speckit.specify"] B --> C{"Need clarification?"} C -->|"Yes"| D["/speckit.clarify"] D --> B C -->|"No"| E["/speckit.plan"] E --> F{"Check domain coverage?"} F -->|"Yes"| G["/speckit.checklist"] G --> E F -->|"No"| H["/speckit.tasks"] H --> I{"Need consistency check?"} I -->|"Yes"| J["/speckit.analyze"] J --> H I -->|"No"| K["/speckit.implement"] style A fill:#4CAF50 style B fill:#4CAF50 style E fill:#4CAF50 style H fill:#4CAF50 style K fill:#4CAF50 style D fill:#FF9800 style G fill:#FF9800 style J fill:#FF9800
All of the commands are available to you in the agent of choice. I myself use Visual Studio Code with GitHub Copilot, but the same commands and principles are applicable to any agent that you’re using.

Step-by-step #
Let’s go step-by-step through all the commands that we can run here. And to make this more practical, I will actually be building something I wanted to have for some time - a tracker of the Mauna Kea cameras that are maintained by University of Hawai’i.
Setting up the constitution #
The constitution is a set of non-negotiable principles for your project. Think of it as a set of constraints that you want to be univerasally applicable, that the LLM cannot skip out on under any circumstances. To set up the constitution, use the following command and prompt as an example:
I want to build a website that aggregates all the images in a nice, consumable way, without having to go to the website manually every time I want to see the latest status. The constitution serves that purpose - it establishes the foundational requirements that I do not want any complexity (e.g., do not build me SQL databases connected to some remote services) - this is all about local data collection.

Using this command will create a new constitution document inside .specify/memory/constitution that will contain the non-negotiable principles for your project. You can also manually amend it if something stands out that you don’t like.

But do I have to create the the constitution with this command? What if I already have an idea of the constraints, can I just bake them into my own constitution document manually and then have Spec Kit commands refer to it?
I mean, you can also manually write the constitution document too - you don’t need to rely on the LLM to do that work if you have a very clear understanding of what the constraints should be. For ease of use, however, I just recommend keeping it in the same location where Specify CLI put it - that way you don’t need to update any references within the templates.
Building the specification #
With the constitution out of the way, we can now go ahead and actually focus on the specification. As a reminder, a specification in this context is not about technical details but rather about the functional requirements. To get the specification bootstrap, you can use the /speckit.specify command - it will use a built-in template and will populate it based on the prompt that you give it, like this:
The specification prompt looks way more elaborate than the constitution one. Is that intentional? Do I need to provide more context for the spec than I do for other items here?
That’s a very astute observation! I’d say yes - the specification prompt should be as detailed as possible without getting into the weeds of technical implementation. It’s OK if you don’t include everything - specifications are documents that are in flux. We will have an opportunity to refine the requirements as we iterate.
Once you kick off the spec building process, a helper script will be executed (the type will depend on your choice of platform) that will bootstrap a new branch and then the LLM will start populating the specification.

Wait… Hold on… Did you say that you’re creating a “branch”? Are the Spec Kit prompts managing my Git repository here?
As with any new feature and capability added to the product, the best way to iterate on it is in isolation. That is - no matter what changes we make or how we break the code, it will all be contained to the designated branch alone. We’re not very keen on breaking production code with our experiments.
The specification document will contain a few things of interest here, and if you are someone who is deep into the product management world like myself, you will see some familiar terms. Things like user stories, functional requirements, and success criteria.

Clarifying requirements #
This step is optional. You don’t need to run it if you believe your spec is clear or if you clarified things within it yourself.
With the specification now set, we can now also use the same LLM we’ve been using to iterate on the spec to ask for clarifying questions. The biggest problem with specifications in my experience is actually underspecification - it’s easy to miss requirements that you don’t know you don’t know. That’s right - we’re talking about unknown unknowns.
To help with that problem, we’ll use /speckit.clarify to help bring the creative genius out of our amateur product manager endeavor.

To make the clarification process a bit smoother, we baked some logic into the prompt to make sure that you’re given a few potential answers instead of writing everything manually. AI is all about automation and delegation, right?

When the clarification process is done, the specification document will be updated with all the details that you provided. The specification only becomes stronger if we question the initial assumptions.
Creating the technical plan #
We’re now ready to proceed to defining technical details with the help of the /speckit.plan command, which will instruct the LLM to create all the required artifacts that will enable us to actually build a version of the web experience I’ve described.
My planning prompt is as follows:

And with the command executed, we’ll get - you guessed it - more Markdown files. Primarily we need plan.md, but we can also see that there is a data-model.md that outlines, well, our data model, as well as some contracts - such as the schema for the cameras.json file that will store the camera metadata. We will need that to regularly collect image snapshots.

Validating with checklists #
This step is optional. You don’t need to run it if you believe your spec is clear or if you clarified things within it yourself.
An intermediary step that I also found helpful to have when we have the specification and the plan outlined is the use of /speckit.checklist, which allows me to introduce “unit tests for English” - go through the spec and ensure that for a given domain (e.g., UX, security, accessibility, etc.) I’ve specified the right amount of detail. This is yet another guardrail against underspecification, but this time it combines the power of functional and technical requirements.
Let’s add one for UX:
That’s it, that’s the command.

Notice that even when you’re generating a checklist, we’re asking you clarifying questions because we want to make sure that the checklist accurately reflects the intent.

Once the checklist is generated, we now need to make sure that the cross-correlate it with the contents of the spec and the technical plan. You can do this manually, but it will be a fairly tedious process. Or, you can ask the LLM to help you:
It’s worth noting that what can happen sometimes is that the implementation details, such as element sizes or colors, will bleed into the spec. That’s especially common with LLMs that are over-eager, such as Claude Sonnet. When you inspect the changes and see that there is incorrect inclusion of technical details in the functional spec, ask the LLM to remove technical requirements and move them into the plan.
Break things into tasks #
Time to now take all the context we’ve baked into the swath of documents and have a clear set of tasks for the LLM to execute against. This is, in my opinion, one of the most important steps to verify - the tasks are a reflection of what the LLM will build, so we need to ensure that it’s accurate and doesn’t introduce unexpected changes. LLMs, after all, can be very enthusiastic about changing things on the fly.
To generate the tasks, just use:

You will notice that the generated tasks.md file has a breakdown of tasks by user story, and crucially - sequences them in a way that allows us to test the changes before all the tasks are complete.

Pre-implementation analysis #
This step is optional. You don’t need to run it if you believe that the spec, technical plan, and task list are aligned.
The last step before we actually trigger the implementation is encapsulated in /speckit.analyze. This command scans the spec, technical plan, and tasks to make sure that they are coherent and map to the constitution. It’s your last defense line against a potential mismatch in requirements.

When the analysis is complete, you can ask the LLM to apply the relevant fixes - this will help prevent ambiguities or potential requirement conflicts. I’ve, once again, noticed this happen with over-eager models, where a certain requirement might be specified several times in different ways, and the /speckit.analyze command helps avoid the scenario where they conflict or are re-implemented differently.
Implementation #
Before proceeding with the implementation, make sure to check in all existing artifacts. This will dramatically improve your ability to take out the code that doesn’t correctly implement your requirements if the production goes off the rails.
We’re finally at the last step - /speckit.implement. We’ve done the work on the constitution and the spec, we made sure it’s not underspecified, set up a technical plan, broke it down into tasks, and ensured that all of them make sense in the context of the whole project.
Time to type in and wait for the magic to happen:

After a few turns and iteration, I got a working aggregator that collected all of the images in GitHub with the help of GitHub Actions, and of course running entirely statically - it’s a Hugo-based site hosted on Cloudflare.
You can even try it out yourself! It’s not an overly-complex project, but I managed to wire it up and set it up after having it on my “to do” list for a year. Using the spec-driven approach with a coding agent literally let me move a project from an idea to a functional app in less than two hours. Not a bad outcome, considering how much time I’d spend manually scaffolding and implementing CSS and JavaScript logic.
Iteration #
One of the things I mentioned above that you might’ve glanced over is that I called out iteration. As Mitchell Hashimoto observed as well, agents require supervision, and if you let a coding agent run loose without you guiding it to the right destination, you might just end up at the wrong destination.
The spec-based approach allows you to confidently build the scaffolding. This is the first step of the process. Once the scaffolding is established, it’s your job to now refine the output. If it’s not quite what you expected to begin with, you can blow away the code and start with tweaking the spec, plan, and task list and them re-building the implementation.
Another approach here is to have the LLM add Git-based checkpoints after specific phases - it’s not yet baked into Spec Kit from the start, but the more I think about the iterative approach, the more I like leaning into existing developer tools to make this process smoother and more, shall we say, reversible. Go where the developers are.
All in all, however, it’s worth keeping in mind that specs are not a panacea and certainly not yet anywhere near offering the ability to one-shot products or project changes. Human in the loop is as important as it ever was, and what will help you grow moving forward is becoming better and better (and then much better than that) at defining crisp requirements.
LLMs will continue advancing, and so will their ability to process context at scale. But the bottleneck at that point will not be how many files you can reference or whether it correctly pulls constitutional principles for your project but rather how much guesswork is eliminated from encoding the developer intent into the produced output.
Conclusion #
We’re just a bit over a month since Spec Kit came to life, and we’ve plugged quite a few improvements in, but it’s certainly not a done deal. Our team is still experimenting with best ways to integrate with existing tools and developer workflows, as well as lean into more advanced capabilities such as multi-variant implementations (I wonder if Git can help here).
To stay up-to-date with all things Spec Kit, I recommend you star and follow our repository on GitHub. And if you are so inclined, I also have been recording and publishing video updates - they’re the reflection of the latest bits in the repo.