decision

package
v0.37.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package decision — lifecycle transition orchestration for the Decision kind.

This file hosts ChangeStatus, the orchestrator invoked by `specscore decision change-status`. It composes pkg/lifecycle/ primitives (state-machine validation, status-line rewrite, rollback) with Decision- specific behavior that has no analogue in Idea/Plan/Task/Lesson:

  • Archival is NOT an optional axis for a Decision the way it is for an Idea. pkg/lint's D-archived-location rule REQUIRES every Decision whose Status is Rejected, Superseded, or Deprecated to live under spec/decisions/archived/. ChangeStatus therefore relocates the file itself, atomically with the status rewrite, whenever `to` is one of those three dispositions — there is no separate `decision archive` verb to call afterward.
  • Superseding is bidirectional. pkg/lint's D-supersedes-bidirectional rule requires the OLD decision's `**Superseded By:**` and the NEW (successor) decision's `**Supersedes:**` to point at each other. ChangeStatus writes BOTH sides in the same transition — the successor file is mutated as a side effect of transitioning the superseded one.
  • The decisions index is NOT self-healing the way the ideas index is. pkg/lint's decisions-index fixer can ADD a missing row to the active table (used by `decision new`) but has no fixer that adds a missing archived-index bullet or removes an active-index row for a decision that just relocated. ChangeStatus performs both edits itself (removeActiveIndexRow / appendArchivedIndexEntry in index.go) before invoking the PostMutation hook, so the tree is lint-clean the moment the verb returns rather than depending on a lint-fixer capability that does not exist today.

LINT INVOCATION lives in the cobra adapter (internal/cli/decision.go), NOT here, mirroring every other kind package: the adapter passes a PostMutationHook callback into ChangeStatus; this package only knows "run the post-mutation hook, and roll back everything if it fails."

Cross-references:

  • Decision status vocabulary + archival/supersession rules: pkg/lint/decision_rules.go (decisionValidStatuses, D-archived-location, D-superseded-requires-successor, D-supersedes-bidirectional, D-supersedes-target-exists).
  • Sibling shapes: pkg/plan/transitions.go (prep band + disposition band, **Superseded By:** on --to=superseded), pkg/lesson/transitions.go (--note required for disposition targets, --successor required only for Superseded), pkg/idea/archive.go (relocate + collision + full rollback).

Index

Constants

View Source
const FormatURL = "https://specscore.md/decision-specification"

FormatURL is the canonical spec URL for the Decision document type, emitted in both the frontmatter `format:` field and the adherence-footer line.

Variables

This section is empty.

Functions

func AllNumbers

func AllNumbers(specRoot string) []int

AllNumbers returns all decision numbers found in spec/decisions/ and archived/, sorted ascending.

func EnsureArchivedIndexStub added in v0.32.0

func EnsureArchivedIndexStub(specRoot string) (created bool, err error)

EnsureArchivedIndexStub guarantees that spec/decisions/archived/ exists and contains a lint-clean README.md index. It creates the directory if absent and writes archivedDecisionsIndexStub to archived/README.md ONLY when that file does not already exist. It returns created=true iff it wrote the stub, so callers can undo exactly what they materialized on rollback.

Mirrors idea.EnsureArchivedIndexStub. specRoot is the project root that contains the spec/ subtree (NOT the spec/ directory itself).

func LegalChangeStatusTargetNames added in v0.32.0

func LegalChangeStatusTargetNames() []string

LegalChangeStatusTargetNames returns the canonical-titled names of the legal --to values, for stderr rendering and help text.

func LegalTransitionMatrix added in v0.32.0

func LegalTransitionMatrix() string

LegalTransitionMatrix returns a human-readable, ANSI-free rendering of the Decision legal-transition matrix, suitable for cobra `Long` help text.

func NextNumber

func NextNumber(specRoot string) (int, error)

NextNumber scans spec/decisions/ and spec/decisions/archived/ for the highest NNNN prefix and returns max + 1. Returns 1 if no decisions exist.

func Scaffold

func Scaffold(opts ScaffoldOptions) ([]byte, error)

Scaffold returns a lint-clean Decision file body.

func ValidateFullSlug added in v0.32.0

func ValidateFullSlug(slug string) error

ValidateFullSlug validates the full on-disk Decision identifier (NNNN-slug) used by `decision change-status`'s positional argument and its --successor flag. A bare slug (no NNNN- prefix), as accepted by `decision new`, is rejected here — change-status resolves directly to a file, and that file is always named NNNN-<slug>.md.

func ValidateSlug

func ValidateSlug(slug string) error

Types

type ChangeStatusOptions added in v0.32.0

type ChangeStatusOptions struct {
	// SpecRoot is the project root that contains the `spec/` subtree (NOT the
	// `spec/` directory itself). The Decision is resolved at
	// SpecRoot/spec/decisions/<Slug>.md.
	SpecRoot string

	// Slug is the full on-disk Decision identifier, e.g.
	// "0009-go-wasm-single-engine". Caller is expected to have validated it
	// via ValidateFullSlug.
	Slug string

	// To is the canonical (title-case) target status. The cobra adapter
	// parses the raw --to value via lifecycle.ParseStatus before reaching
	// this function.
	To lifecycle.Status

	// Note is the optional free-form markdown transition note, written as a
	// `## Resolution` section atomically with the status rewrite. The cobra
	// adapter REQUIRES a non-empty Note for every disposition target
	// (Rejected, Superseded, Deprecated) — a Decision moving into archival
	// always carries a reason, both in the artifact body and (flattened to a
	// single line) as the archived-index entry's reason column.
	Note string

	// Successor is the full on-disk identifier of the Decision that
	// supersedes this one. REQUIRED (enforced by the cobra adapter) for
	// --to=Superseded, rejected otherwise. ChangeStatus writes
	// `**Supersedes:** <Slug>` onto the successor file and
	// `**Superseded By:** <Successor>` onto this one — the bidirectional link
	// D-supersedes-bidirectional requires.
	Successor string

	// PostMutation is the post-rewrite hook (typically a spec-lint pass).
	// Required; ChangeStatus returns exit 10 if nil.
	PostMutation PostMutationHook
}

ChangeStatusOptions packages the inputs to ChangeStatus.

type ChangeStatusResult added in v0.32.0

type ChangeStatusResult struct {
	Slug string
	From lifecycle.Status
	To   lifecycle.Status
	// ArchivedPath is non-empty when the transition relocated the file (every
	// disposition target: Rejected, Superseded, Deprecated).
	ArchivedPath string
}

ChangeStatusResult is the success payload returned on exit 0. The cobra adapter formats it as the `<slug>: <from> → <to>` success line.

func ChangeStatus added in v0.32.0

func ChangeStatus(opts ChangeStatusOptions) (ChangeStatusResult, error)

ChangeStatus performs a Decision-kind lifecycle transition end-to-end.

Flow:

  1. Resolve <slug> to spec/decisions/<slug>.md. A missing file returns exit 3.
  2. lifecycle.Validate against the KindDecision matrix. Illegal transitions return exit 4.
  3. When `to` is a disposition (Rejected/Superseded/Deprecated): pre-check the archived-path collision (exit 1) and, for Superseded, resolve the successor file (exit 2 if it does not exist) — BEFORE any mutation.
  4. Rewrite the **Status:** line; capture original for rollback.
  5. For Superseded: write **Supersedes:** on the successor and **Superseded By:** on this decision.
  6. Write the optional `## Resolution` note.
  7. For a disposition: relocate the file to spec/decisions/archived/ and sync both index files (remove the active row, add the archived entry).
  8. Invoke the PostMutation hook. Failure → full rollback + exit 10.

ChangeStatus performs all rollback internally — by the time it returns an error, the on-disk state is byte-identical to its pre-invocation shape.

type PostMutationHook added in v0.32.0

type PostMutationHook func() error

PostMutationHook is the callback the cobra adapter wires to `specscore spec lint --fix` (plus a verify pass). It MUST return nil on success; a non-nil return triggers full rollback of every on-disk mutation ChangeStatus performed, and the error is returned by ChangeStatus.

type ScaffoldOptions

type ScaffoldOptions struct {
	Slug       string
	Title      string
	Owner      string
	Date       string
	Tags       string
	SourceIdea string
	Supersedes string
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL