spec

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package spec is abcd's transport-agnostic native spec store (intent lifecycle, itd-64). It owns the in-memory model of spec records and the disk operations that mint, load, and transition them. Every function takes a structured request and returns a structured result; nothing here writes to stdout or knows about a CLI, MCP, or hook surface — the front doors under internal/surface/* marshal these results for their transport.

A spec record is a markdown file under <repoRoot>/.abcd/development/specs/{open,closed}/spc-N-<slug>.md. The bucket directory IS the status (directory-as-truth: open/ vs closed/, with no status: frontmatter field), mirroring how intents encode their lifecycle by bucket. The load-bearing field is intent: itd-N, the link a spec declares to the intent it realises.

Frontmatter is read by the shared internal/core/frontmatter line scanner, not a YAML parser — the package pulls in zero new dependencies. Ids are validated against strict regexes before any path is built, so a hostile id can never traverse out of the spec store.

Index

Constants

View Source
const (
	StatusOpen   = "open"
	StatusClosed = "closed"
)

Bucket directory names. The directory a spec file lives in is its status.

View Source
const (
	// SpecsRelDir is the spec-store root, relative to the repo worktree.
	SpecsRelDir = ".abcd/development/specs"
	// IntentsRelDir is where intents live; NextID scans it for reserved spec ids.
	IntentsRelDir = ".abcd/development/intents"
)

Repo-relative store locations.

Variables

This section is empty.

Functions

func BodyIsStub

func BodyIsStub(content string) bool

BodyIsStub reports whether a spec body still carries the minted placeholder — i.e. no human has written the design record yet.

func HasNum added in v0.6.7

func HasNum(v string) bool

HasNum reports whether v is a spec reference carrying a usable number. It is the tolerant counterpart of the ^spc-[0-9]+$ argument grammar, for validating a STORED spec_id, which the record lint lets carry a slug or zero padding.

func NextID

func NextID(repoRoot string) (id, mintWarning string, err error)

NextID mints the next spec id. The rule is:

max(N over existing spec-store files ∪ N over every intent's spec_id
frontmatter across .abcd/development/intents/** ∪ N over spec-store filenames
on every other git ref) + 1

Scanning the intents is what keeps a freshly minted spec from colliding with a reservation: itd-3 shipped with spec_id: spc-1 but has no spec-store file, so a spec-only scan would hand out spc-1 again. Folding intent reservations in means the first minted id is spc-2 while that reservation stands.

Scanning every git ref (recordid.MaxAcrossRefs) is what keeps two parallel branches from re-minting the same spc-N: once branch A commits spc-10, branch B's ref scan sees it even though the file is absent from B's working tree (iss-115, iss-120). The returned mintWarning is non-empty (and MUST be surfaced) when that ref scan degraded to working-tree-only — git absent, not a repo, or a failed ref query — so the fallback is never silent. The cross-ref scan reads spec-store FILENAMES only; a spec_id reservation committed in an intent's frontmatter on another branch is not folded in (that narrower window is left to the record-lint uniqueness rules on the merged PR).

func SameNum added in v0.6.7

func SameNum(a, b string) bool

SameNum reports whether two spec references name the same spec number — the canonical comparison the record lint makes, so a verb using it can never refuse a spelling the lint accepts (spc-9, spc-9-widget, spc-009 are one spec). A value carrying no usable number (null, "spc-", "spc-abc", an over-int64 N) matches nothing, including another such value.

func Validate

func Validate(s Spec) error

Validate enforces the id regexes and that intent is a well-formed itd-N. It is the fail-closed guard both Load and the minting path run before trusting a record's id in a filesystem path.

Types

type Spec

type Spec struct {
	ID     string `json:"id"`     // spc-N
	Slug   string `json:"slug"`   // kebab-case
	Intent string `json:"intent"` // itd-N, the load-bearing link
	Status string `json:"status"` // open | closed (directory-as-truth)
	Path   string `json:"path"`   // repo-relative markdown path
}

Spec is one spec record. Status is the bucket it was found in; Path is repo-relative (never an absolute local path).

func Close

func Close(repoRoot, specID string) (Spec, error)

Close moves a spec file open/ -> closed/ via os.Rename (atomic on one filesystem) and returns the updated Spec. It fails closed if the spec is missing or already closed. The linked intent is deliberately left untouched: moving it is a later reconcile concern that consumes Spec.Intent.

func Create

func Create(repoRoot, intentID, slug, productionMode string) (Spec, string, error)

Create mints an id via NextID and writes specs/open/spc-N-<slug>.md with the intent link and the origin/production_mode disclosure pair in frontmatter. Both the intent id and the slug are validated before any path is built (the slug becomes a filename), as is the production mode — a spec is minted by a verb a person invoked, so its arrival path is researcher-authored and is derived here rather than asked for. An empty mode takes the vocabulary's default. The write is atomic. The returned mintWarning is non-empty when the refs-union scan degraded to working-tree-only minting; the caller MUST surface it (never swallow it).

type Store

type Store struct {
	Specs []Spec `json:"specs"`
}

Store is the in-memory set of spec records discovered under both buckets.

func Load

func Load(repoRoot string) (Store, error)

Load discovers spec files under both buckets, parses their frontmatter, and returns the in-memory Store. A missing specs/ directory yields an empty store (soft, mirroring lint's missing-dir behaviour). A present-but-malformed spec file is a hard, loud error.

func (Store) ByIntent

func (s Store) ByIntent(intentID string) (Spec, bool)

ByIntent returns the spec linked to the given intent id; ok is false when no spec realises that intent.

func (Store) Lookup

func (s Store) Lookup(specID string) (Spec, bool)

Lookup returns the spec the given reference names; ok is false when absent.

Matching is on the spec NUMBER, not the literal string, because that is the comparison record-lint makes: a spec_id is written bare (spc-9), with its slug (spc-9-widget), and zero-padded (spc-009) across the record, and all three are lint-green. A literal-only compare would let the lifecycle verbs refuse a record the lint accepts. An exact string match still wins when the store holds one, so a caller that names a record precisely gets that record; two specs sharing a number is a record defect the lint's spec_id_unique rule flags (the mint never produces one, since NextID allocates max+1 over the same numbers).

Jump to

Keyboard shortcuts

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