Spec Driven Development: Our way
AI-assisted development evolves very fast and it's hard to tell which things are here to stay and which will disappear or be absorbed by other tools, but it seems like Spec-Driven Development (SDD) is a way of working that's here to stay. Many will say, and we include ourselves, that this should have been the way of working from the start.
The funny thing is that, like many other things such as tmux or git worktrees, SDD is not new — the shift in how we work driven by AI has simply made it more popular.
In fact, the origins go back to the 60s and NASA's Project Mercury, with a consolidated culture of very strict upfront requirements, checklists, and pre-verifications for mission-critical software/hardware.1
For those who've spent the last year living under a rock and haven't heard of it, Spec-Driven Development typically follows 4 steps:
- Create the specifications
- Plan how they will be implemented
- Break the work into tasks
- Work on the tasks
In the previous post we mentioned the SKILL /workflow:create-story as our first step for creating tasks. We keep testing and iterating, but for now, in this blog, we're going to tell you about the current state of things.
Good Old Kanban
To manage tasks we use GitHub Projects with the classic Kanban board.
New tasks can go through 3 different columns before ending up in "In progress":
- Idea: Here we jot down brief ideas that come to mind so we don't lose focus on what we're doing.
- Todo: This is where ideas end up after being refined, or tasks we create directly with proper specs.
- Next: Here we simply put the ones we want to tackle next.

/workflow-idea
This skill is very simple, and we use it when we want to jot something down quickly while keeping a minimal format so it's easier to refine later.
/workflow-idea
---
name: workflow-idea
description: > # (1)!
Quickly capture a raw idea to the GitHub backlog.
Use when asked to "log idea", "quick idea", "add to backlog", or "jot this down".
metadata:
author: unicrons
version: "1.0.0"
---
# Quick Idea Skill
Capture a raw idea to the backlog with minimal friction. Speed over structure.
## When to Use
- User wants to jot down a quick idea
- User says "log idea", "quick idea", "add to backlog", or "jot this down"
- The input is short and doesn't need a full user story
## When NOT to Use
If the user starts providing detailed requirements, acceptance criteria, or multi-paragraph descriptions, suggest switching to `/workflow-create-story` instead.
## Workflow
### Phase 1: Capture the Idea
1. Accept the raw input (text, image, or link)
2. Only ask for the **component** if it's not obvious from the input (1 question max):
- Components: `repository`, `platforms`, `research`
3. Do NOT ask for role, acceptance criteria, or value proposition
### Phase 2: Create the Issue
Use `gh` CLI to create a minimal issue: # (2)!
```bash
gh issue create --repo unicrons/carbocalc \
--title "TITLE" \
--body "BODY" \
--label "COMPONENT_LABEL"
```
**Title**: Short, descriptive — no user story format needed.
**Body**: Keep it simple:
```markdown
## Idea
[1-3 sentence description of the idea]
## Source
[Optional: link to reference, screenshot description, or context]
```
**No assignment** — ideas are unassigned by default.
### Phase 3: Add to Board
1. **Add to project board**:
```bash
gh project item-add 1 --owner unicrons --url <issue-url>
```
2. **Set status to "Idea"**:
```bash
gh project item-edit \
--project-id PVT_XXXXXXXXXXXXX \ # (3)!
--id <item-id> \
--field-id PVTSSF_XXXXXXXXXXX \
--single-select-option-id 84258558
```
3. **Confirm**: "Added idea **#42**: [Title](url) to backlog."
## Language
**IMPORTANT: All ideas MUST be written in English.** Regardless of the language of the user's input, always translate and write the title and description in English.
## Notes
# (4)!
- Default status is "Idea" — lightweight capture, not a spec
- No assignee, no priority, no acceptance criteria
- If the idea grows into something bigger, refine it later with `/workflow-create-story`
- We usually call the skill directly, but through the trigger Claude Code could discover and use it automatically.
- In the steps we define which tools to use and how, including examples.
- Including hardcoded values so the agent doesn't have to look them up every time.
- In the "notes" we can define small nuances in style or things to avoid.
We follow the agentskills.io specification to define our skills.
And as you can see, we use CLI tools directly whenever we can — it's usually faster and uses fewer tokens.
/workflow-create-story
While the first skill was for jotting down tasks quickly, this is where things get interesting.
We use this skill both for refining the ideas we've noted down and for directly creating already-refined tasks.
Since it's bigger, let's go through it section by section:
Frontmatter
---
name: workflow-create-story
description: >
Create a well-structured GitHub issue/story from an idea (free text, images, files, or href).
Use when asked to "create issue", "new task", or "add story".
metadata:
author: unicrons
version: "2.2.0"
---
# Create Story Skill
Create a well-structured GitHub issue from raw input (free text, images, files, or hrefs) through iterative questioning.
## When to Use
- User has an idea they want to turn into a structured story
- User provides a link/screenshot and wants it converted to a task
- User says "create issue", "new task", or "add story"
For quick, unstructured ideas use `/workflow-idea` instead.
Standard frontmatter explaining when it should be used. In the end, this is useful for both agents and humans.
The rest of the skill defines the story creation process, starting with gathering the information:
Phase 1: Iterative Requirements Gathering
## Workflow
### Phase 1: Iterative Requirements Gathering
#### Step 1: Accept Raw Input
Collect the idea input from the user:
1. **Text input**: Direct description of what they want
2. **Images/files**: Screenshots, designs, or reference files
3. **HREFs**: Links to external resources, GitHub issues, documentation
##### From Text
Extract:
- Goal/objective → WANT
- Target user → ROLE
- Value proposition → SO_THAT
- Requirements → Acceptance Criteria
##### From Images
1. Analyze the image content
2. Describe what it shows
3. Extract requirements or issues visible
4. Ask user to confirm interpretation
##### From HREFs
1. Fetch and analyze the linked content
2. Summarize key points
3. Extract actionable items
4. Create story with link as reference
The information can come from text (or audio2), images, or links with examples or references.
We pass along all the info we think might be relevant for the task — mainly from external sources, since information about the project itself is correctly auto-discovered.
Step 2: Opening Questions
#### Step 2: Opening Questions (Round 1)
Ask the user to clarify the core dimensions:
- **Goal**: What is the main outcome you want?
- **User role**: Who is this for?
- **Component**: Which area does this relate to? (Repository, Platforms, Research)
- **Value**: Why does this matter? What problem does it solve?
The second step is to clarify the objective of the task, and continue digging into the details.
Step 3: Iterative Deepening
#### Step 3: Iterative Deepening (Rounds 2-4)
After each round of answers, assess **confidence** across 5 dimensions:
| Dimension | Question to Self |
|-----------|-----------------|
| Goal clarity | Can I write a single-sentence "I want" statement? |
| Acceptance criteria | Can I list 3+ testable criteria? |
| User persona | Do I know exactly who benefits and how? |
| Technical scope | Can I identify which files/components are affected? |
| Edge cases | Have obvious failure modes been addressed? |
**If overall confidence < 95%**: Ask 1-3 targeted follow-up questions about the weakest dimensions. Keep questions specific, not open-ended.
**Stop iterating when**:
- Confidence reaches 95% across all dimensions
- 4 rounds of questions have been completed
- User signals to move on (e.g., "that's enough", "just create it")
The "95% trick" helps force the agent not to assume anything and keep asking until the objective is clarified. 3
Step 4: Confirm Understanding
A summary to confirm we're not missing anything, and then it's time to move on to writing the story in the typical format.
Phase 2 & 3: Structure & Create
### Phase 2: Structure the Story
Transform raw input into user story format:
**User Story Template**:
```
As a [ROLE]
I want [WANT]
So that [SO_THAT]
```
**Acceptance Criteria**:
- Extract actionable criteria from the input
- Make criteria testable and specific
- Include any referenced requirements
### Phase 3: Create in GitHub
Use `gh` CLI to create the issue:
**Step 1: Create the issue**:
```bash
gh issue create --repo unicrons/carbocalc \
--title "TITLE" \
--body "BODY" \
--label "COMPONENT_LABEL" \
--assignee "GITHUB_USERNAME"
```
**Component labels**: `component:repository`, `component:platforms`, `component:research`
**Issue body** should contain the full user story:
```markdown
## Description
**As a** ROLE
**I want** WANT
**So that** SO_THAT
## UI Hints
[Optional: Free-form text with design notes, screenshot links, mockup references]
## Acceptance Criteria
- [ ] Criterion 1
- [ ] Criterion 2
- [ ] Criterion 3
## References
[Optional: Links to external documentation, examples, or non-GitHub resources. Do NOT list related issues here — use GitHub issue relationships instead (see "Issue Relationships" section)]
```
**IMPORTANT: File references MUST use GitHub permalink URLs, NEVER relative paths.** Relative paths (e.g., `docs/architecture/spec.md`) are broken in GitHub issue bodies — they resolve against the issue URL, not the repo root.
# (1)!
For files in the repo, use blob permalinks with the current commit SHA:
```
https://github.com/unicrons/carbocalc/blob/<commit-sha>/path/to/file.md
```
Get the current SHA with: `git log --oneline -1 --format="%H"`
External URLs (https://...) can be used as-is.
- An important detail so links don't break. 😅
We'll skip Phase 4 since it's the gh CLI instructions for creating the issue, just like in the Idea skill.
We also have documented how and when to create relationships between tasks.
Phase 4: Relationships
### Phase 4: Confirm and Link
[...]
## Issue Relationships
**Do NOT just mention related issues as text in the body.** Use GitHub's native issue relationships via the GraphQL API to create proper parent/child and blocking links.
### Available Relationship Types
| Relationship | Meaning | GraphQL Mutation |
|-------------|---------|-----------------|
| **Parent** | This issue is a sub-issue of another | `addSubIssue` |
| **Blocked by** | This issue is blocked by another | `addBlockedBy` |
| **Blocking** | This issue blocks another | `addBlockedBy` (reversed) |
### How to Get Issue Node IDs
All mutations require node IDs (not issue numbers). Get them with:
```bash
gh api graphql -f query='query { repository(owner: "unicrons", name: "carbocalc") { issue(number: ISSUE_NUMBER) { id } } }' --jq '.data.repository.issue.id'
```
### Adding a Parent (this issue is a sub-issue of PARENT)
```bash
gh api graphql \
-H "GraphQL-Features: sub_issues" \
-f query='mutation { addSubIssue(input: { issueId: "<PARENT_NODE_ID>", subIssueId: "<THIS_ISSUE_NODE_ID>" }) { issue { title } subIssue { title } } }'
```
### Marking as Blocked By
```bash
gh api graphql \
-f query='mutation { addBlockedBy(input: { issueId: "<THIS_ISSUE_NODE_ID>", blockingIssueId: "<BLOCKING_ISSUE_NODE_ID>" }) { issue { title } blockingIssue { title } } }'
```
### Marking as Blocking (this issue blocks ANOTHER)
Use `addBlockedBy` with the arguments reversed — the other issue is blocked by this one:
```bash
gh api graphql \
-f query='mutation { addBlockedBy(input: { issueId: "<OTHER_ISSUE_NODE_ID>", blockingIssueId: "<THIS_ISSUE_NODE_ID>" }) { issue { title } blockingIssue { title } } }'
```
### When to Create Relationships
During **Phase 5 (Refinement)**, when references to other issues are identified:
1. Ask the user what type of relationship it is (parent, blocked by, blocking)
2. Create the relationship via the GraphQL API
3. **Do NOT use** `Refs #`, `Closes #`, `Fixes #`, or `Resolves #` as substitutes for proper relationships
4. Non-issue references (external docs, URLs) still go in the `## References` section of the body
A simple output example:
Example
## Example
**User input**: "We need better error handling when API calls fail"
**Generated story**:
```
Title: Improve API error handling
As a developer
I want clear error messages when API calls fail
So that I can quickly diagnose and fix issues
Acceptance Criteria:
- [ ] Display user-friendly error messages for common failures
- [ ] Log detailed error info for debugging
- [ ] Implement retry logic for transient failures
- [ ] Show loading state during retries
```
**Confirmation message**: "Created issue **#15**: [Improve API error handling](https://github.com/unicrons/carbocalc/issues/15)"
And finally, a notes section to clarify and add remarks5 that we pick up over time.
Notes
## Notes
- New stories are created with status "Idea" and **must** be refined before they're actionable
- After refinement is confirmed, the story is moved to "Todo" automatically
- If invoked on an existing issue (e.g., "refine #226"), skip creation and go straight to Phase 5
- Use GitHub's native issue relationships (parent, blocked by, blocking) via GraphQL API — never substitute with `Refs #`, `Closes #`, etc.
- Non-issue references (external docs, URLs, images) still go in the `## References` section of the body
- The issue number is auto-generated by GitHub
Final Notes
In case you're wondering... no, we didn't write these skills by hand from scratch. We've been generating, refining, and editing them based on what we learn in each reflect cycle (and when something doesn't work as expected 😂). In the end, skills are living code that evolves with the project.
We're aware that these skills could be abstracted to make them more generic and reusable across projects. But being honest, for now what we do is clone and adapt with Claude, and it works.
We also have on our radar to explore other improvements we see in other projects, like marking the invariants of the spec and tagging them with an ID4. For now, well-defined acceptance criteria are enough for us. If at some point we see we need more traceability, we'll add it. But for now we wanted to start simple and add things gradually.
And we haven't only added things, from time to time we've also done reviews to simplify and trim down.
Saludos, and may the force be with you.
-
We increasingly use Superwhisper in our workflow. ↩
-
We first saw this trick in "The 3-Phase Method for Bulletproof Specs When You're Not Starting From Scratch" by Nathan Onn ↩
-
Such as this approach that tags each invariant with a unique ID for traceability. ↩
-
This follows Anthropic's own recommendations on how they create their skills. ↩