Documentation
¶
Overview ¶
Package lifecycle hosts a kind-parameterized state machine for SpecScore artifact Status transitions. It is the shared implementation layer that the per-kind `change-status` CLI verbs consume.
The package is deliberately kind-agnostic: Idea, Feature, and any future doc kind plug their own legal-transition matrix into the package's lookup tables. Verb-specific logic (archive relocation, feature-id resolution, cobra wiring, exit-code mapping) lives in the calling CLI verbs, not here.
Architectural contract implemented by this package (see spec/features/cli/lifecycle-transitions/README.md):
- REQ: state-machine-strictness — Transition rejects any (from, to) pair not declared in the kind's matrix.
- REQ: not-idempotent — the matrix MUST NOT contain a self-loop (from == to). The package's init step panics if a self-loop is declared, so corruption is caught at startup rather than runtime.
- REQ: status-line-rewrite — Rewrite mutates only the **Status:** line, preserving every other byte (including line endings and trailing whitespace).
- REQ: rollback-on-lint-failure — Rollback restores the original **Status:** line byte-for-byte.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidTransition = errors.New("invalid lifecycle transition")
ErrInvalidTransition is returned by Transition (and Validate) when the requested (from, to) pair is not present in the kind's matrix.
The error carries the kind, source status, target status, and the legal target set from the current source, so the CLI layer can render a user-friendly message without re-querying the matrix.
var ErrStatusLineNotFound = errors.New("lifecycle: artifact has no **Status:** line")
ErrStatusLineNotFound is returned by Validate/Rewrite when the artifact does not contain a recognizable `**Status:**` line.
Functions ¶
func Rewrite ¶
Rewrite mutates the artifact's `**Status:**` line in place, replacing only the value text. Every other byte of the file (line ordering, indentation, line endings, trailing whitespace) is preserved (REQ: status-line-rewrite).
The returned string is the ORIGINAL line content (including any line terminator that was attached to it), suitable for passing to Rollback to undo the mutation. The caller is responsible for retaining this value until index sync is confirmed successful.
If the file has no `**Status:**` line, Rewrite returns ErrStatusLineNotFound and the file is left untouched.
func Rollback ¶
Rollback restores the artifact's `**Status:**` line to its pre-Rewrite content, identified by the originalStatusLine returned from the prior Rewrite call.
Rollback locates the file's current `**Status:**` line (which is now the MUTATED value), replaces that single line with originalStatusLine, and writes the file back. After Rollback returns nil, the file content is byte-identical to its pre-Rewrite state.
If the file has been mutated externally between Rewrite and Rollback such that no `**Status:**` line remains, Rollback returns ErrStatusLineNotFound. Concurrent modification is outside the contract (REQ: no-coordination in the lifecycle-transitions Meta spec).
func Transition ¶
Transition validates that (from, to) is a legal transition in kind's matrix. It returns nil on success and a wrapped ErrInvalidTransition on failure. The wrapped error carries the legal target set from the current source so callers can render a useful message.
Transition does NOT touch the filesystem; it is pure matrix lookup.
Types ¶
type InvalidTransitionError ¶
InvalidTransitionError is a typed error carrying the context of a rejected transition. It wraps ErrInvalidTransition, so callers can use errors.Is to detect this category.
func (*InvalidTransitionError) Error ¶
func (e *InvalidTransitionError) Error() string
Error implements the error interface. The message is human-readable and names both endpoints plus the legal target set from the current source.
func (*InvalidTransitionError) Unwrap ¶
func (e *InvalidTransitionError) Unwrap() error
Unwrap exposes ErrInvalidTransition so errors.Is(err, ErrInvalidTransition) returns true.
type Kind ¶
type Kind string
Kind names a doc kind that participates in the lifecycle state machine.
type Status ¶
type Status string
Status is a domain-scoped status value. The set of legal Status values is per-Kind and validated by the kind's transition table; callers SHOULD use ParseStatus to obtain a canonical Status from a raw flag string.
const ( IdeaDraft Status = "Draft" IdeaUnderReview Status = "Under Review" IdeaApproved Status = "Approved" IdeaImplementing Status = "Implementing" IdeaSpecified Status = "Specified" IdeaArchived Status = "Archived" )
Idea statuses.
const ( FeatureDraft Status = "Draft" FeatureUnderReview Status = "Under Review" FeatureApproved Status = "Approved" FeatureImplementing Status = "Implementing" FeatureStable Status = "Stable" FeatureDeprecated Status = "Deprecated" )
Feature statuses.
func LegalSources ¶
LegalSources is the inverse of LegalTargets: which from-states can transition INTO to. Used by error-message construction when target is valid as a status name but invalid for the current source state.
func LegalStatuses ¶
LegalStatuses returns every recognized status for a kind (the union of every From and To in its matrix), sorted alphabetically. Used by the CLI layer to validate a --to flag value before invoking the state-machine check.
func LegalTargets ¶
LegalTargets returns the legal target statuses reachable from (kind, from), sorted alphabetically. The empty slice is returned (never nil for an unknown kind, but never nil for a known kind either) when from is not a legal source state in the kind's matrix.
func ParseStatus ¶
ParseStatus does case-insensitive parsing of a raw flag-string against the kind's recognized statuses, returning the canonical title-cased Status on success.
Whitespace is trimmed; case is folded (so "draft", "Draft", "DRAFT", and " Draft " all match). Multi-word statuses ("Under Review") match case-insensitively but the internal-whitespace shape MUST match (i.e., "underreview" without a space does NOT match "Under Review"). This is the least-surprising behavior for a CLI flag.
func Validate ¶
Validate reads artifactPath, extracts its current Status, and checks that the (kind, current, to) transition is legal. It does NOT mutate the file.
It returns the from status on success. On failure it returns one of:
- an os error if the file cannot be opened or read
- ErrStatusLineNotFound if the file has no recognizable **Status:** line
- an *InvalidTransitionError if the transition is illegal in kind's matrix
Validate is the primitive that the CLI verb runs FIRST, before any mutation; it is the single check that guarantees REQ: state-machine-strictness.