Spec-Kit Works—If You're Specific Enough
What 12 attempts taught me about AI-driven development with GitHub's spec-kit, and why specificity is the key to making it work.
I’ve used GitHub’s spec-kit about a dozen times now over the past week. The first few attempts were frustrating failures. The recent ones have been genuinely useful. The difference? Specificity—in ways the documentation doesn’t emphasize enough.
Here’s what I learned the hard way.
The Problem I Was Trying to Solve
My team had manually migrated four Angular stores from Akita to NgRx. Four developers, four migrations, four slightly different approaches. The work was solid, but the knowledge was locked in their heads and scattered across PR branches.
We had dozens more stores to migrate. I wanted to capture what my team had learned and turn it into a repeatable process that an AI agent could execute consistently.
Spec-kit’s promise of “spec-driven development” seemed perfect for this. I watched the video, read the docs, and dove in.
Setting Up Spec-Kit
Before diving into my attempts, here’s how you initialize spec-kit. For Copilot in VS Code:
specify init --here --ai copilot
Note: Copilot CLI isn’t supported out of the box. You need to enable it:
copilot --banner --allow-all-tools
The slash commands live in .github/prompts/ and can be launched as an agent with /agent.
For Claude CLI, you can use:
specify init --here --ai claude
Spec-kit adds its files to .specify/ and leaves any existing .github/instructions/ untouched. The agent reads from both—your team standards AND the spec-kit workflow. No conflicts.
Attempt #1: Everything Went Wrong
I started spec-kit from a subdirectory—one level below the project root.
This sent me down a rabbit hole Of problems.
The agent couldn’t see our existing markdown files, our .github/instructions/ with team coding standards, or any of the context that would have made the migration meaningful. It generated generic Akita-to-NgRx boilerplate that ignored our patterns entirely.
Halfway through, I stopped and started over.
Attempt #2: Better, But Still Generic
I moved to the project root and ran spec-kit again. This time the agent could see everything—our codebase, our .github/instructions/, the whole picture.
The output improved. The agent picked up our team’s coding standards and generated code that followed our patterns for testing and file organization. It understood we were using Angular Material, that we had specific Tailwind conventions, that our backend used NUnit with Arrange/Act/Assert comments.
But the Akita-to-NgRx migration itself was still generic. The agent knew how we wrote code, but it didn’t know how we migrated stores. It produced textbook NgRx—technically correct, but missing the nuances our team had worked out over four manual migrations.
Things like: How do we handle the transition period when some components still expect Observables? What’s our naming convention for store files? How do we structure the withComputed block?
The agent was guessing at these decisions instead of following our established patterns.
Attempt #3: Teaching It Our Patterns
I realized the agent needed to see our actual migration work, not just our general coding standards.
I opened Claude CLI and pointed it at our completed migration branches:
I want to build a skill for converting akita to ngrx. We have moved several
components from akita to ngrx but have several more that we want to do.
We have replaced akita in the category component, server details, server,
title request and user device. We need to move the market component over
plus many others.
Can you look at the branches for these completed migrations and build a
skill based on what we have done so far?
Claude examined the before-and-after of each migration. It noticed patterns I hadn’t consciously articulated:
- We always include Observable versions alongside Signals for backward compatibility
- Our stores use
withTreeShakableDevToolswith a specific naming convention - We structure
withComputedblocks with sorted getters first, then boolean flags - Services get refactored to HTTP-only wrappers, with state management moved entirely to the store
The skill it generated wasn’t generic Akita-to-NgRx advice. It was our migration playbook, extracted from real work.
This changed everything—but I still needed to wire it into spec-kit correctly.
Attempt #4: Putting It All Together
Now I had all the pieces: running from the project root, our team standards in .github/instructions/, and a custom migration skill built from real work. Time to wire it all together.
I ran through the spec-kit workflow: constitution, specify, plan, tasks, implement. Each step built on the previous one, and this time the agent had everything it needed.
The results were dramatically better. But it took a few more iterations to nail down the exact prompts that worked consistently.
What Finally Worked
Here’s my actual workflow with the prompts that produced good results:
Constitution
/speckit.constitution
Fill the constitution with the bare minimum requirements for the current
application that is already present in the folder. It consists of a API
in dotnet, ui in angular using ngrx, a custom cli for running jobs in
Kubernetes using cron jobs and a service that runs processes in a back
to back methodology similar to cron but waits for the previous task to
complete before scheduling it.
Notice I didn’t write aspirational principles. I described what already exists. The result was a constitution with five concrete principles that matched our actual codebase:
### I. Layered Architecture
All code MUST follow the established layered architecture pattern:
- API Layer: ASP.NET Core REST controllers handle HTTP concerns only
- Service Layer: Business logic resides in services
- Repository Layer: Data access through Entity Framework Core
- Validator Layer: Input validation uses FluentValidation
### II. State Management Patterns
- Backend: Stateless API design
- Frontend: NgRx Signal Store for Angular state management
- CLI: Stateless command execution
### III. Testing Discipline
All production code MUST have corresponding tests:
- Backend: NUnit tests with Arrange/Act/Assert comments
- Frontend: Jasmine/Karma tests; mock services with moq.ts
These principles aren’t decoration—they become a gate that the plan must pass before implementation begins.
Specify
/speckit.specify
Migrate market akita store to ngrx
Short. Clear. Specific about which store.
What surprised me was the output: the spec focused on user value, not implementation details. It produced user stories like:
### User Story 1 - Admin Manages Market Assignments (Priority: P1)
As an administrator, I need to view and manage market assignments
for users and category templates so that I can control which markets
users have access to.
**Acceptance Scenarios**:
1. Given an administrator is on the user profile page,
When they view the markets section,
Then they see a list of all available markets with checkboxes
indicating which markets are assigned to the user
The spec also captured edge cases I hadn’t explicitly mentioned:
### Edge Cases
- What happens when the market list API call fails?
The system should display an error state and allow retry.
- What happens when a market is deleted while a user has it assigned?
The UI should gracefully handle orphaned assignments.
Plan
/speckit.plan
Create a plan to migrate the market akita store to ngrx. Make sure you
use the speckit agent for akita migrate.
That last sentence matters. I explicitly told it to use the custom migration skill we created. Don’t assume the agent will find and use your custom files—point it there.
The plan phase did something I didn’t expect: it generated a research document that analyzed our existing code before designing the solution.
## Decision: Follow TitleRequestDataStore Pattern
**Rationale**: TitleRequestDataStore is the cleanest, most recent
example in the codebase and matches the Market store's complexity level.
**Alternatives Considered**:
1. ProductStore pattern (keyed caching) - Rejected: Market data is global
2. CategoryStore pattern (complex computed) - Rejected: Simpler relationships
3. CurrentPortalStore pattern (full hooks) - Rejected: Over-engineered
The agent found an existing pattern in our codebase rather than inventing something new. This is exactly what a human developer would do.
The plan also ran a constitution check—verifying that the proposed architecture passed all five of our principles:
## Constitution Check Gate
| Principle | Status | Notes |
|-----------|--------|-------|
| Layered Architecture | PASS | Service handles HTTP, store manages state |
| State Management Patterns | PASS | NgRx Signal Store per standards |
| Testing Discipline | PASS | Jasmine/moq.ts with AAA pattern |
| Code Generation Consistency | N/A | No T4 code affected |
| Observability | PASS | NGXLogger integration maintained |
Tasks
/speckit.tasks
Let's create the tasks for this first migration for market to ngrx from akita
The task output was surprisingly structured. It generated 24 tasks across 6 phases, with explicit dependency tracking:
## Phase 2: Foundational (Blocking Prerequisites)
**CRITICAL**: No user story work can begin until this phase is complete
- [ ] T004 Create new `MarketStore` NgRx Signal Store with state interface
(isLoading, marketsAreLoaded, error, _markets), withTreeShakableDevTools,
withComputed (markets sorted, hasError), withMethods (_loadMarkets rxMethod,
ensureStoreIsPopulated)
- [ ] T005 Refactor `MarketService` to HTTP-only wrapper - remove Akita imports
- [ ] T006 Create unit tests for `MarketStore`
- [ ] T007 [P] Update unit tests for `MarketService`
The [P] marker means “parallelizable”—tasks that can run concurrently because they touch different files. Tasks without that marker must run sequentially.
Each user story phase could be worked independently after the foundational phase:
## Phase 3: User Story 1 - Admin Manages Market Assignments (P1) MVP
**Goal**: Enable administrators to view and manage market assignments
- [ ] T008 [US1] Update `user-profile-markets.component.ts`
- [ ] T009 [US1] Update unit tests for `user-profile-markets.component.spec.ts`
Implement
/speckit.implement
The implementation followed the task breakdown exactly. The final migration touched 22 files with 604 insertions and 319 deletions.
Here’s what the actual NgRx Signal Store looked like—generated based on our team’s patterns:
export const MarketStore = signalStore(
{ providedIn: 'root' },
withTreeShakableDevTools('market'),
withState(initialState),
withComputed((store) => ({
markets: computed(() => {
const markets = store._markets();
return [...markets].sort((a, b) =>
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
);
}),
hasError: computed(() => !!store.error()),
})),
// Observable versions for backward compatibility during migration
withProps((store) => ({
markets$: toObservable(store.markets).pipe(debounceTime(0)),
hasError$: toObservable(store.hasError).pipe(debounceTime(0)),
isLoading$: toObservable(store.isLoading).pipe(debounceTime(0)),
})),
withMethods((store, ...) => ({
ensureStoreIsPopulated(): void {
if (!store.marketsAreLoaded()) {
store._loadMarkets();
}
},
})),
);
Notice the withProps section—the agent knew to include Observable versions alongside Signals for backward compatibility. This came from analyzing our previous migrations, not from a generic tutorial.
Claude vs Copilot: Similar Results
I ran this workflow with both Claude CLI and Copilot in VS Code, and use the same base model for both, Opus 4.5.
Honestly? The base changes were about the same between the two. Both followed the spec-kit workflow correctly. Both picked up our custom migration skill and team standards.
I slightly preferred Claude, but it’s not a dramatic difference. Pick the one that fits your environment.
The Core Lesson: Specificity Is Everything
Looking back at my dozen attempts, every failure came down to a lack of specificity:
| What I Did Wrong | What Worked Instead |
|---|---|
| Started in a subdirectory | Started at project root |
| Generic prompts | Specific prompts naming exact stores and skills |
| Assumed the agent would find context | Pointed it to custom skills explicitly |
| Empty or aspirational constitution | Constitution describing what actually exists |
| No custom skill | Skill built from analyzing real completed work |
Spec-kit isn’t magic. It’s a structured workflow that helps AI agents build software systematically. But the agent can only be as specific as the context you provide.
Front-load the specificity. Describe your existing codebase. Build skills from real examples. Point the agent to exactly what it needs.
What the Docs Don’t Tell You
A few things I wish I’d known on day one:
-
Location matters. Always run spec-kit from your project root.
-
Your existing
.github/instructions/files are an asset. Spec-kit won’t overwrite them, and the agent will use both your standards and the spec-kit workflow together. -
Build custom skills from real work. If your team has already done the task manually, have an AI analyze that work and generate a skill. This is far more valuable than generic instructions.
-
Be explicit in your prompts. Don’t say “migrate to ngrx.” Say “migrate the market akita store to ngrx using the speckit agent for akita migrate.”
-
Copilot CLI needs extra setup. Run
copilot --banner --allow-all-toolsto enable the slash commands. -
The research phase matters. Spec-kit generates a research document that analyzes your existing code before designing a solution. It found patterns in our codebase (like
TitleRequestDataStore) that I had forgotten about. -
Constitution gates are real. The plan phase verifies your proposed design against your constitutional principles. If something violates your architecture, you’ll know before implementation.
-
Backward compatibility is built in. For migrations like ours, the agent knew to provide both Signal and Observable interfaces so components could migrate incrementally.
The Numbers
For the market store migration:
- 24 tasks across 6 phases
- 22 files changed
- 604 insertions, 319 deletions
- 5 consuming components updated
- Zero test regressions
The migration followed the same pattern we had established manually—but now it was documented, repeatable, and executable by an agent.
Was It Worth It?
Yes.
After the learning curve, spec-kit gave us a repeatable process for our remaining Akita-to-NgRx migrations. The custom skill captured our team’s patterns. The structured workflow (constitution → specify → plan → tasks → implement) kept the agent focused.
It took several attempts to get here. But now that I understand how to feed spec-kit the right context, it’s become a genuinely useful tool for repetitive, pattern-based work.
The trick is being specific enough.
Have questions about spec-kit or my setup? Feel free to reach out.