Documentation
¶
Overview ¶
Package graph implements discovery, parsing, validation, scaffolding, and navigation for GraphSpec artifact trees consumed by the `specscore graph` command group.
GraphSpec defines the language (see the specscore spec repo, features graphspec/*, and decisions 0004-0007); this package only consumes it. A consumer graph tree lives at a single graph root per repository (default spec/graph/) with modules at modules/<module-id>/README.md, artifacts in plural collection directories (entities/, relationships/, commands/, events/, policies/) as unsuffixed <id>.md files, and ModelSpec sources at modules/<module-id>/models/*.hcl (decision 0006).
Index ¶
- Constants
- Variables
- func DeriveID(name string) string
- func FindGraphRoot(repoRoot, rootRel string) (string, bool)
- func GraphRootDirs(repoRoot, rootRel string) []string
- func GraphRuleNames() []string
- func IsKebab(s string) bool
- func ResolveRef(g *Graph, ref string) (qid string, candidates []string, found bool)
- func ValidateRuleNames(names []string) error
- type Artifact
- type Concept
- type Graph
- type InputItem
- type LintOptions
- type LintResult
- type ListItem
- type MetaEntry
- type ModelDiag
- type ModelModule
- type ModelRef
- type ModelspecParseError
- type ModelspecRef
- type Module
- type ParticipantItem
- type QualifiedRef
- type RefItem
- type Resolver
- type RoleIssue
- type RuleItem
- type ScaffoldOptions
- type ScaffoldResult
Constants ¶
const ( KindModule = "module" KindEntity = "entity" KindRelationship = "relationship" KindCommand = "command" KindEvent = "event" KindPolicy = "policy" )
Kind tokens — the six GraphSpec kinds (decision 0004, amended by decision 0013 which adds policy). Value objects and enums are ModelSpec concepts, never GraphSpec kinds.
const DefaultGraphRoot = "spec/graph"
DefaultGraphRoot is the default repository-level graph root relative to the repo root (decision 0005). Per decision 0009 a repository's graph is the UNION of this root plus one per configured SpecScore module.
const ModelspecScheme = "modelspec://"
ModelspecScheme is the reference scheme for graph-to-ModelSpec references.
Variables ¶
var ArtifactCollections = []string{"entities", "relationships", "commands", "events", "policies"}
ArtifactCollections lists the five artifact-bearing collection directories, in canonical order (models/ excluded — it holds HCL, not graph artifacts).
var CollectionDirs = []string{"entities", "relationships", "commands", "events", "policies", "models"}
CollectionDirs lists the six directories a full module scaffold carries (five artifact collections plus models/ for ModelSpec sources).
var Kinds = []string{KindModule, KindEntity, KindRelationship, KindCommand, KindEvent, KindPolicy}
Kinds lists the six GraphSpec kinds in canonical order.
var ModelspecKindTokens = map[string]string{
"entities": "entity",
"components": "component",
"enums": "enum",
"collections": "collection",
"recordsets": "recordset",
}
ModelspecKindTokens maps the plural kind segment of a modelspec:// reference (decision 0011) to the singular ModelSpec concept kind it addresses. The kind segment is OPTIONAL: the two-segment form modelspec:///<module>.<Name> resolves against the flat entity/component/enum trio; the three-segment form modelspec:///<module>.<kind>.<Name> names one of these five kinds explicitly.
var ReservedConceptNames = map[string]bool{ "entities": true, "components": true, "enums": true, "collections": true, "recordsets": true, }
ReservedConceptNames is the set of tokens (decision 0011 / ModelSpec decision 0015) forbidden as ModelSpec concept names in any scope: they are the kind segments of consumer reference syntax, so reserving them keeps kind-explicit references unambiguous without lookahead.
Functions ¶
func DeriveID ¶
DeriveID derives a bare kebab-case id from a display name (decision 0005): `CreateBooking` and `teamMember` both derive dash-separated ids (`create-booking`, `team-member`); non-alphanumerics become dashes.
func FindGraphRoot ¶
FindGraphRoot resolves the repository-level graph root for repoRoot. rootRel overrides the default spec/graph when non-empty. It returns the absolute path and whether the directory exists.
func GraphRootDirs ¶
GraphRootDirs enumerates the union of existing graph roots for repoRoot: the repository-level root (rootRel override or spec/graph) plus one per configured SpecScore module (<module.path>/<specs_dir>/graph), resolved through the existing repo-config machinery (decision 0009). Absent roots are omitted; duplicates (same cleaned path) are collapsed. Order is repo-level first, then module roots in configuration order.
func GraphRuleNames ¶
func GraphRuleNames() []string
GraphRuleNames returns the sorted set of valid graph lint rule names.
func ResolveRef ¶
ResolveRef resolves a <ref> argument to a single qualified id. It matches a qualified id first, then a shorthand (bare id or display name). candidates is populated (sorted) when the shorthand is ambiguous; found is false when nothing matches.
func ValidateRuleNames ¶
ValidateRuleNames returns an error naming the first unknown graph rule.
Types ¶
type Artifact ¶
type Artifact struct {
Path string // absolute path
Stem string // filename stem (README for modules, <id> otherwise)
Module string // owning module id, derived from placement
CollectionDir string // "entities"/"relationships"/... or "" for a module README
// Raw frontmatter presence — a key being present (even if empty) matters
// for several rules (owner, fields/properties, sources, lifecycle).
HasFrontmatter bool
FMError string
// Decoded scalar fields.
Kind string
ID string
Name string
Status string
Summary string
// module
DependsOn []string
// entity
Model string
LifecycleStates []string
LifecycleStatesPresent bool
// relationship. From/To always carry the endpoint reference; FromRole/
// ToRole carry the optional decision-0012 role labels of the map form.
From string
FromRole string
To string
ToRole string
Cardinality string
Metadata []MetaEntry
// command / event. Participants carries the references (scalar view);
// ParticipantItems carries the role-labeled per-item view (decision 0012).
Subject string
Actors []string
Participants []string
ParticipantItems []ParticipantItem
Inputs []InputItem
PossibleEvents []string
Sources []string
// RoleIssues collects endpoint/participant shape violations (decision
// 0012) for the graph-role-labels rule.
RoleIssues []RoleIssue
// Rules carries the Tier-1 rules: list of entity/relationship/command
// artifacts (decision 0013); rulesMalformed records a non-list container
// or non-map items, mirroring the inputs pattern.
Rules []RuleItem
// policy (decision 0013). AppliesKind/AppliesRef are set when the applies:
// block carries exactly one of command|entity|relationship; the raw clause
// structures back the graph-policy-shape rule.
AppliesKind string
AppliesRef string
// contains filtered or unexported fields
}
Artifact is a parsed GraphSpec artifact (module README or a collection artifact). Parse is resilient: a non-nil Artifact is returned for every readable file. FMError is set when the file has no leading frontmatter block or the block is malformed YAML; callers treat that as a hard violation.
func ParseArtifact ¶
ParseArtifact reads a GraphSpec artifact file and decodes its frontmatter. module and collectionDir are supplied by discovery (placement-derived).
func (*Artifact) KeyLine ¶
KeyLine returns the 1-based file line of the named top-level frontmatter key, or 0 when the key is absent or its line is unknown.
func (*Artifact) QualifiedID ¶
QualifiedID returns the computed <module>.<id> form (decision 0005). When the artifact has no owning module the bare id is returned.
type Concept ¶
type Concept struct {
Name string
Kind string // "entity", "component", "enum", "collection", or "recordset"
File string // absolute path to the .hcl file
Line int
EnumValues []string // populated for enum concepts only
Properties []string // property/field member names (entity/component concepts; decision 0013 actor-is.model-role)
}
Concept is one ModelSpec concept declared in a module's HCL sources: an entity, component, enum, collection, or recordset (decision 0006; ModelSpec core-model; decision 0011 addressable concepts).
type Graph ¶
type Graph struct {
Root string // absolute repo-level graph root (may not exist)
Roots []string // absolute paths of every existing graph root in the union
RepoRoot string // absolute repo root (contains specscore.yaml)
Modules []*Module
}
Graph is a fully discovered graph: the union of the repository's graph roots and their modules (decision 0009).
func Load ¶
Load discovers the union of graph roots for repoRoot (with an optional rootRel override on the repository-level root). ok is false when NO graph root exists — callers treat that as the "no graph root" notice case, not an error.
func (*Graph) AllArtifacts ¶
AllArtifacts returns every collection artifact across all modules, plus each module README, in a deterministic order (module id, then path).
func (*Graph) ModuleByID ¶
ModuleByID returns the first module with the given id, or nil.
type InputItem ¶
type InputItem struct {
Name string
Ref string
Model string
HasName bool
HasRef bool
HasModel bool
ExtraKeys []string
Line int
}
InputItem is one entry of a CommandSpec `inputs:` list. Each item carries a name plus exactly one of ref: (qualified graph ref) or model: (modelspec:// ref). Presence flags and ExtraKeys let the inputs-shape rule report items with both, neither, or embedded structure.
type LintOptions ¶
type LintOptions struct {
RepoRoot string // absolute repo root (contains specscore.yaml)
Root string // graph-root override relative to RepoRoot; "" = spec/graph
Rules []string // enabled rules; nil = all
Ignore []string // disabled rules
Severity string // minimum severity: error|warning|info; "" = no filter
Fix bool // when true, apply the specified graph fixers on disk
}
LintOptions configures a graph lint pass.
type LintResult ¶
LintResult is the outcome of a graph lint pass. NoGraphRoot is true when the repository has no graph root — a notice case, not a violation. Fixed lists the repo-root-relative paths of files changed by the fix pass (only when opts.Fix is true).
func Lint ¶
func Lint(opts LintOptions) (LintResult, error)
Lint discovers the graph root and runs every enabled graph rule.
type ListItem ¶
type ListItem struct {
ID string `json:"id" yaml:"id"`
Kind string `json:"kind" yaml:"kind"`
Name string `json:"name" yaml:"name"`
Owner string `json:"owner" yaml:"owner"`
Path string `json:"path" yaml:"path"`
}
ListItem is one row of `graph list`. Owner is the owning module id, derived from placement (a module row owns itself).
type MetaEntry ¶
type MetaEntry struct {
Key string
Value string // scalar text when Scalar is true
Scalar bool
Line int
}
MetaEntry is one key of a RelationshipSpec `metadata:` flat map. Scalar records whether the value is a scalar (string/number/bool); when false the value was a nested map or list, which the metadata-shape rule rejects.
type ModelModule ¶
type ModelModule struct {
ID string
Dir string
Concepts []*Concept
Refs []*ModelRef
ParseErrors []ModelDiag
}
ModelModule is the parsed ModelSpec module formed by all *.hcl files under a graph module's models/ directory (decision 0006: one models/ dir = one ModelSpec module whose short name is the graph module id).
func LoadModelModule ¶
func LoadModelModule(dir, id string) (*ModelModule, error)
LoadModelModule parses every *.hcl file under dir into one ModelModule.
func (*ModelModule) HasConcept ¶
func (m *ModelModule) HasConcept(name string) bool
HasConcept reports whether the module declares a concept with the given name in any kind (used for bare same-module HCL references, which are attribute-typed and therefore kind-unambiguous at the source level).
type ModelRef ¶
type ModelRef struct {
Target string
Attr string // "entity", "component", "enum", or "use"
Owner string // declaring concept name
File string
Line int
}
ModelRef is one module-qualified or bare reference inside HCL sources — a property/field type reference (entity/component/enum) or an entity `use` entry (decision 0014). Target is bare (<Name>) for same-module references or qualified (<module>.<Name>) for cross-module references. Owner is the name of the concept whose body declares the reference — navigation uses it to derive graph edges from model structure (association-object visibility).
type ModelspecParseError ¶
type ModelspecParseError struct {
Rewrite string
// contains filtered or unexported fields
}
ModelspecParseError is the typed error returned by ParseModelspecRef. Rewrite is set only for the legacy-form class and carries the exact corrected reference the --fix rewriter applies.
func (*ModelspecParseError) Error ¶
func (e *ModelspecParseError) Error() string
type ModelspecRef ¶
type ModelspecRef struct {
Module string
Kind string // singular: entity|component|enum|collection|recordset; "" = two-segment (trio)
Name string
Repo string // "{host}/{org}/{repo}" for cross-repo, or "" for local
Ref string // advisory ?ref= git pin (branch/tag/commit), or ""
Fragment string // '#<value>' enum-value address (decision 0013), or ""
}
ModelspecRef is a parsed modelspec:// reference (decisions 0010/0011). The local form (modelspec:///<...>) carries an empty Repo; the cross-repo form (modelspec://{host}/{org}/{repo}/<...>) fills Repo with "host/org/repo". Kind is the singular concept kind for the three-segment form, or "" for the two-segment (trio) form. Ref carries an advisory ?ref= git pin, if any. Fragment carries a '#<value>' enum-value address (decision 0013), if any: it resolves only when the referenced concept is an enum declaring that value.
func ParseModelspecRef ¶
func ParseModelspecRef(ref string) (ModelspecRef, error)
ParseModelspecRef parses a modelspec:// reference into its parts. On failure it returns a *ModelspecParseError whose class the linter inspects (legacy form carries the exact --fix rewrite; other classes carry a clear message).
type Module ¶
type Module struct {
ID string
Root string // absolute path to the module directory
Readme *Artifact // parsed README.md frontmatter, or nil when absent
DependsOn []string // declared outbound dependencies (from README)
Artifacts []*Artifact // collection artifacts owned by this module
ModelDir string // absolute path to models/, or "" when absent
Model *ModelModule
}
Module is one GraphSpec module discovered under a graph root's modules/<id>/ directory. Identity is by placement: ID is the directory name (decision 0006).
type ParticipantItem ¶ added in v0.17.0
ParticipantItem is one entry of an EventSpec `participants:` list — either a scalar qualified reference or a role-labeled {ref, role} map (decision 0012).
type QualifiedRef ¶
QualifiedRef is a parsed graph-to-graph reference of the form <module>.<local-id> (decision 0005).
func ParseQualifiedRef ¶
func ParseQualifiedRef(ref string) (QualifiedRef, bool)
ParseQualifiedRef splits a <module>.<local-id> reference. ok is false when the value is not exactly module + one dot + local, both non-empty, or carries a modelspec:// scheme.
type RefItem ¶
type RefItem struct {
ID string `json:"id" yaml:"id"`
Kind string `json:"kind" yaml:"kind"`
Path string `json:"path" yaml:"path"`
}
RefItem is one referencing artifact in `graph refs` output.
func Refs ¶
Refs returns the artifacts that reference targetQID (inbound), including edges derived from ModelSpec structure (an entity whose model carries an entity-reference property references the entity whose model is the target concept — association-object visibility). With transitive, it returns the full set of artifacts that can reach targetQID through reference chains, cycle-safe.
type Resolver ¶
type Resolver struct {
// contains filtered or unexported fields
}
Resolver resolves module short names to ModelSpec modules across the three decision-0007 steps: local graph root, configured `projects:` local paths, then explicit @{host}/{org}/{repo} suffixes for locally-available repos.
func BuildResolver ¶
BuildResolver constructs the resolver for graph g rooted at repoRoot, reading repoRoot/specscore.yaml for configured `projects:` local paths. A missing or unreadable config yields a local-only resolver — never an error, so lint stays deterministic and offline.
type RoleIssue ¶ added in v0.17.0
RoleIssue is a shape violation found while decoding a role-labeled endpoint or participant (decision 0012); the graph-role-labels rule reports them.
type RuleItem ¶ added in v0.18.0
type RuleItem struct {
ID string
Text string
Refs []string
HasID bool
HasText bool
ExtraKeys []string
Line int
// contains filtered or unexported fields
}
RuleItem is one entry of a Tier-1 `rules:` list (decision 0013): {id, text, refs?}. Presence flags and ExtraKeys let the graph-rules-shape rule report missing keys and unknown structure; refsMalformed records a refs value that is not a list of scalar references.
type ScaffoldOptions ¶
type ScaffoldOptions struct {
Kind string
Name string
ID string // optional; derived from Name when empty
Module string // required for non-module kinds
RepoRoot string // absolute repo root
Root string // repo-level graph-root override (relative); "" = spec/graph
Status string // default "draft"
Summary string
From string // relationship
To string // relationship
Subject string // command / event
Bare bool // module: skip collection scaffolding
}
ScaffoldOptions configures `graph new`.
type ScaffoldResult ¶
ScaffoldResult reports the created files (repo-relative, sorted) and the primary artifact path.
func Scaffold ¶
func Scaffold(opts ScaffoldOptions) (ScaffoldResult, error)
Scaffold writes a new GraphSpec artifact and returns the created files. It performs the collision check before any write (no partial writes) and returns exitcode-carrying errors for the documented failure modes.