Skip to content

fix(status): order artifacts by the schema, not the alphabet - #1465

Merged
clay-good merged 7 commits into
mainfrom
fix/artifact-order-declaration-tiebreak
Jul 28, 2026
Merged

fix(status): order artifacts by the schema, not the alphabet#1465
clay-good merged 7 commits into
mainfrom
fix/artifact-order-declaration-tiebreak

Conversation

@clay-good

@clay-good clay-good commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Status

Ready. An ordering fix with no gating change: no dependency edge was added or removed, so nothing newly blocks and no artifact becomes mandatory. Four adversarial reviews were run against it; everything they confirmed is fixed in this branch.

What was wrong

OpenSpec told agents to write design.md before any spec existed.

specs and design both require only proposal, so both become ready at the same moment. The graph broke that tie alphabetically, and "design" sorts before "specs". On a change with only a proposal:

[x] proposal
[ ] design          ← nextSteps recommended this
[ ] specs
[-] tasks (blocked by: design, specs)

Everything else in OpenSpec said the opposite — and in two places the CLI already contradicted itself:

Source Says
schemas/spec-driven/schema.yaml description proposal → specs → design → tasks
The design instruction "Reference the proposal for motivation and, once written, the specs for requirements"
openspec schemas (reads declaration order) Artifacts: proposal → specs → design → tasks
docs/opsx.md status sample lists specs first, annotated ◄── First ready
docs/commands.md continue transcript ◆ specs (ready) above ◆ design (ready), "Creating specs..."
Workflow docs, skill templates, schemas from openspec schema init specs before design

How it was fixed

Ties are broken by the order the schema declares its artifacts (src/core/artifact-graph/graph.ts). That covers getBuildOrder, getNextArtifacts, and getBlocked, so openspec status, status --json, nextSteps, and blocked by: lists all agree. openspec instructions' unlocks field was still alphabetical and is now consistent too.

design.md stays optional and still writable early — you just get a warning, exactly as before. Custom schemas get the same guarantee: list artifacts in the order you want them written.

#695 and #1173 proposed adding specs to design.requires instead. That was deliberately not done: it would mark design blocked before specs exist, and drafting design early is legitimate. It also isn't needed to fix the reported behavior.

Proof it works

$ openspec status --change fresh-change
[x] proposal
[ ] specs
[ ] design
[-] tasks (blocked by: specs, design)

$ openspec status --change fresh-change --json | jq -r .nextSteps[0]
Run openspec instructions specs --change "fresh-change" --json before writing that artifact.

$ openspec instructions proposal --change fresh-change --json | jq -c .unlocks
["specs","design"]        # was ["design","specs"]
  • Tests: full suite green (112 files, 2296 tests), plus new coverage for spec-driven's build order, ready siblings, blocked-by order, unlocks order, and the status --json artifact list.
  • Property check: over 5000 randomly generated schemas — including ones whose declaration order is not topological — the build order exactly equals an independent greedy "earliest-declared ready artifact" reference. The build order stays complete and topologically valid in every case.
  • End-to-end: fresh project through init → new change → proposal → specs → design → tasks → validate --strict → archive behaves as before, with the spec merged into openspec/specs/ and the change archived.
  • Baseline diff: list, show --json, schemas, templates --json, instructions apply --json, and instructions archive --json are byte-identical to main. Only status, nextSteps, and unlocks moved.
  • Edge cases: empty schema, single artifact, duplicate ids, self-requires, cycles, and dangling requires all behave exactly as on main (schema validation rejects the invalid ones before the graph sees them). Perf of the per-iteration queue re-sort at 2000 artifacts: ~54 ms; real schemas have four.

Review findings folded in

  • Sorting only the newly-ready artifacts left a waiting artifact ahead of one declared earlier ([root, child, laterRoot] gave root → laterRoot → child). The whole queue is re-sorted now, with a regression test.
  • unlocks was still alphabetical, so instructions named artifacts in one order while status recommended another.
  • The comparator is a method rather than an arrow-valued field, so ArtifactGraph remains structuredClone-able.

Docs and specs updated

File Change
openspec/specs/artifact-graph/spec.md declaration-order scenarios for build order, ready query, and blocked query
openspec/specs/cli-artifact-workflow/spec.md status lists artifacts in declared order; first [ ] is the recommended next artifact
openspec/specs/instruction-loader/spec.md unlocks uses the same order
docs/agent-contract.md ordering guarantee for status --json and instructions --json
docs/cli.md two stale output samples corrected; the guarantee stated
docs/customization.md schema authors: list order decides ties
docs/opsx.md, docs/commands.md samples that under-reported missingDeps / what a new artifact unlocks
.changeset/ patch release note, including the note for custom-schema authors

No template or skill content changed, so no golden-hash regeneration.

Notes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Artifact workflows now follow the artifact order declared in the schema.
    • When multiple artifacts are equally ready or blocked, their listed/retrieved order follows the schema sequence (not alphabetical).
    • status output and recommended next steps now use this deterministic ordering for ties.
  • Documentation
    • Updated CLI/customization guidance and spec examples to describe the ordering semantics for artifacts and missingDeps.
  • Tests
    • Added/updated unit and integration tests to enforce schema-declared ordering across build plans, status (text/JSON), and instruction unlocks.

Artifacts that become ready at the same time were sorted alphabetically,
so spec-driven's `specs` and `design` - both requiring only `proposal` -
came back as design first. `openspec status` listed design above specs
and `nextSteps` pointed at design, sending agents to write design.md
before any spec existed. That contradicts the schema's own description
(proposal -> specs -> design -> tasks), the design instruction ("reference
the specs for requirements"), the workflow docs, and the schema `openspec
schema init` scaffolds (where design requires specs).

Break ties by the order the schema declares its artifacts instead. The
dependency edges are untouched, so nothing newly blocks and no artifact
becomes mandatory - only the order of equally-ready artifacts changes, and
it now follows the sequence the schema author wrote, for custom schemas
too.

Closes #692
Closes #695

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@clay-good
clay-good requested a review from a team as a code owner July 27, 2026 23:11
@clay-good
clay-good requested review from alfred-openspec and removed request for a team July 27, 2026 23:11
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a5a0ef75-2fbc-45f7-8b5c-4b9f1bc6a685

📥 Commits

Reviewing files that changed from the base of the PR and between 0e430f2 and e19b7de.

📒 Files selected for processing (6)
  • .changeset/schema-declared-artifact-order.md
  • docs/agent-contract.md
  • docs/cli.md
  • docs/opsx.md
  • openspec/specs/artifact-graph/spec.md
  • openspec/specs/cli-artifact-workflow/spec.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • .changeset/schema-declared-artifact-order.md
  • openspec/specs/artifact-graph/spec.md
  • docs/cli.md
  • openspec/specs/cli-artifact-workflow/spec.md
  • docs/agent-contract.md

📝 Walkthrough

Walkthrough

Artifact ordering now follows schema declaration order across graph operations, instruction generation, status output, tests, and documentation. Ready artifacts, build sequences, unlocked artifacts, and blocked dependencies use the declared sequence instead of alphabetical sorting.

Changes

Artifact ordering

Layer / File(s) Summary
Ordering contracts and workflow semantics
openspec/specs/artifact-graph/spec.md, openspec/specs/cli-artifact-workflow/spec.md, openspec/specs/instruction-loader/spec.md, .changeset/...
Specifications and release notes define schema-declared ordering for build sequences, status output, ready artifacts, unlocked artifacts, and unmet dependencies.
Declaration-order graph semantics
src/core/artifact-graph/graph.ts
ArtifactGraph uses schema declaration order for root, ready, newly-ready, and unmet-dependency ordering.
Instruction unlock ordering
src/core/artifact-graph/instruction-loader.ts, test/core/artifact-graph/instruction-loader.test.ts
Generated unlocks preserve declaration order, with tests asserting the ordered result.
Workflow validation and examples
test/core/artifact-graph/*, test/commands/artifact-workflow.test.ts, docs/cli.md, docs/customization.md, docs/agent-contract.md, docs/commands.md, docs/opsx.md
Tests and examples verify the proposal → specs → design → tasks sequence, status ordering, readiness recommendations, and blocked dependency reporting.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: alfred-openspec, tabishb

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: status artifacts are ordered by schema declaration instead of alphabetically.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/artifact-order-declaration-tiebreak

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploying openspec-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: e19b7de
Status: ✅  Deploy successful!
Preview URL: https://b60990c6.openspec-docs.pages.dev
Branch Preview URL: https://fix-artifact-order-declarati.openspec-docs.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/artifact-graph/graph.ts (1)

109-129: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Re-sort the entire ready queue after adding newly ready artifacts.

Sorting only newlyReady leaves existing queue entries ahead of earlier-declared artifacts. For a schema ordered [root, child, laterRoot], where child requires root, the build order becomes root, laterRoot, child even though child and laterRoot are both ready after root and child is declared first.

Append the new entries, then sort the full queue (or use a declaration-order priority queue), and add a regression test for this interleaving case.

Suggested fix
-      queue.push(...newlyReady.sort(this.compareByDeclarationOrder));
+      queue.push(...newlyReady);
+      queue.sort(this.compareByDeclarationOrder);

As per coding guidelines, run the focused test with pnpm exec vitest run test/core/artifact-graph/graph.test.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/artifact-graph/graph.ts` around lines 109 - 129, Update the
topological ordering loop to sort the entire ready queue after appending newly
ready artifacts, using compareByDeclarationOrder, so existing and newly added
entries are globally declaration-ordered. Add a regression test in the artifact
graph tests covering [root, child, laterRoot] and asserting root, child,
laterRoot order, then run the focused Vitest command specified in the review.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/core/artifact-graph/graph.ts`:
- Around line 109-129: Update the topological ordering loop to sort the entire
ready queue after appending newly ready artifacts, using
compareByDeclarationOrder, so existing and newly added entries are globally
declaration-ordered. Add a regression test in the artifact graph tests covering
[root, child, laterRoot] and asserting root, child, laterRoot order, then run
the focused Vitest command specified in the review.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 60982aaa-38c6-4d00-8b47-48f08fb6c9ee

📥 Commits

Reviewing files that changed from the base of the PR and between 5bcf057 and 69adf8a.

📒 Files selected for processing (6)
  • docs/cli.md
  • docs/customization.md
  • src/core/artifact-graph/graph.ts
  • test/commands/artifact-workflow.test.ts
  • test/core/artifact-graph/graph.test.ts
  • test/core/artifact-graph/workflow.integration.test.ts

clay-good and others added 4 commits July 27, 2026 18:18
CodeRabbit caught it: sorting only the newly ready artifacts left an
already-queued artifact ahead of one declared earlier. For [root, child,
laterRoot] where child requires root, the build order came out root ->
laterRoot -> child even though child is declared first and both are ready
after root. Sort the full queue after each push.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adversarial review found `unlocks` was left alphabetical while build order,
ready lists and blocked lists moved to declaration order, so `openspec
instructions proposal` said "enables: design, specs" while `openspec status`
listed specs first - the one field whose job is naming what comes next
disagreed with everything else. getAllArtifacts() already yields declaration
order, so the stray sort is simply dropped.

Also make compareByDeclarationOrder a method rather than an arrow-valued
field: the field added an own enumerable function property that made
ArtifactGraph fail structuredClone.

Docs and specs updated for the new guarantee:
- openspec/specs/{artifact-graph,cli-artifact-workflow,instruction-loader}
- docs/agent-contract.md: status --json and instructions --json ordering
- docs/opsx.md: the status sample's missingDeps was missing design
- changeset

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d lines

The sample said tasks was blocked by specs alone and that creating specs
made tasks available; tasks needs design too. Same class of inaccuracy as
the status samples this branch already corrected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/opsx.md`:
- Line 507: Update the JSON example’s tasks entry to include the required
"status": "blocked" field alongside its existing properties, preserving the
documented status shape for every artifact.

In `@openspec/specs/artifact-graph/spec.md`:
- Around line 91-94: Update the “Ready artifacts ordered by declaration”
scenario in the artifact graph specification to use the standard wording “the
artifact the schema recommends writing next” instead of “the artifact the schema
wants written next.”

In `@openspec/specs/cli-artifact-workflow/spec.md`:
- Around line 43-49: Update the status-ordering documentation at
openspec/specs/cli-artifact-workflow/spec.md lines 43-49 and the JSON contract
at docs/agent-contract.md line 58 to describe topological ordering from
getBuildOrder(), using schema declaration order to break ties rather than
guaranteeing raw declaration order; preserve the stated ready-artifact and
missingDeps ordering behavior at both sites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 44023c8d-385c-4e6f-aab3-01169d84bf80

📥 Commits

Reviewing files that changed from the base of the PR and between 43a2266 and 0e430f2.

📒 Files selected for processing (10)
  • .changeset/schema-declared-artifact-order.md
  • docs/agent-contract.md
  • docs/commands.md
  • docs/opsx.md
  • openspec/specs/artifact-graph/spec.md
  • openspec/specs/cli-artifact-workflow/spec.md
  • openspec/specs/instruction-loader/spec.md
  • src/core/artifact-graph/graph.ts
  • src/core/artifact-graph/instruction-loader.ts
  • test/core/artifact-graph/instruction-loader.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/artifact-graph/graph.ts

Comment thread docs/opsx.md Outdated
Comment thread openspec/specs/artifact-graph/spec.md
Comment thread openspec/specs/cli-artifact-workflow/spec.md Outdated
clay-good and others added 2 commits July 27, 2026 18:51
CodeRabbit was right that "artifacts appear in the order the schema
declares them" over-claims: dependency order still wins, and declaration
order only breaks ties. Proved with a schema that declares tasks, specs,
proposal - status renders proposal, specs, tasks, not the declared order.
Corrected in the cli-artifact-workflow spec, agent-contract.md, cli.md and
the changeset.

Also restores "status": "blocked" in the opsx.md status sample (split across
two lines so the ASCII box still aligns) and uses "recommends writing next"
in the artifact-graph spec.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@alfred-openspec alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at e19b7de. Declaration order now breaks only dependency ties across build order, ready artifacts, blocked dependencies, next steps, and unlocks; dependency edges and optionality are unchanged. 147 focused tests, build, lint, strict validation, the 2,296-test cross-platform suite, CodeQL, audit, dependency review, and release tracking all pass.

@clay-good
clay-good added this pull request to the merge queue Jul 28, 2026
Merged via the queue into main with commit f917b8b Jul 28, 2026
17 checks passed
@clay-good
clay-good deleted the fix/artifact-order-declaration-tiebreak branch July 28, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug Report: spec-driven schema dependency order Generation order seems reversed between design and spec

2 participants