OpenSpec v1.6.0 introduced the /opsx:update skill — revising a change’s planning artifacts mid-implementation while keeping proposal/specs/design/tasks coherent. v1.7.0 brought it into the core profile workflow and comprehensively polished the templates. This article walks through a real case — “product search and price sort” — demonstrating the full end-to-end workflow (Explore → Propose → Update → Apply → Sync → Archive).
Prerequisite:
/opsx:updateis part of the core profile. After runningopenspec update, if you see “missing 1 core workflow: update”, runopenspec config profile coreto enable it.
In the v1.5.0 workflow (see The Three Major Changes), Fluid Workflow allows editing documents at any time, but the AI needs to be explicitly told “now we’re revising the plan”, and coherence across artifacts cannot be guaranteed automatically — editing one document may leave others out of sync.
/opsx:update, introduced in v1.6.0 and made part of the default workflow in v1.7.0, fills this gap. Its job is to revise a change’s existing planning artifacts and keep them coherent with one another, and it explicitly never edits code. This changes the way you work: requirements can evolve before and during implementation without worrying about planning documents falling out of sync.
v1.5.0: explore → propose → apply → sync → archive
↑
manual document editing, no coherence guarantee
v1.6.0+: explore → propose → update → apply → sync → archive
↑
standardized revision flow, artifacts stay coherent
In the e-commerce example system (Node.js + Python dual implementation), the product list endpoint GET /api/products only returned the full list. After exploration, the candidate requirements were:
| Candidate | Trade-off |
|---|---|
| Delete product | ❌ Touches stock references, too large a change |
| Pagination | ❌ Requires designing pagination parameters, overly complex |
| Search by name | ✅ Small but valuable (~10 lines of code), and it modifies an existing spec (MODIFIED) — exactly what triggers the update flow |
Created the change and generated 4 artifacts:
catalog-managementBefore apply, the user raised a new requirement: search results should support sorting by price. This is exactly what /opsx:update is for.
Key judgment: ADDED vs MODIFIED: Price sorting is a new concern that doesn’t change the existing search behavior — so ## ADDED Requirements is used rather than MODIFIED. This avoids the common archive pitfall: using MODIFIED with partial content loses existing details in the main spec at archive time.
Coherent revision of all 4 artifacts:
| Artifact | Revision |
|---|---|
| proposal.md | What Changes + Impact add the sort parameter |
| specs/…/spec.md | ADDED “Product List Price Sort” requirement (4 Scenarios: ascending/descending/search+sort combo/invalid value fallback) |
| design.md | New Decision 3 (sort parameter whitelist validation) + Decision 4 (sorting in the service layer) |
| tasks.md | Task signatures updated to list(name, sort), tests cover sort scenarios |
Whitelist design: sort accepts only price_asc/price_desc; invalid values are silently ignored (natural order preserved). Silent ignore rather than a 400 error preserves backward compatibility — old clients passing unknown parameters aren’t broken.
Implemented per tasks; the service layer handles filtering and sorting, the HTTP layer only passes parameters through:
// Node.js - catalog.js
list(name, sort) {
let products = this.repo.findAll()
if (name) {
const keyword = name.toLowerCase()
products = products.filter(p => p.name.toLowerCase().includes(keyword))
}
if (sort === 'price_asc') {
products.sort((a, b) => a.priceCents - b.priceCents)
} else if (sort === 'price_desc') {
products.sort((a, b) => b.priceCents - a.priceCents)
}
return products
}
# Python - catalog.py
def list_products(self, name: Optional[str] = None, sort: Optional[str] = None):
products = self.repo.find_all()
if name:
keyword = name.lower()
products = [p for p in products if keyword in p.name.lower()]
if sort == "price_asc":
products.sort(key=lambda p: p.price_cents)
elif sort == "price_desc":
products.sort(key=lambda p: p.price_cents, reverse=True)
return products
Two test logic bugs found and fixed along the way:
list('a', 'price_desc'), ‘a’ is a fuzzy substring match, and only “A” contains ‘a’ (B and C don’t) — the assertion was corrected to [300]>= 3 + set containment) instead of hardcoded countsTest results: Node.js 10/10, Python 4/4 all passing.
The delta spec and main spec are merged following the intelligent merge principle:
After the merge, the main spec contains no delta headers (## ADDED/MODIFIED) — clean structure: 4 Requirements, 11 Scenarios.
Before archiving, consistency was verified (search Scenario present, sort Requirement present, no delta headers), then:
openspec/changes/archive/2026-07-28-add-product-search/
Compared to manual editing in v1.5.0, the update flow brings three changes:
From v1.5.0 to v1.7.0, OpenSpec filled the weakest link in its workflow. v1.5.0 solved “AI dynamically understands the project” (Schema-driven), while v1.6.0 introduced the update skill — brought into the default workflow in v1.7.0 — solving “planning documents keep evolving”. Together, they make Spec-Driven Development truly fit iterative development — requirements aren’t fixed once, they grow together with the implementation.
Based on the add-product-search practice in the OpenSpec Practise repository (2026-07-28). Full artifacts in openspec/changes/archive/2026-07-28-add-product-search/.