OpenSpec-practise

Focusing Delta Review on What Actually Changed: OpenSpec v1.11.0 Workflow Practice

OpenSpec v1.10.0 and v1.11.0 shipped in quick succession (August 2026) — both incremental, no breaking changes. The centerpiece of v1.11.0 is openspec show --diff, which answers the most practical pain point in SDD practice: a MODIFIED requirement must restate every scenario it keeps, so reviewing a delta means reading mostly text identical to the main spec. This article walks the full post-upgrade workflow (Explore → Propose → Apply → Archive) through a real change — “cart query and item removal” (cart-query-and-remove) — and records the changes in both releases that affect how we practice.

1. Context: Three Friction Points Left by the v1.9.0 Era

Before this upgrade (v1.9.0), three rough edges in our workflow were each addressed by v1.10/v1.11:

Friction 1: MODIFIED reviews are verbose
  A modified requirement restates every kept scenario -> >90% of a review
  is unchanged text. Real changes drown in restatement.

Friction 2: explore's write boundary is fuzzy
  Is "create the proposal file" thinking or implementing? No explicit
  confirmation point before explore writes. Answering a clarifying
  question can read as consent to write.

Friction 3: status checks are per-change
  With several active changes you run status one by one — awkward for
  dashboards and CI.

2. What Changed in v1.10.0 and v1.11.0 (Practice Perspective)

v1.10.0: A Small Iteration

v1.11.0: A Review-Experience Upgrade

3. The Practice Change: cart-query-and-remove

3.1 Explore: Taking Stock of Spec–Code Inversion

Exploring the dual implementations (Node.js + Python) against the specs surfaced a two-way gap in cart capabilities:

Capability Spec declares Node.js Python
Add item Yes (3 scenarios) ✅ ✅
Remove item Yes (1 scenario) ❌ no code ❌ no code
View cart No requirement service only, no route service only, no route

“Remove” is under-implemented (the spec says it, code doesn’t have it); “query” is inverted (code exists, but neither spec nor HTTP layer exposes it). This change closes the loop — add → view → remove → order — and naturally produces both ADDED and MODIFIED deltas, exercising both renderings of show --diff.

3.2 Propose: Four Artifacts

openspec new change "cart-query-and-remove"

Schema-driven generation produced four artifacts:

Validate caught an editing trap: renaming the scenario “移除购物车商品” to “移除购物车中的商品” inside MODIFIED failed validation — a MODIFIED block replaces the whole requirement, and archive refuses to silently drop a scenario the main spec still has. There is no scenario-level RENAMED mechanism (RENAMED exists only at the requirement level); the fix is to keep the scenario name and change only its content:

✗ [ERROR] MODIFIED "购物车商品移除" omits scenario(s) the current spec still has:
  "移除购物车商品". Copy them into the MODIFIED block ...
-> restoring the original scenario name passes validation

3.3 Review: The Two Renderings of show –diff

Reviewing the delta takes a single command:

openspec show cart-query-and-remove --diff

The key experience: ADDED “购物车查询” renders as full text (a new requirement has no baseline to diff against); MODIFIED “购物车商品移除” renders only the lines that actually changed — the old 15-line requirement was replaced wholesale by 30 lines, yet the diff shows just: the reworded description and Rationale, the refined When/Then clauses, and the 2 new scenarios. The review surface shrinks from “reading 45 lines of restatement” to “looking at 15 lines of change.”

3.4 Apply: Dual-Language Implementation

Service-layer implementations stay isomorphic:

// Node.js - cart.js
removeItem(userId, productId) {
  const cart = this.getCart(userId)
  const index = cart.items.findIndex(i => i.productId === productId)
  if (index === -1) throw new Error('CART_ITEM_NOT_FOUND')
  cart.items.splice(index, 1)
  this.cartRepo.save(cart)
  return cart
}
# Python - cart.py
def remove_item(self, user_id: str, product_id: str) -> Cart:
    cart = self.get_cart(user_id)
    item = next((i for i in cart.items if i.product_id == product_id), None)
    if not item:
        raise ValueError("CART_ITEM_NOT_FOUND")
    cart.items.remove(item)
    self.repo.save(user_id, cart)
    return cart

HTTP layer: the Node dev server keeps its fixed-user_dev mock pattern; Python follows its request-carried userId convention (query parameter on GET/DELETE). Route error mapping gains CART_ITEM_NOT_FOUND → 404. As in the two previous practices, the separately maintained reduced route table in server.prod.js stays out of scope.

Tests: Node unit +3 (successful removal / missing item throws / multi-item isolation), integration E2E +1 (empty-cart query → add → query → remove → repeat remove 404); Python smoke +1 (same sequence with an isolated userId). Results: Node 14/14, Python 5/5 green.

3.5 Archive: Sync + Archive in One Command

v1.11.0’s openspec archive embeds the spec merge (no standalone sync command in the CLI):

$ openspec archive cart-query-and-remove --yes
Specs to update:
  cart-management: update
Applying changes to openspec/specs/cart-management/spec.md:
  + 1 added
  ~ 1 modified
Totals: + 1, ~ 1, - 0, → 0
Specs updated successfully.
Change 'cart-query-and-remove' archived as '2026-09-02-cart-query-and-remove'.

Merge fidelity spot-checked: MODIFIED replaces only the target requirement block, ADDED appends, and untouched requirements (e.g. “购物车数量限制”) keep their original content and position. Full openspec validate: all 7 specs pass, no active changes left behind.

4. Takeaways

4.1 show –diff changes the review cadence

Reviewing a delta used to mean opening the file and reading restated text; now a single show --diff call exposes every real change. The more scenarios a MODIFIED requirement keeps, the bigger the win — this is the compensating mechanism for the design cost of “restate everything you keep.”

4.2 v1.11.0’s Net Effect on the Practice Workflow

  1. Reviews move earlier: show --diff turns “write delta → review immediately” into a zero-friction move; semantic drift can be caught as early as the propose phase.
  2. The write boundary is explicit: explore’s “name it, ask plainly, wait for a separate answer” narrows AI write permission to explicit user consent — clarifying Q&A no longer implies consent.
  3. Archiving is simpler: sync and archive merge into one command that prints merge stats (+1/~1), reducing the mental load of agent-driven manual merging.

4.3 Continuity with the v1.9.0 Migration

After v1.9.0’s ## Purpose migration, v1.11.0’s validate warns on unwritten Purpose placeholders (default warning) — completing a “format migration → content guard” loop.


This article is based on the cart-query-and-remove practice in the OpenSpec Practise repository (2026-09-02); full artifacts live under openspec/changes/archive/2026-09-02-cart-query-and-remove/.