Documentation
¶
Overview ¶
Package entity defines the six aiwf entity kinds, their status sets, id formats, and the in-memory frontmatter shape every entity carries.
The package is the data model. It deliberately knows nothing about the filesystem, git, or validation; the tree package loads entities, the check package validates them.
Index ¶
- Constants
- Variables
- func AllocateID(k Kind, entities []*Entity, trunkIDs []string) string
- func AllowedACStatuses() []string
- func AllowedStatuses(k Kind) []string
- func AllowedTDDPhases() []string
- func AllowedTDDPolicies() []string
- func AllowedTransitions(k Kind, from string) []string
- func BodyTemplate(k Kind) []byte
- func CancelTarget(k Kind) string
- func IDFormat(k Kind) string
- func IDFromPath(relPath string, k Kind) (string, bool)
- func IsAllowedACStatus(s string) bool
- func IsAllowedStatus(k Kind, status string) bool
- func IsAllowedTDDPhase(p string) bool
- func IsAllowedTDDPolicy(p string) bool
- func IsCompositeID(s string) bool
- func IsLegalACTransition(from, to string) bool
- func IsLegalTDDPhaseTransition(from, to string) bool
- func IsProseyTitle(title string) bool
- func MilestoneCanGoDone(m *Entity) (canGoDone bool, openACs []string)
- func ParseACSections(body []byte) map[string]string
- func ParseBodySections(body []byte) map[string]string
- func ParseCompositeID(s string) (parent, sub string, ok bool)
- func SectionSlug(heading string) string
- func Serialize(e *Entity, body []byte) ([]byte, error)
- func Slugify(title string) string
- func SlugifyDetailed(title string) (slug string, dropped []rune)
- func Split(content []byte) (frontmatter, body []byte, ok bool)
- func SubKindFromID(id string) (string, bool)
- func ValidateID(k Kind, id string) error
- func ValidateTransition(k Kind, from, to string) error
- type AcceptanceCriterion
- type BodySection
- type Cardinality
- type Entity
- type ForwardRef
- type Kind
- type RefField
- type Schema
Constants ¶
const ( // Epic / shared. StatusProposed = "proposed" StatusActive = "active" StatusDone = "done" StatusCancelled = "cancelled" // Milestone-only. StatusDraft = "draft" StatusInProgress = "in_progress" // ADR / Decision. StatusAccepted = "accepted" StatusSuperseded = "superseded" StatusRejected = "rejected" // Gap. StatusOpen = "open" StatusAddressed = "addressed" StatusWontfix = "wontfix" // Contract. StatusDeprecated = "deprecated" StatusRetired = "retired" // Acceptance criterion (composite). StatusMet = "met" StatusDeferred = "deferred" )
Status constants for the closed sets. Hardcoded; see docs/pocv3/design/design-decisions.md and the schemas table for per-kind allowance. Use these constants instead of bare string literals so a future renumber / spelling shift lands in one place.
const ( TDDPhaseRed = "red" TDDPhaseGreen = "green" TDDPhaseRefactor = "refactor" TDDPhaseDone = "done" )
TDD-phase constants for the AC FSM.
Variables ¶
var ErrNoFrontmatter = errors.New("no YAML frontmatter found")
ErrNoFrontmatter is returned when the input does not contain a `---`-delimited YAML block at the top of the file.
Functions ¶
func AllocateID ¶
AllocateID picks the next free id for the kind, scanning the union of (a) entities — the caller's working tree — and (b) trunkIDs — id strings already present in the configured trunk ref's tree. Computes max(parsed-id over both sources) + 1 and formats with the canonical pad width. trunkIDs may be nil when no trunk is in scope (e.g., a sandbox repo with no remotes); see package trunk for the policy that produces the slice.
ids in either source whose prefix does not match k are ignored (cheap-and-correct: they parse to 0); ids that match the prefix but fail strconv are also ignored. The bookkeeping error is surfaced by the frontmatter-shape and id-path-consistent checks; the allocator does not need to refuse to start.
Branch-to-branch collisions that survive both sources (two branches from the same trunk SHA both allocating the same id before either lands on trunk) are caught at merge time by the ids-unique check, which also reads the trunk ref, and resolved by `aiwf reallocate`.
func AllowedACStatuses ¶
func AllowedACStatuses() []string
AllowedACStatuses returns the closed status set for an acceptance criterion. The returned slice shares memory with the package-level constant; callers must not mutate it.
func AllowedStatuses ¶
AllowedStatuses returns the closed status set for the kind. Statuses outside this set are reported by the status-valid check. Delegates to the schemas table so there is a single source of truth.
func AllowedTDDPhases ¶
func AllowedTDDPhases() []string
AllowedTDDPhases returns the closed phase set for an acceptance criterion's `tdd_phase` field. Shares memory with the package-level constant; callers must not mutate it.
func AllowedTDDPolicies ¶
func AllowedTDDPolicies() []string
AllowedTDDPolicies returns the closed policy set for a milestone's `tdd:` field. Shares memory with the package-level constant; callers must not mutate it.
func AllowedTransitions ¶
AllowedTransitions returns the statuses reachable from `from` for the given kind. Returns nil if the kind or the source status is unknown.
func BodyTemplate ¶
BodyTemplate returns the per-kind starter body that `aiwf add` writes after the frontmatter. Sections are scaffolds; bodies are not validated by `aiwf check`.
func CancelTarget ¶
CancelTarget returns the kind's terminal-cancel status — the one `aiwf cancel` promotes any non-terminal entity to. Used by the cancel verb to know which terminal status maps to "discarded": cancelled for epic/milestone, rejected for adr/decision/contract, wontfix for gap.
func IDFormat ¶
IDFormat returns a human-readable description of the kind's id shape. Used in error messages produced by the frontmatter-shape check. Delegates to the schemas table so there is a single source of truth.
func IDFromPath ¶
IDFromPath extracts the entity id encoded in an entity-bearing path, for the given kind. The id is the leading "<kind>-<digits>" portion of the relevant path component (the parent directory for epic and contract; the filename for milestone, gap, decision, and adr); any trailing slug is ignored.
Returns false if the path does not match the kind's expected shape or the extracted id does not validate. Used by the tree loader to register stub entities for files that fail to parse.
func IsAllowedACStatus ¶
IsAllowedACStatus reports whether s is a recognized AC status. Empty string returns false; the empty-string sentinel for "absent" is not itself a legal status value.
func IsAllowedStatus ¶
IsAllowedStatus reports whether status is in the kind's allowed set.
func IsAllowedTDDPhase ¶
IsAllowedTDDPhase reports whether p is a recognized TDD phase. Empty string returns false; phase absence (when the parent milestone is `tdd: none` or `tdd: advisory`) is checked separately by the caller.
func IsAllowedTDDPolicy ¶
IsAllowedTDDPolicy reports whether p is a recognized policy value. Empty string returns false; the absent-field default is `none`, but the caller is responsible for substituting that before consulting this predicate (the empty string is not itself a legal policy value).
func IsCompositeID ¶
IsCompositeID reports whether s matches the composite-id grammar `<entity-id>/AC-<digits>`. Bare ids and malformed inputs return false.
func IsLegalACTransition ¶
IsLegalACTransition reports whether (from, to) is a legal AC status transition under the FSM. Self-transitions, unknown `from`, and unknown `to` all return false. The verb-projection finding `acs-transition` (Step 6) consults this; `--force --reason` (Step 4) is what relaxes it.
func IsLegalTDDPhaseTransition ¶
IsLegalTDDPhaseTransition reports whether (from, to) is a legal transition along an AC's TDD phase FSM. Self-transitions, unknown `from`, and unknown `to` all return false.
func IsProseyTitle ¶
IsProseyTitle reports whether a title looks like prose rather than a short label. Used by `aiwf add ac` to refuse multi-sentence / markdown-formatted titles at verb time, and by `acs-title-prose` (a warning finding) to surface the same on standing trees.
Triggers: markdown formatting (`**`, `__`, backticks); link brackets (`[...](`); explicit newlines; >1 sentence (`.`/`?`/`!` followed by a space and a capital letter); length > 80 chars.
Pure function. No allocations beyond a single rune walk; safe to call in both verb projection and check.Run.
func MilestoneCanGoDone ¶
MilestoneCanGoDone reports whether the milestone's ACs are in a state that permits the milestone itself to transition to `done`. Returns (true, nil) when no AC has `status: open`; returns (false, openACs) listing the bare AC ids (`AC-N`) that are still open.
This is the AC-level precondition; the per-status milestone FSM (`in_progress → done`) is a separate check that ValidateTransition already covers. Step 6's `milestone-done-incomplete-acs` finding surfaces this on every `aiwf check` pass; Step 7's promote verb wires it into the projection.
The function is milestone-specific by intent. Calling it on other kinds returns (true, nil) trivially — non-milestone Entities never carry ACs in the schema.
func ParseACSections ¶
ParseACSections walks `### AC-N` headings in body and returns a map from AC id to that section's prose, trimmed of leading and trailing whitespace. The heading line itself is not included; only the prose under it. A subsequent `### AC-N` heading or any `## ` / `# ` heading terminates the current section.
Unrecognized `### ` headings (anything that does not start with `AC-<digits>`) are skipped — the section's body is not captured. This keeps the parser predictable when authors add free-form `### ` notes outside the AC structure. Returns nil when no `### AC-N` heading is present.
func ParseBodySections ¶
ParseBodySections walks `## `-level headings in body and returns a map from slugified heading to the section's prose, trimmed of leading and trailing whitespace. Sub-headings (`### `, `#### `, …) and prose inside the section are returned verbatim under the parent `## `.
The slug lowercases the heading, replaces every run of non-alphanumeric runes with a single `_`, and trims leading/trailing `_`. So:
## Goal → "goal" ## Out of scope → "out_of_scope" ## What's missing → "what_s_missing"
Multiple `## ` sections with the same slug collapse to the last one (last write wins). Body content before the first `## ` is dropped — the templates always start with a `## ` heading. Returns nil when no `## ` heading is present.
This is the read-side companion to BodyTemplate. It is intentionally not a full markdown parser: only `## ` boundaries matter, and a `# ` or EOF terminates the current section.
func ParseCompositeID ¶
ParseCompositeID splits a composite id into its parent and sub portions. Returns ok=false (with both strings empty) when s does not match the composite grammar — including bare ids, malformed parents, missing sub-ids, and trailing garbage.
func SectionSlug ¶
SectionSlug derives the body-section key from a `## ` heading. See ParseBodySections for the rule.
func Serialize ¶
Serialize composes an entity file's bytes: the opening "---" line, the entity's YAML frontmatter, the closing "---" line, and the body bytes verbatim. Use Split's body output as the body argument when editing an existing file, or BodyTemplate(kind) for newly-created entities.
Field order in the YAML follows the Entity struct definition: id, title, status, then per-kind fields (which appear only when set, thanks to `omitempty`). This makes output deterministic across runs.
func Slugify ¶
Slugify converts a title into a kebab-case slug suitable for use in filenames and directory names. Lowercases ASCII letters, keeps digits, collapses runs of non-alphanumerics into single hyphens, and trims leading and trailing hyphens.
Non-ASCII characters are dropped (e.g., "Café" becomes "caf"). Use SlugifyDetailed if you need to know which characters were dropped so the user can be warned.
func SlugifyDetailed ¶
SlugifyDetailed is Slugify plus the list of input runes that were silently dropped because they were non-ASCII letters/digits. The dropped list is empty when the title is purely ASCII (or contained only ASCII alphanumerics + punctuation that legitimately collapses to hyphens). Callers in the verb dispatcher use the dropped list to surface a one-line notice to the user.
func Split ¶
Split returns the YAML frontmatter bytes and the markdown body bytes from a complete entity file. Returns (nil, nil, false) when the input has no frontmatter delimiter, mirroring Parse's tolerance.
Mutating verbs use Split to preserve body prose during frontmatter edits: read → split → modify entity → Serialize(entity, body) → write.
func SubKindFromID ¶
SubKindFromID returns the sub-kind label encoded in a composite id. Currently only `"ac"` is defined (acceptance criterion). Bare ids and malformed composites return ("", false).
func ValidateID ¶
ValidateID returns nil if id matches the kind's format, or an error describing the mismatch. Used by the frontmatter-shape check.
func ValidateTransition ¶
ValidateTransition reports nil when (kind, from, to) is a legal step. Returns a descriptive error when from is unknown to the kind, when the kind itself is unknown, or when no transition from→to exists.
Types ¶
type AcceptanceCriterion ¶
type AcceptanceCriterion struct {
ID string `yaml:"id,omitempty"`
Title string `yaml:"title,omitempty"`
Status string `yaml:"status,omitempty"`
TDDPhase string `yaml:"tdd_phase,omitempty"`
}
AcceptanceCriterion is a milestone sub-element addressed by composite id `M-NNN/AC-N`. ACs are namespaced inside their parent milestone — they have no global allocator and cannot move between milestones (see docs/pocv3/design/design-decisions.md §"Acceptance criteria and TDD").
Every field is a plain string with `omitempty`; empty == absent. Closed-set membership (IsAllowedACStatus, IsAllowedTDDPhase) rules out "" as a legal value, so the empty-string sentinel is unambiguous.
AC ids are position-stable: `acs[i].ID == fmt.Sprintf("AC-%d", i+1)` for every index. Cancelled ACs stay in the slice at their original position; the allocator picks `max+1` over the full list including cancelled.
type BodySection ¶
BodySection is one `## ` section with its display heading preserved alongside the slugified key. Heading is what the markdown source actually wrote (no slug round-trip — apostrophes, spaces, and capitalization come back verbatim); Slug is the ParseBodySections key; Content is the section prose, trimmed.
Used by ParseBodySectionsOrdered when the caller cares about document order (the HTML renderer per G35/G36) instead of just the slug → content map.
func ParseBodySectionsOrdered ¶
func ParseBodySectionsOrdered(body []byte) []BodySection
ParseBodySectionsOrdered walks `## `-level headings in body and returns the sections in source order, with each section's display heading preserved alongside the slug. Same semantics as ParseBodySections for heading boundaries (`# ` or EOF terminates the current `## ` section; content before the first `## ` is dropped); duplicate slugs collapse to the last occurrence so callers can rely on slug uniqueness.
Returns nil when body is empty or has no `## ` heading.
type Cardinality ¶
type Cardinality string
Cardinality describes whether a reference field carries a single id or a list of ids.
const ( Single Cardinality = "single" Multi Cardinality = "multi" )
Cardinality values used by RefField.
type Entity ¶
type Entity struct {
// Common — present on every kind.
ID string `yaml:"id"`
Title string `yaml:"title"`
Status string `yaml:"status"`
// Lineage. PriorIDs lists the ids this entity has carried before
// the current one, oldest first. Populated by `aiwf reallocate`
// on every renumber; the resulting chain lets `aiwf history` walk
// the entity's full timeline under its current canonical id.
// Tree-only readers (HTML render, `aiwf show`, future projections)
// can resolve lineage without re-walking git log.
PriorIDs []string `yaml:"prior_ids,omitempty"`
// Milestone references.
Parent string `yaml:"parent,omitempty"`
DependsOn []string `yaml:"depends_on,omitempty"`
// Milestone — acceptance criteria and TDD policy (added in I2).
TDD string `yaml:"tdd,omitempty"`
ACs []AcceptanceCriterion `yaml:"acs,omitempty"`
// ADR chain.
Supersedes []string `yaml:"supersedes,omitempty"`
SupersededBy string `yaml:"superseded_by,omitempty"`
// Gap.
DiscoveredIn string `yaml:"discovered_in,omitempty"`
AddressedBy []string `yaml:"addressed_by,omitempty"`
AddressedByCommit []string `yaml:"addressed_by_commit,omitempty"`
// Decision.
RelatesTo []string `yaml:"relates_to,omitempty"`
// Contract.
LinkedADRs []string `yaml:"linked_adrs,omitempty"`
// Loader-set metadata, not part of YAML.
Kind Kind `yaml:"-"`
Path string `yaml:"-"`
}
Entity is the in-memory representation of a single aiwf entity, loaded from a markdown file's YAML frontmatter. The body prose is not parsed.
The struct is the union of all six kinds' frontmatter fields. Per-kind shape rules (which fields are required, which references point to which kinds) live in the check package; this struct is the data model.
func Parse ¶
Parse decodes a markdown file's YAML frontmatter into an Entity. The body (everything after the closing `---`) is discarded; aiwf does not validate body prose. Path is recorded on the returned entity for downstream finding-context.
Parse does not assign Entity.Kind. The tree loader resolves kind from the file's path before handing the entity to checks.
type ForwardRef ¶
ForwardRef describes one outbound reference from an entity. Field is the YAML key the reference came from (e.g., "parent", "addressed_by"); Target is the referenced id (bare or composite); AllowedKinds is the closed set of kinds the target may resolve to, taken from the kind's schema. An empty AllowedKinds means any kind is allowed (open-target fields like gap.addressed_by and decision.relates_to).
ForwardRef is the published shape used by both the validator (check package) and the reference-graph index (tree package). Callers that only need the (referrer, target) pair can ignore Field and AllowedKinds.
func ForwardRefs ¶
func ForwardRefs(e *Entity) []ForwardRef
ForwardRefs returns every outbound reference the entity carries, one ForwardRef per (field, target) pair. Multi-cardinality fields produce one entry per target. Empty / absent fields produce no entries.
The function is the single source of truth for "what references does an entity make" — consulted by check.refsResolve to validate them and by tree.Load to invert them into the reverse-ref index. A drift- detection test in the check package pins the result against the per- kind schema table.
type Kind ¶
type Kind string
Kind identifies one of the six aiwf entity kinds. The value is the canonical lowercase identifier used in path-discovery rules and in error messages.
const ( KindEpic Kind = "epic" KindMilestone Kind = "milestone" KindADR Kind = "adr" KindGap Kind = "gap" KindDecision Kind = "decision" KindContract Kind = "contract" )
The six aiwf entity kinds. Hardcoded; see docs/pocv3/design/design-decisions.md.
func AllKinds ¶
func AllKinds() []Kind
AllKinds returns the closed set of kinds in canonical order. Useful for iteration in checks that walk every kind.
func KindFromID ¶
KindFromID returns the kind matching the id's format. The second return is false if the id matches no kind's format. Useful for reverse-lookup when validating cross-kind references.
Composite ids (e.g. `M-007/AC-1`) resolve to their parent's kind (here, milestone). The sub-kind is reported separately by SubKindFromID.
func PathKind ¶
PathKind returns the kind implied by a file's path, relative to the consumer repo root. The second return is false if the path doesn't match any entity-bearing pattern; such files are skipped by the loader.
Recognized patterns:
work/epics/<dir>/epic.md -> epic work/epics/<dir>/M-*.md -> milestone work/gaps/G-*.md -> gap work/decisions/D-*.md -> decision work/contracts/<dir>/contract.md -> contract docs/adr/ADR-*.md -> adr
type RefField ¶
type RefField struct {
Name string `json:"name"`
Cardinality Cardinality `json:"cardinality"`
AllowedKinds []Kind `json:"allowed_kinds,omitempty"`
Optional bool `json:"optional"`
}
RefField describes one reference field on an entity. AllowedKinds is the set of kinds the target id may resolve to; an empty slice means any kind is allowed (e.g. gap.addressed_by, decision.relates_to). Optional reports whether the field is allowed to be empty/absent.
type Schema ¶
type Schema struct {
Kind Kind `json:"kind"`
IDFormat string `json:"id_format"`
AllowedStatuses []string `json:"allowed_statuses"`
RequiredFields []string `json:"required_fields"`
OptionalFields []string `json:"optional_fields,omitempty"`
References []RefField `json:"references,omitempty"`
}
Schema describes the frontmatter contract for one kind. It is the single source of truth consulted by `aiwf schema` (the published surface for skill authors) and by check.refsResolve (the runtime enforcement). Drift between the two is pinned by a regression test in the check package.
func AllSchemas ¶
func AllSchemas() []Schema
AllSchemas returns one Schema per kind, in AllKinds() order.
func SchemaForKind ¶
SchemaForKind returns the Schema for k. The second return is false if k is not one of the six aiwf kinds. The returned Schema shares slice memory with the package-level table; callers must not mutate it.