Skip to content

feat(core): add status field to spec metadata #12

@lsmonki

Description

@lsmonki

Overview

Specs currently have no lifecycle state. Any spec in the tree is implicitly active and included in context compilation. This issue adds a status field (with optional superseded-by and supersedes) to spec-lock.json so that specs can be marked as inactive or superseded, enabling use cases like ADR-style append-only workflows.

Motivation

When a team treats specs as append-only (creating a new spec to replace an old one instead of editing it), there is no way to tell specd that the old spec is no longer operative. Without this, stale specs would still be included in context and potentially mislead the agent.

This also unlocks using specs as a semantic vehicle for ADRs: a superseded ADR-spec is replaced by a new one, not edited — matching standard ADR conventions.

New fields in spec-lock.json

Status lives in spec-lock.json alongside schema and dependsOn. It is system infrastructure — CompileContext filters by it, so it cannot depend on metadata being regenerated.

{
  "schema": { "name": "schema-std", "version": 1 },
  "status": "superseded",
  "supersededBy": "cli:spec-list-v2",
  "supersedes": "cli:spec-list",
  "dependsOn": []
}
  • status is optional; absence is treated as active — fully backwards compatible, no existing spec breaks. Valid values: active (default) | inactive | superseded
  • supersededBy is only meaningful when status: superseded; it is a workspace:capability-path spec-id
  • supersedes is informational — it links the new spec to the old one, enabling forward navigation
  • Written at archive time by the system, never edited directly by the user or LLM
  • See feat: spec-lock.json — immutable sidecar for schema identity and spec dependencies #56 for the spec-lock.json mechanism

Design note: relationship to #56

spec-lock.json (#56) provides the immutable sidecar for schema identity and dependencies. status fits the same pattern — it is system infrastructure that must persist independently of metadata extraction. The content of a spec may have a human-visible ## Status section for readability, but the system reads status from spec-lock.json.

## Status section (optional, human-visible)

Specs may optionally include a ## Status section for human readability:

## Status

- **status:** superseded
- **superseded-by:** `cli:spec-list-v2`

This is informational only. The system reads status from spec-lock.json, not from spec content.

Affected specs

  • specs/core/spec-lock/spec.md — add status, supersededBy, supersedes fields to spec-lock.json format
  • specs/core/compile-context/spec.md — add requirement: exclude specs where status != active from context by default
  • specs/cli/spec-list/spec.md — default output to active specs only; add --status flag for lifecycle filtering

Constraints

  • Absence of status field must be treated as active (no breaking change)
  • superseded and inactive specs must remain readable and navigable (e.g. specd spec show)
  • compile-context must not include inactive or superseded specs unless explicitly requested
  • superseded-by must be a valid workspace:capability-path format when present

Related

Relationship to metadata.json

metadata.json may also contain status as a derived cache of the value in spec-lock.json, synced at archive time. This enables fast queries without reading the sidecar. If both exist and disagree, spec-lock.json is the source of truth.

Conflict detection with metadataExtraction

If metadataExtraction extracts a status from spec content that differs from what is in spec-lock.json, validation must fail. spec-lock.json is the source of truth — the content cannot implicitly contradict the sidecar. Same rule applies to dependsOn (see #56).

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions