Spec Driven Development: Our way
El desarrollo con IA evoluciona muy rápido y es difícil ver qué cosas van a establecerse y cuáles van a desaparecer o ser absorbidas por otras herramientas, pero parece que Spec-Driven Development (SDD) es una forma de trabajar que ha venido para quedarse. Muchos dirán, y nos incluimos, que esta debía haber sido la forma de trabajar desde el inicio.
Lo curioso es que al igual que muchas otras cosas, como tmux o los git worktrees, SDD no es nuevo, solo que el cambio en la forma de trabajar impulsado por la IA lo ha hecho más popular.
Es más, los orígenes se remontan a los 60 y el Project Mercury de la NASA, con una cultura consolidada de requisitos iniciales muy estrictos, checklist y verificaciones previas al software/hardware críticos para la misión.1
Para los que hayan pasado el último año en una cueva y no lo conozcan, el Spec-Driven Development normalmente sigue 4 pasos:
- Crear las especificaciones
- Planear cómo se van a implementar
- Dividir el trabajo en tareas
- Trabajar en las tareas
En el anterior post mencionamos la SKILL /workflow:create-story como nuestro primer paso para crear tareas. Seguimos probando e iterando, pero de momento, en este blog, vamos a contaros cuál es el estado actual.
El Kanban de toda la vida
Para gestionar las tareas utilizamos GitHub Projects con el típico Kanban.
Las nuevas tareas pueden pasar por 3 columnas diferentes antes de acabar en "In progress":
- Idea: Aquí apuntamos de forma breve las ideas que se nos ocurren para no perder el foco de lo que estamos haciendo.
- Todo: Aquí es donde acaban las ideas después de ser refinadas o tareas que creamos directamente con sus specs en condiciones.
- Next: Aquí simplemente ponemos las que queremos atajar a continuación.

/workflow-idea
Esta skill es muy simple, y la utilizamos cuando queremos apuntar algo rápido pero manteniendo un formato mínimo para que luego sea más fácil de refinar.
/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`
- Normalmente llamamos a la skill directamente, pero a través del trigger Claude Code podría descubrirla y usarla automáticamente.
- En los pasos definimos qué herramientas tiene que usar y cómo, incluyendo ejemplos.
- Incluyendo valores hardcoded para que el agente no tenga que buscarlos cada vez.
- En la "notas" podemos definir pequeños matices en el estilo o cosas a evitar.
Seguimos la especificación de agentskills.io para definir nuestras skills.
Y como veis, utilizamos directamente herramientas de CLI siempre que podemos, suele ser más rápido y gasta menos tokens.
/workflow-create-story
Mientras que la primera skill era para anotar tareas de forma rápida, aquí es donde viene lo interesante.
Esta tarea la utilizamos tanto para refinar las ideas que anotamos como para crear directamente tareas ya refinadas.
Como es más grande, vamos a revisarla por partes:
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.
Estándar frontmatter explicando cuándo debe usarse. Al final esto es útil tanto para los agentes como para los humanos.
El resto de la skill define el proceso de creación de una historia, empezando por recopilar la información:
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
La información puede venir de texto (o audio2), imágenes o enlaces con ejemplos o referencias.
Le pasamos toda la info que nos parece que pueda ser relevante para la tarea, principalmente la que es de fuentes externas, la información sobre el propio proyecto ya la autodescubre correctamente.
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?
El segundo paso es clarificar cuál es el objetivo de la tarea, y continuar profundizando en los detalles.
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")
El "truco del 95%" ayuda a forzar al agente a no asumir nada y continuar preguntando hasta clarificar el objetivo. 3
Step 4: Confirm Understanding
Un resumen para confirmar que no nos dejamos nada, y lo siguiente sería pasar ya a escribir la historia con el formato típico.
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.
- Importante detalle para que los enlaces no dejen de funcionar. 😅
La fase 4 nos la vamos a saltar porque son las instrucciones de la gh CLI para crear la issue, como en la skill de Idea.
También tenemos descrito cómo y cuándo crear relaciones entre las tareas. Así nos aseguramos que use las relaciones nativas de Github y no frases en la descripción.
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
Un ejemplo sencillo del output como referencia:
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)"
Y por último una sección de notas para clarificar y añadir puntualizaciones5 que vamos viendo con el tiempo.
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
Notas finales
Por si os lo estáis preguntando... no, no hemos escrito estas skills a mano desde cero. Las hemos ido generando, refinando y editando a partir de lo que aprendemos en cada ciclo de reflect (y cuando algo no funciona como esperamos 😂). Al final, las skills son código vivo que evoluciona con el proyecto.
Somos conscientes de que estas skills se podrían abstraer para hacerlas más genéricas y reutilizarlas entre proyectos más fácilmente. Pero siendo honestos, de momento lo que hacemos es clonar y adaptar con Claude, y funciona.
También tenemos pendiente explorar otras mejoras que vemos en otros proyectos, como marcar los invariants de la spec y etiquetarlos con un ID4. Por ahora, con los acceptance criteria bien definidos nos es suficiente. Si en algún momento vemos que necesitamos más trazabilidad, lo añadiremos. Pero de momento hemos querido empezar simple e ir añadiendo poco a poco.
Y no solo hemos añadido cosas, en alguna ocasión también hemos hecho alguna revisión para simplificar y reducir.
Saludos, y que la fuerza os acompañe.
-
Cada vez más utilizamos Superwhisper para nuestro workflow. ↩
-
Este truco lo vimos por primera vez en "The 3-Phase Method for Bulletproof Specs When You're Not Starting From Scratch" by Nathan Onn ↩
-
Como por ejemplo este enfoque que etiqueta cada invariant con un ID único para trazabilidad. ↩
-
Esto sigue las propias recomendaciones de Anthropic sobre como ellos crean sus skills. ↩