OpenSpec Hands‑On: 3 Commands, 4 Artifacts, 4‑Step Closed Loop for Incremental Feature Development
This tutorial walks through adding a priority field to an existing todo‑API using OpenSpec’s incremental workflow—proposing the change with /opsx:propose, reviewing the generated Delta Specs, applying the code with /opsx:apply, and archiving the change with /opsx:archive—while illustrating ADDED and MODIFIED specifications, code updates, testing, and best‑practice tips.
In real projects, new requirements often need to be added to an existing code base, which can cause bugs if changes are not carefully scoped. OpenSpec provides a brownfield‑first workflow that lets developers create, review, apply, and archive incremental changes using three slash commands.
You Will Complete
✅ Create an incremental change proposal with /opsx:propose ✅ Understand the difference between ADDED and MODIFIED entries in Delta Specs
✅ Generate code automatically with /opsx:apply ✅ Archive the change and merge Delta Specs into the main specification with
/opsx:archivePrerequisites
Completed the first tutorial (todo‑api project with CRUD)
OpenSpec CLI installed: npm install -g @fission-ai/openspec@latest Claude Code or another OpenSpec‑compatible AI coding assistant
Node.js >= 20.19.0
Step 1: Create an Incremental Change with /opsx:propose
Run the slash command in the project directory: /opsx:propose add-todo-priority The assistant asks for a concise requirement description. A detailed description such as:
Give the Todo API a priority field. The field supports Low / Medium / High, defaults to Medium, and the list endpoint must support filtering by priority. Update the existing Update endpoint to accept priority.This description becomes the source for proposal.md, which in turn drives the generation of four artifacts under openspec/changes/add-todo-priority/: proposal.md – why the change, what it does, scope, risks specs/ – Delta Specs with ADDED and MODIFIED sections design.md – technical decisions (e.g., string enum for priority, default value) tasks.md – granular implementation checklist
Delta Specs Example
## ADDED Requirements
### Requirement: Todo has priority field
Each todo item SHALL have a `priority` field with value `Low`, `Medium`, or `High`. Default: `Medium`.
#### Scenario: Default priority
- **WHEN** a POST request is sent to `/todos` with `{"title": "Buy groceries"}`
- **THEN** the system SHALL create a todo with `priority: "Medium"`
#### Scenario: Explicit priority on create
- **WHEN** a POST request is sent to `/todos` with `{"title": "Fix bug", "priority": "High"}`
- **THEN** the system SHALL create a todo with `priority: "High"`
#### Scenario: Invalid priority value
- **WHEN** a POST request is sent to `/todos` with `{"title": "Test", "priority": "Critical"}`
- **THEN** the system SHALL return HTTP 400
### Requirement: Filter todos by priority
... (omitted for brevity)
## MODIFIED Requirements
### Requirement: Update todo
... (existing scenarios) ...
#### Scenario: Update priority
- **WHEN** a PUT request is sent to `/todos/{id}` with `{"priority": "Low"}`
- **THEN** the system SHALL return HTTP 200 with `priority: "Low"`The key difference from the first‑time CRUD change is that the Delta Specs now contain both ADDED (new priority field, filter) and MODIFIED (extension of existing create/update scenarios) entries.
Step 2: Review the Four Artifacts
proposal.md – answers “why change, what change, impact, risks”. Used by humans and the AI assistant.
specs/ – Delta Specs described above; focus on ADDED vs MODIFIED.
design.md – records decisions such as using a string enum for priority and choosing “Medium” as the default.
tasks.md – a checklist grouped by Model, API, Testing. Example excerpt:
## 1. Model Changes
- [ ] 1.1 Add priority field to Todo typedef in `src/todo.js`
- [ ] 1.2 Add VALID_PRIORITIES constant and `isValidPriority()` function
- [ ] 1.3 Modify `createTodo()` to accept optional priority
## 2. API Changes - Create
- [ ] 2.1 Modify POST `/todos` handler to accept optional `priority`
- [ ] 2.2 Validate priority value, return 400 for invalid values
## 3. API Changes - List
- [ ] 3.1 Modify GET `/todos` handler to filter by `priority`
- [ ] 3.2 Return 400 for invalid filter values
## 4. API Changes - Update
- [ ] 4.1 Modify PUT `/todos/:id` handler to accept `priority`
- [ ] 4.2 Validate and apply priority update
## 5. Testing
- [ ] 5.1‑5.5 Verify priority scenariosStep 3: Apply the Change with /opsx:apply
After confirming the artifacts, run: /opsx:apply The assistant reads tasks.md and implements each step, marking completed items with [x]. Key code changes include:
src/todo.js – Add priority field and validation
const VALID_PRIORITIES = ['Low', 'Medium', 'High'];
function createTodo(title, options = {}) {
return {
id: crypto.randomUUID(),
title,
completed: false,
priority: options.priority || 'Medium',
};
}
function isValidPriority(priority) {
return VALID_PRIORITIES.includes(priority);
}
module.exports = { createTodo, isValidTitle, isValidPriority, VALID_PRIORITIES };src/app.js – Update routes (Express‑style illustration)
// POST /todos — create todo
app.post('/todos', (req, res) => {
const { title, priority } = req.body;
if (!isValidTitle(title)) {
return res.status(400).json({ error: 'title is required and must be non‑empty' });
}
if (priority !== undefined && !isValidPriority(priority)) {
return res.status(400).json({ error: `priority must be one of: ${VALID_PRIORITIES.join(', ')}` });
}
const todo = createTodo(title, { priority });
save(todo);
res.status(201).json(todo);
});
// GET /todos — list with optional filter
app.get('/todos', (req, res) => {
const { priority } = req.query;
if (priority !== undefined && !isValidPriority(priority)) {
return res.status(400).json({ error: `priority must be one of: ${VALID_PRIORITIES.join(', ')}` });
}
const todos = findAll();
if (priority !== undefined) {
return res.json(todos.filter(t => t.priority === priority));
}
res.json(todos);
});
// PUT /todos/:id — update including priority
app.put('/todos/:id', (req, res) => {
if (!exists(req.params.id)) {
return res.status(404).json({ error: 'Todo not found' });
}
const todo = findById(req.params.id);
const { title, completed, priority } = req.body;
if (priority !== undefined && !isValidPriority(priority)) {
return res.status(400).json({ error: `priority must be one of: ${VALID_PRIORITIES.join(', ')}` });
}
if (title !== undefined) todo.title = title;
if (completed !== undefined) todo.completed = Boolean(completed);
if (priority !== undefined) todo.priority = priority;
save(todo);
res.json(todo);
});Running the existing test suite plus the new curl commands verifies the behavior:
# Create with explicit priority
curl -X POST http://localhost:3000/todos -H "Content-Type: application/json" -d '{"title": "Fix bug", "priority": "High"}'
# Create without priority (defaults to Medium)
curl -X POST http://localhost:3000/todos -H "Content-Type: application/json" -d '{"title": "Buy groceries"}'
# Filter by priority
curl "http://localhost:3000/todos?priority=High"
# Invalid priority should return 400
curl -X POST http://localhost:3000/todos -H "Content-Type: application/json" -d '{"title": "Test", "priority": "Critical"}'If all four tests pass, the feature is correctly implemented.
Step 4: Archive the Change with /opsx:archive
/opsx:archiveThe assistant moves the change directory to openspec/changes/archive/YYYY‑MM‑DD‑add-todo-priority/, checks that all tasks are completed, and merges ADDED entries into the main specification while replacing MODIFIED entries.
After archiving, the active openspec/changes/ directory is empty, and the main openspec/specs/ reflects the new priority capability.
Advanced Tips
Delta Specs Operations : ADDED (new feature), MODIFIED (extend existing), REMOVED (delete).
Naming : Use concise action‑object names like add-todo-priority, fix-login-timeout, remove-legacy-auth.
Explore Phase : Run /opsx:explore to discuss vague requirements before proposing.
Context Reset : After propose, start a new AI session for apply to avoid accumulated noise.
Common Questions
Q1: What if a task fails during apply ?
The assistant pauses, offering options to skip, fix manually, or retry. In tasks.md, completed tasks stay marked with [x], and the next apply resumes from the first unfinished task.
Q2: Can I edit Delta Specs manually?
Yes. All artifacts are plain Markdown files; editing them updates what the AI reads on subsequent runs.
Q3: Are the commands different for incremental vs. first‑time changes?
No. The same three commands work; OpenSpec determines ADDED vs. MODIFIED based on the existing specs/ content.
Q4: Must every change be archived?
Archiving promptly keeps openspec/changes/ as an active work queue and improves proposal quality for future changes.
Q5: How to handle conflicting incremental changes?
Complete and archive one change before starting the next, or manually reconcile overlapping tasks in tasks.md.
Conclusion
The tutorial demonstrates the repeatable four‑step loop— propose → apply → archive —that lets developers add or modify features in an existing code base with minimal friction. By focusing on Delta Specs (ADDED, MODIFIED, REMOVED), the workflow isolates the exact change set, enabling quick reviews and reliable AI‑generated code while keeping the project’s source of truth up‑to‑date.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Shuge Unlimited
Formerly "Ops with Skill", now officially upgraded. Fully dedicated to AI, we share both the why (fundamental insights) and the how (practical implementation). From technical operations to breakthrough thinking, we help you understand AI's transformation and master the core abilities needed to shape the future. ShugeX: boundless exploration, skillful execution.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
