Spec-driven development means writing a structured specification of
what a change should do before any code is generated, so that the AI
implements against that spec rather than improvising from a loose
prompt. This lab uses OpenSpec, a
lightweight framework that adds an openspec/ directory to a
repo containing project descriptions, specifications, and change
proposals, and that works with any AI coding tool.
We will work with a small CLI task manager (cli.py) that
reads and writes tasks.json. Three commands exist already
(add, list, complete), and a
fourth, delete, is stubbed out. The task is to implement
delete with archiving, so that deleted tasks move to
archive.json rather than disappearing.
The interesting part is everything that one-line description leaves open: what should happen when someone deletes a nonexistent ID, what fields the archive entry should contain, whether task IDs get reused after deletion, and whether archiving preserves the original timestamp or adds a new one. Without a spec to pin these decisions down, a model will resolve all of them silently, possibly inconsistently across runs.
Start by installing OpenSpec:
npm i -g @fission-ai/openspec@latestDownload the starter files (cli.py), place them in a
folder, and initialize a git repo along with OpenSpec:
git init && git add -A && git commit -m "initial"
openspec initOnce that is done, open the project in OpenCode and switch to a free
model with the /model command (e.g. glm-4.7,
minimax-2.5).
OpenSpec works through slash commands typed inside OpenCode rather
than from the terminal. The CLI (openspec init,
openspec list) handles project setup, but the actual change
workflow happens via /opsx-propose,
/opsx-apply, and so on.
With the project open in OpenCode, run the propose slash command:
/opsx-propose implement-delete
This creates openspec/changes/implement-delete/
containing proposal.md, design.md,
tasks.md, and a specs/ subfolder with
requirement specs. Read through what the model generated, then open the
spec files under openspec/changes/implement-delete/specs/
to review and edit them. The model will have written some scenarios, but
they are likely vague or incomplete, so tighten them into concrete
Given/When/Then scenarios that pin down the edge cases from the
introduction. For reference, here is what a precise scenario looks like
(this one describes the existing complete command, not
delete):
Given tasks.json contains task with id 2 and status "pending"
When I run `python cli.py complete 2`
Then task 2 in tasks.json has status "complete"
And stdout contains "Completed task 2."
Write scenarios for delete that are equally specific. Think about what happens on a nonexistent ID, what fields the archive entry should have, what the output messages look like, and whether task IDs can be reused. The more precise the scenarios are, the less room the model has to improvise.
Once the spec is complete, apply it inside OpenCode:
/opsx-apply implement-delete
After the model generates the implementation, check it against each scenario and note whether it follows the spec faithfully or still invents behaviour where the spec left room.
A delta spec describes only what changes relative to an existing
spec, using ## ADDED and ## MODIFIED sections
rather than rewriting everything from scratch. This second change
extends the task manager with a --priority flag on the
add command that accepts low,
medium, or high (default medium),
and the list command should then show high-priority tasks
first.
Propose it inside OpenCode:
/opsx-propose add-priority
Then edit the specs in
openspec/changes/add-priority/specs/. This time, structure
the spec as a delta: figure out which parts of the change are entirely
new (## ADDED) and which parts modify existing behaviour
(## MODIFIED), and write scenarios for each. Think about
what happens to tasks that were created before the priority feature
existed.
Once the spec is ready, apply it:
/opsx-apply add-priority