model

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Apr 28, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// DefaultGraphDir is the conventional graph directory relative to repo root
	// when initialized with sdd init.
	DefaultGraphDir = ".sdd/graph"

	// SDDDirName is the metadata directory name at the repository root.
	SDDDirName = ".sdd"

	// DefaultLLMProvider is the provider used when none is configured. The
	// claude CLI bridge runs via the user's logged-in Claude Code session, so
	// no API key is required for first-run usage.
	DefaultLLMProvider = "claude-cli"

	// DefaultLLMModel is the claude model used when none is configured.
	DefaultLLMModel = "claude-haiku-4-5-20251001"

	// DefaultLLMConcurrency is the default worker count for concurrent
	// LLM calls (e.g. sdd summarize --all).
	DefaultLLMConcurrency = 4

	// DefaultSyncCooldown bounds how often background sync runs git fetch
	// when last-fetch exceeds this duration. Applied when Config.Sync.Cooldown
	// is empty or unparseable.
	DefaultSyncCooldown = "15m"
)
View Source
const (
	SkillStampVersion = "sdd-version"
	SkillStampHash    = "sdd-content-hash"
)

Stamp frontmatter keys injected into every installed skill file. Stripped before hashing so the stamps themselves don't pollute the content hash.

View Source
const CurrentGraphSchemaVersion = 1

CurrentGraphSchemaVersion is the schema version this binary knows how to read and write. Bumped only by an explicit schema migration.

View Source
const DefaultAgentTarget = AgentClaude

DefaultAgentTarget is the target used when nothing is specified. Only one agent is wired at MVP.

View Source
const DefaultScope = ScopeUser

DefaultScope is the scope used when nothing is specified.

View Source
const SchemaMetaFileName = "meta.json"

SchemaMetaFileName is the committed metadata file under .sdd/ that records graph schema version and the oldest binary permitted to write to the graph.

Variables

View Source
var LayerAbbrev = map[Layer]string{
	LayerStrategic:   "stg",
	LayerConceptual:  "cpt",
	LayerTactical:    "tac",
	LayerOperational: "ops",
	LayerProcess:     "prc",
}

LayerAbbrev maps full layer names to abbreviations used in IDs.

View Source
var LayerFromAbbrev = map[string]Layer{
	"stg": LayerStrategic,
	"cpt": LayerConceptual,
	"tac": LayerTactical,
	"ops": LayerOperational,
	"prc": LayerProcess,
}

LayerFromAbbrev maps abbreviations to full layer names.

View Source
var TypeAbbrev = map[EntryType]string{
	TypeSignal:   "s",
	TypeDecision: "d",
}

TypeAbbrev maps full type names to abbreviations used in IDs.

View Source
var TypeFromAbbrev = map[string]EntryType{
	"s": TypeSignal,
	"d": TypeDecision,
}

TypeFromAbbrev maps abbreviations to full type names.

Functions

func AttachDirRelPath

func AttachDirRelPath(id string) (string, error)

AttachDirRelPath returns the relative path to the attachment directory for an entry ID. This is the entry's file path without the .md extension.

func CanonicalizeFrontmatter added in v0.2.0

func CanonicalizeFrontmatter(fm map[string]any) []byte

CanonicalizeFrontmatter returns a deterministic byte representation of fm suitable for hashing. The encoding is JSON with sorted keys; json.Marshal on map[string]any sorts keys alphabetically at every depth, giving a stable ordering across YAML read/write cycles that don't preserve order.

The output has no trailing newline. Callers embedding it in a larger hash stream must add their own separator.

func ComputeSkillHash added in v0.2.0

func ComputeSkillHash(fileContent []byte) string

ComputeSkillHash returns the canonical content hash for a skill file. The hash is computed over the canonicalized frontmatter (with the sdd-version and sdd-content-hash keys stripped) concatenated with the body bytes. The result is a lowercase hex-encoded sha256 digest.

Embedded entries carry no stamps, so their hash equals a freshly-written file's computed hash — this is the equality that lets a previously-installed pristine file match its embedded source across version bumps.

func CountGraphCommits added in v0.3.0

func CountGraphCommits(gitLogOutput string) int

CountGraphCommits counts non-empty lines from `git log --grep='^sdd:' --pretty=format:%H <range>`. Each line is one SDD-written commit in the range; we only need the count, not the hashes themselves.

func DeriveBranchName

func DeriveBranchName(entryID, description string) string

DeriveBranchName creates a branch name from an entry ID and description. Format: sdd/<entry-suffix>-<description-slug>

func FormatConfig

func FormatConfig(cfg Config) string

FormatConfig returns a commented YAML config template with the given graph dir. If cfg.Language is set, the locale is written as an active `language: <code>` entry. Otherwise a commented hint is emitted instead so the option stays discoverable in the file.

func FormatFrontmatter

func FormatFrontmatter(e *Entry) string

FormatFrontmatter creates the YAML frontmatter string for an entry.

func FormatSchemaMeta added in v0.2.0

func FormatSchemaMeta(m SchemaMeta) ([]byte, error)

FormatSchemaMeta returns a pretty-printed JSON representation with a trailing newline, suitable for writing to disk.

func FormatWIPMarker

func FormatWIPMarker(m *WIPMarker) string

FormatWIPMarker creates the file content for a WIP marker.

func GenerateID

func GenerateID(typ EntryType, layer Layer, suffix string) string

GenerateID creates a new document ID with the current timestamp and a random suffix.

func GenerateIDAt

func GenerateIDAt(typ EntryType, layer Layer, suffix string, t time.Time) string

GenerateIDAt creates a new document ID with the given timestamp and a random suffix. Accepts the time explicitly so callers can inject a clock for testability.

func GenerateWIPMarkerID

func GenerateWIPMarkerID(participant string) string

GenerateWIPMarkerID creates a marker ID from the current timestamp and participant name.

func IDToRelPath

func IDToRelPath(id string) (string, error)

IDToRelPath converts an entry ID to its relative file path in the hierarchical layout. ID format: YYYYMMDD-HHmmss-type-layer-suffix Path format: YYYY/MM/DD-HHmmss-type-layer-suffix.md

func IsDevVersion added in v0.2.0

func IsDevVersion(v string) bool

IsDevVersion reports whether v is a non-release build. Dev builds (including the literal "dev", commit hashes, or any non-semver string) bypass write gates to avoid blocking local development against a released graph.

func IsValidKindForType added in v0.2.0

func IsValidKindForType(t EntryType, k Kind) bool

IsValidKindForType reports whether k is an allowed kind for the given type. Empty kind is allowed at this layer — defaults are applied separately during entry construction (see DefaultKindForType).

func IsWIPDir

func IsWIPDir(d fs.DirEntry) bool

IsWIPDir returns true if the given directory entry represents the wip/ directory.

func ParseMergeTreeConflicts added in v0.3.0

func ParseMergeTreeConflicts(output string) []string

ParseMergeTreeConflicts parses the output of `git merge-tree --write-tree --name-only --no-messages HEAD @{u}`. On a clean merge, output is a single tree-OID line → returns nil. On a conflict, output is the tree OID followed by conflicted file paths → returns the paths. A blank line terminates the path list; any trailing informational/conflict-message section (present when --no-messages is omitted) is ignored, so the parser stays correct even if the caller forgets the flag.

func RandomSuffix

func RandomSuffix(n int) (string, error)

RandomSuffix returns an n-character lowercase alphanumeric string suitable for use as the trailing random portion of a document ID.

func RelPathToID

func RelPathToID(rel string) (string, error)

RelPathToID converts a relative path (YYYY/MM/DD-HHmmss-type-layer-suffix.md) to a full entry ID.

func RenderSkillFile added in v0.2.0

func RenderSkillFile(entry SkillBundleEntry, version, contentHash string) ([]byte, error)

RenderSkillFile reassembles frontmatter and body into a skill file with the given install-time stamps injected. If the entry has no original frontmatter, a fresh block with just the stamps is written.

The returned bytes are what gets hashed by ComputeSkillHash once the stamps are stripped — callers writing new installations should compute the hash from the embedded entry content (which has no stamps) to populate the sdd-content-hash stamp itself.

func ResolveAttachmentLinks(content, id string) string

ResolveAttachmentLinks replaces {{attachments}} placeholders in content with the actual relative directory path for markdown links.

func ResolveSyncCooldown added in v0.3.0

func ResolveSyncCooldown(cfg *Config) time.Duration

ResolveSyncCooldown returns the effective cooldown duration from cfg, falling back to DefaultSyncCooldown on empty or unparseable values.

func RewriteID added in v0.2.0

func RewriteID(id string, newType EntryType) (string, error)

RewriteID returns the id with its type abbreviation replaced by newType's abbreviation. Timestamp, layer, and suffix are preserved. Used by the sdd rewrite command for mechanical type changes.

func SetYAMLField added in v0.3.0

func SetYAMLField(existing []byte, path string, value any) ([]byte, error)

SetYAMLField sets the scalar at dotted path to value in an existing YAML document, returning new bytes. Unknown keys, user comments, and the order of sibling keys are preserved — only the targeted scalar (or the minimal set of missing ancestors) is touched. An empty input is treated as an empty document; the result is a fresh mapping containing just the target key chain.

Value may be anything yaml.Node.Encode accepts as a scalar (string, int, bool, nil, etc.). Sequences and nested mappings are out of scope — targeting a non-scalar leaf (or a slice/map value) is rejected.

Path uses dotted segments (e.g. "participant", "llm.provider"). No support for array indexing or filter expressions — keep paths flat. Empty segments (leading/trailing/consecutive dots) are rejected.

The port of the JSONPath-capable vignet patcher was intentional: SDD's settings are flat config, so the simpler dotted-path form covers the need without the extra dependency.

func SkillInstallDir added in v0.2.0

func SkillInstallDir(target AgentTarget, scope Scope, repoRoot, userHome string) (string, error)

SkillInstallDir returns the absolute directory where skills install for a given agent target and scope.

Claude skills install at <home>/.claude/skills (user scope) or <repoRoot>/.claude/skills (project scope). The function errors if the required base path is empty for the chosen scope — callers should populate UserHome or RepoRoot before the query reaches the finder.

func ValidateEntry

func ValidateEntry(e *Entry, g *Graph)

ValidateEntry checks a single entry for integrity issues and populates its Warnings field. Used both at lint time (all entries) and at write time (new entry before commit).

func WIPDir

func WIPDir(graphDir string) string

WIPDir returns the path to the wip/ directory within a graph directory.

func WIPMarkerPath

func WIPMarkerPath(markerID string) string

WIPMarkerPath returns the path to a marker file relative to the graph directory.

Types

type ActorChain added in v0.3.0

type ActorChain struct {
	// Entries are all actor signals in the chain, ordered by time (oldest
	// first).
	Entries []*Entry
	// Head is the chain's current head — the actor signal not superseded
	// by any other entry in the chain. Capture-time checks resolve against
	// the head's canonical; derivation-time checks walk CanonicalHistory.
	Head *Entry
	// CanonicalHistory is the ordered list of distinct canonicals ever
	// carried by entries in this chain, oldest first.
	CanonicalHistory []string
	// contains filtered or unexported fields
}

ActorChain represents a supersession chain of kind: actor signals — all actor entries linked by supersedes (transitively) plus their shared canonical history. Canonicals within a chain may change across entries (e.g., typo correction supersedes an earlier spelling); across chains each canonical is write-once (see validateActorInvariant in lint).

func (*ActorChain) HasCanonical added in v0.3.0

func (c *ActorChain) HasCanonical(name string) bool

HasCanonical reports whether the chain has ever held canonical c.

type AgentTarget added in v0.2.0

type AgentTarget string

AgentTarget identifies which agent a skill bundle installs for. Claude is the only supported target for MVP; the type exists so the install flow can grow additional agents (Codex, etc.) without structural change.

const (
	// AgentClaude installs into Claude Code's skill directories
	// (~/.claude/skills or <repo>/.claude/skills).
	AgentClaude AgentTarget = "claude"
)

type CompatibilityResult added in v0.2.0

type CompatibilityResult struct {
	// Compatible is true when the binary may proceed. Dev builds are always
	// compatible (see IsDevBuild).
	Compatible bool

	// IsDevBuild is true when the binary version is non-semver; both gates
	// are bypassed.
	IsDevBuild bool

	// Reason is a human-readable explanation when !Compatible.
	Reason string
}

CompatibilityResult reports whether a binary is permitted to write to a graph given its .sdd/meta.json contents.

func CheckCompatibility added in v0.2.0

func CheckCompatibility(meta SchemaMeta, binaryVersion string, binarySchemaVersion int) CompatibilityResult

CheckCompatibility evaluates meta against the binary's own version and declared schema version.

Dev builds (non-semver binary versions) are always compatible; both gates are bypassed so local development isn't blocked by a released graph's minimum_version or a bumped schema_version.

For released binaries: a schema_version mismatch errors with a pointer to sdd init / sdd migrate. A minimum_version higher than the binary errors with upgrade guidance. Either failure leaves Compatible = false.

type Config

type Config struct {
	GraphDir string     `yaml:"graph_dir,omitempty"`
	LLM      LLMConfig  `yaml:"llm,omitempty"`
	Sync     SyncConfig `yaml:"sync,omitempty"`
	// Participant is the canonical name used for entry authorship when
	// --participants / --participant is omitted at capture time. Lives in
	// .sdd/config.local.yaml (gitignored) because the same person may use
	// different spellings across projects.
	Participant string `yaml:"participant,omitempty"`
	// Language is a locale code (e.g. "de", "en", "de-DE") that governs the
	// graph's authored language. Captured entries are written in this
	// language; the /sdd skill renders translated vocabulary to users via
	// bundled translation references. Empty means English (default).
	Language string `yaml:"language,omitempty"`
}

Config represents the contents of .sdd/config.yaml (shared, committed) or .sdd/config.local.yaml (gitignored, per-machine). Both files unmarshal into the same struct; the local file overlays the shared file via MergeConfig. Empty / zero-valued fields in the local file mean "inherit from shared", so any subset of fields can appear in either file.

func MergeConfig added in v0.2.0

func MergeConfig(base, overlay *Config) *Config

MergeConfig returns a new Config with fields from overlay overriding base wherever the overlay value is non-empty/non-zero. APIKeys are merged key-by-key so overlay entries replace individual providers without clobbering the full map. A nil overlay returns a copy of base.

func ParseConfig

func ParseConfig(data []byte) (*Config, error)

ParseConfig unmarshals YAML bytes into a Config struct. Empty input is valid and yields a zero-valued Config.

type Entry

type Entry struct {
	ID           string
	Type         EntryType
	Layer        Layer
	Kind         Kind // only meaningful for decisions; empty = directive (default)
	Refs         []string
	Supersedes   []string
	Closes       []string
	Participants []string
	Confidence   string
	Content      string
	Time         time.Time
	// Canonical and Aliases are only meaningful on kind: actor signals.
	// Canonical is the write-once identity string used in participants fields;
	// Aliases are read-side conveniences for mining and dialogue comprehension.
	Canonical string
	Aliases   []string
	// Actor is only meaningful on kind: role decisions. It names the canonical
	// of the actor-identity chain the role binds to. Role status derives from
	// the actor chain's canonical history (see Graph.RoleStatus).
	Actor       string
	Preflight   string    // "skipped" or "error" annotation from pre-flight validation
	Attachments []string  // filenames discovered from the co-located attachment directory
	Summary     string    // LLM-generated summary: this entry + direct relationships
	SummaryHash string    // hex-encoded hash of the rendered summary prompt inputs
	Warnings    []Warning // validation issues found during graph construction
}

func ParseEntry

func ParseEntry(filename, content string) (*Entry, error)

ParseEntry parses a graph entry from its filename and file content.

func (*Entry) IsActor added in v0.3.0

func (e *Entry) IsActor() bool

IsActor returns true if this signal records a participant identity.

func (*Entry) IsAspiration added in v0.2.0

func (e *Entry) IsAspiration() bool

IsAspiration returns true if this decision is a perpetual direction.

func (*Entry) IsContract

func (e *Entry) IsContract() bool

IsContract returns true if this decision is a standing constraint.

func (*Entry) IsPlan

func (e *Entry) IsPlan() bool

IsPlan returns true if this decision is an implementation plan.

func (*Entry) IsRole added in v0.3.0

func (e *Entry) IsRole() bool

IsRole returns true if this decision commits a participation pattern.

func (*Entry) LayerLabel

func (e *Entry) LayerLabel() string

LayerLabel returns a display label for the layer.

func (*Entry) ShortContent

func (e *Entry) ShortContent(maxLen int) string

ShortContent returns content truncated to maxLen, preferring sentence boundaries. Accumulates complete sentences up to the limit. If no sentence fits, accumulates words.

func (*Entry) TypeLabel

func (e *Entry) TypeLabel() string

TypeLabel returns a display label for the entry type.

type EntryType

type EntryType string
const (
	TypeSignal   EntryType = "signal"
	TypeDecision EntryType = "decision"
)

type Graph

type Graph struct {
	Entries      []*Entry
	ByID         map[string]*Entry
	RefsTo       map[string][]string // reverse index: entry ID -> IDs that reference it
	ClosedBy     map[string][]string // reverse index: entry ID -> IDs that close it
	SupersededBy map[string][]string // reverse index: entry ID -> IDs that supersede it
	// contains filtered or unexported fields
}

Graph holds all entries and their reference indexes.

func NewGraph

func NewGraph(entries []*Entry) *Graph

NewGraph builds a graph from the given entries without touching the filesystem.

func (*Graph) ActiveActorHeads added in v0.3.0

func (g *Graph) ActiveActorHeads() []*Entry

ActiveActorHeads returns the head actor signal of every active actor-identity chain — the head must not be closed. Ordered by head ID.

func (*Graph) ActiveRoles added in v0.3.0

func (g *Graph) ActiveRoles() []*Entry

ActiveRoles returns all kind: role decisions that are derived-active by the cascade (DerivedStatus == StatusActive). Ordered by bound actor canonical then role entry ID so presenters can group without re-sorting.

func (*Graph) Activities added in v0.2.0

func (g *Graph) Activities() []*Entry

Activities returns active activity decisions (not closed, not superseded). Activities are THAT-shaped commitments — capturing that specific work happens, independent of the directive-style choice of *what* to do.

func (*Graph) ActorChains added in v0.3.0

func (g *Graph) ActorChains() []*ActorChain

ActorChains groups all kind: actor signals into supersession chains. Each chain surfaces its head, full entry list, and canonical history. Pure computation — no I/O. Returned in deterministic order by head ID.

func (*Graph) Actors added in v0.3.0

func (g *Graph) Actors() []*Entry

Actors returns all kind: actor signals regardless of chain status. Used by list/filter paths.

func (*Graph) AllParticipants added in v0.3.0

func (g *Graph) AllParticipants() []string

AllParticipants returns the sorted unique set of participant names that appear on any entry in the graph. Empty strings are excluded. Used by the pre-flight participant-drift check to flag names that don't match any established spelling — the caller compares each proposed name against this set.

func (*Graph) Aspirations added in v0.2.0

func (g *Graph) Aspirations() []*Entry

Aspirations returns active aspiration decisions (not superseded, not closed). Like contracts, aspirations are durable — they retire via supersede or close-by-directive with rationale (see dissolution/retirement calibration).

func (*Graph) BuildShowTree

func (g *Graph) BuildShowTree(id string, maxDepth int, includeDownstream bool, rendered, primaries map[string]bool) *ShowTree

BuildShowTree constructs the upstream and optionally downstream traversal trees for a primary entry, respecting max depth, cross-group dedup (rendered), and future-primary dedup (primaries). Both directions use per-direction visited sets. The rendered map is updated with newly-shown entries.

func (*Graph) ChainForCanonical added in v0.3.0

func (g *Graph) ChainForCanonical(canonical string) *ActorChain

ChainForCanonical returns the actor-identity chain that has ever held the given canonical. Returns nil when no chain matches. The write-once-across- chains invariant guarantees at most one match in a well-formed graph; callers that care about violations should use ChainsForCanonical.

func (*Graph) ChainsForCanonical added in v0.3.0

func (g *Graph) ChainsForCanonical(canonical string) []*ActorChain

ChainsForCanonical returns every actor-identity chain that has ever held the given canonical. In a well-formed graph this is zero or one; multiple results indicate a violation of the write-once-across-chains invariant and are surfaced by lint.

func (*Graph) Contracts

func (g *Graph) Contracts() []*Entry

Contracts returns active contract decisions (not superseded, not closed). Contracts retire via a same-kind supersede or a directive-kind decision closing them with rationale (universal retirement rule).

func (*Graph) DerivedStatus

func (g *Graph) DerivedStatus(e *Entry) Status

DerivedStatus returns the computed lifecycle status for an entry, derived from graph relationships. Superseded is checked before closed so a superseded-then-closed entry (rare) surfaces as superseded. When multiple entries close or supersede the target, the first one (by graph insertion order) is reported.

Role decisions additionally derive status via the actor-chain cascade (see ActorChain / RoleStatus in actor.go). A role whose bound actor chain has a closed head surfaces as StatusCascadeClosedBy. A role whose Actor does not match any chain's canonical history surfaces as StatusCascadeOrphan — an abnormal state flagged by lint.

func (*Graph) Directives added in v0.2.0

func (g *Graph) Directives() []*Entry

Directives returns active directive decisions (not closed, not superseded). Allow-list shape keeps future decision kinds from silently flooding this set — each kind gets surfaced deliberately with its own accessor.

func (*Graph) Downstream

func (g *Graph) Downstream(id string) []*Entry

Downstream returns entries that reference, close, or supersede the given ID. Results are sorted by time (oldest first).

func (*Graph) Filter

func (g *Graph) Filter(f GraphFilter) []*Entry

Filter returns entries matching the given filter criteria. Zero-value fields match all. Kind matches across both signal and decision kinds — the two sets are disjoint, so `Kind: KindGap` selects signals and `Kind: KindPlan` selects decisions without further narrowing.

func (*Graph) GraphDir

func (g *Graph) GraphDir() string

GraphDir returns the directory the graph was loaded from.

func (*Graph) Lint

func (g *Graph) Lint() []*Entry

Lint returns all entries that have validation warnings.

func (*Graph) OpenSignals

func (g *Graph) OpenSignals() []*Entry

OpenSignals returns signals that are closure-gated attention items — gaps awaiting a decision/done and questions awaiting dissolution. Facts, insights, and done signals are deliberately excluded: facts and insights are stable observational records (retired via directive close, not resolved), and done signals are terminal facts of execution. The allow-list shape means new signal kinds default to "not an attention item" rather than silently flooding the open set.

func (*Graph) Plans

func (g *Graph) Plans() []*Entry

Plans returns active plan decisions (not closed, not superseded).

func (*Graph) RecentDone added in v0.2.0

func (g *Graph) RecentDone(n int) []*Entry

RecentDone returns the last n kind: done signals by timestamp — the activity stream of what was recently accomplished. Replaces the pre-two-type RecentActions; actions no longer exist in the two-type model.

func (*Graph) RecentInsights added in v0.2.0

func (g *Graph) RecentInsights(n int) []*Entry

RecentInsights returns the last n kind: insight signals by timestamp — observational records that inform current thinking. Insights have no closure gate (they're retired via directive-close, not resolved), so they surface as their own stream rather than mixing into the actionable Open Signals view.

func (*Graph) RefChain

func (g *Graph) RefChain(id string) []*Entry

RefChain returns the entry and all entries it transitively references, in dependency order.

func (*Graph) ResolveID

func (g *Graph) ResolveID(input string) (string, error)

ResolveID resolves a user-supplied ID string (full or short form) to a full entry ID. Full IDs pass through unchanged. Short form {type}-{layer}-{suffix} is matched against entries; a unique match returns the full ID, ambiguous matches return an error listing all candidates (sorted). Inputs that do not recognize as either shape, that use unknown type or layer abbreviations, or that match zero entries pass through unchanged so the caller's existing "entry not found" surface fires against the user's original text.

func (*Graph) ResolveIDs

func (g *Graph) ResolveIDs(inputs []string) ([]string, error)

ResolveIDs resolves a slice of user-supplied IDs. Ambiguous inputs stop resolution and return the error; other inputs pass through the same semantics as ResolveID.

func (*Graph) ResolveRoleChain added in v0.3.0

func (g *Graph) ResolveRoleChain(role *Entry) *ActorChain

ResolveRoleChain returns the actor-identity chain a role binds to via the role's Actor canonical. Returns nil when the role is not kind: role, has no Actor, or the canonical resolves to no chain (orphan — surfaced by lint). Used by both DerivedStatus and finders that need to render the resolved chain alongside the role.

func (*Graph) Roles added in v0.3.0

func (g *Graph) Roles() []*Entry

Roles returns all kind: role decisions regardless of derived status. Used by list/filter paths that render all roles (including retired).

func (*Graph) SetGraphDir

func (g *Graph) SetGraphDir(dir string)

SetGraphDir records the directory the graph was loaded from. Used by IO callers (e.g. sdd.LoadGraph) to attach provenance after constructing the in-memory graph.

func (*Graph) TopologicalOrder

func (g *Graph) TopologicalOrder() []*Entry

TopologicalOrder returns entries sorted so that every entry appears after all entries it references (refs, closes, supersedes). Entries with no references come first. This is a stable sort — entries at the same depth are ordered by time.

type GraphFilter

type GraphFilter struct {
	Type        EntryType
	Layer       Layer
	Kind        Kind
	MissingKind bool // when true, only include entries whose stored kind field is empty
	OpenOnly    bool // when true, exclude closed/superseded signals and decisions
}

GraphFilter specifies criteria for filtering graph entries.

type IDParts

type IDParts struct {
	Timestamp string
	Time      time.Time
	TypeCode  string // abbreviation: "s" or "d"
	LayerCode string // abbreviation: "stg", "cpt", "tac", "ops", "prc"
	Suffix    string
}

IDParts holds the parsed components of a document ID.

func ParseID

func ParseID(id string) (IDParts, error)

ParseID parses a document ID into its components. ID format: {YYYYMMDD}-{HHmmss}-{type}-{layer}-{suffix}

type Kind

type Kind string

Kind is a sub-type classifier carried on signals and decisions. Its allowed values depend on the entry's Type:

  • Signal kinds: gap (default), fact, question, insight, done, actor
  • Decision kinds: directive (default), activity, plan, contract, aspiration, role

Empty Kind on a new entry is replaced by the type's default during capture.

const (
	// Signal kinds.
	KindGap      Kind = "gap"
	KindFact     Kind = "fact"
	KindQuestion Kind = "question"
	KindInsight  Kind = "insight"
	KindDone     Kind = "done"
	KindActor    Kind = "actor"

	// Decision kinds.
	KindDirective  Kind = "directive"
	KindActivity   Kind = "activity"
	KindPlan       Kind = "plan"
	KindContract   Kind = "contract"
	KindAspiration Kind = "aspiration"
	KindRole       Kind = "role"
)

func DefaultKindForType added in v0.2.0

func DefaultKindForType(t EntryType) Kind

DefaultKindForType returns the kind applied when a new entry of type t is captured without an explicit --kind. Signals default to gap; decisions to directive. Other types have no default (empty).

type LLMConfig added in v0.2.0

type LLMConfig struct {
	// Provider selects the runner implementation: "claude-cli" (default, uses
	// the logged-in Claude Code session) or a gollm-supported provider name
	// such as "anthropic", "openai", "ollama".
	Provider string `yaml:"provider,omitempty"`
	// Model is the provider-specific model identifier.
	Model string `yaml:"model,omitempty"`
	// Timeout is a Go duration string (e.g. "2m") applied per LLM call.
	Timeout string `yaml:"timeout,omitempty"`
	// Concurrency bounds the worker pool for batch operations. Zero means
	// "use DefaultLLMConcurrency".
	Concurrency int `yaml:"concurrency,omitempty"`
	// OllamaEndpoint overrides the default Ollama URL for the gollm adapter.
	OllamaEndpoint string `yaml:"ollama_endpoint,omitempty"`
	// APIKeys maps provider name to API key. Typically lives in
	// config.local.yaml so keys stay out of version control.
	APIKeys map[string]string `yaml:"api_keys,omitempty"`
	// RateLimitRPS caps remote-provider requests per second. Zero means
	// "apply a conservative per-model default safe for Anthropic/OpenAI
	// tier 1"; set an explicit positive value (e.g. a high number like
	// 100) to effectively disable the cap on higher tiers. The claude-cli
	// and ollama providers ignore this field.
	RateLimitRPS float64 `yaml:"rate_limit_rps,omitempty"`
}

LLMConfig holds settings for LLM provider selection, model choice, and concurrency/rate-limit behavior. API keys and per-machine endpoints typically live in .sdd/config.local.yaml; defaults (provider, model, timeout, concurrency) are safe to commit in .sdd/config.yaml.

type Layer

type Layer string
const (
	LayerStrategic   Layer = "strategic"
	LayerConceptual  Layer = "conceptual"
	LayerTactical    Layer = "tactical"
	LayerOperational Layer = "operational"
	LayerProcess     Layer = "process"
)

type SchemaMeta added in v0.2.0

type SchemaMeta struct {
	GraphSchemaVersion int     `json:"graph_schema_version"`
	MinimumVersion     *string `json:"minimum_version,omitempty"`
}

SchemaMeta is the decoded contents of .sdd/meta.json.

GraphSchemaVersion is required and advances through explicit migrations. MinimumVersion is written once at initial init (set to the binary's semver at creation time) and preserved thereafter; a nil pointer means the field is absent on disk.

func ParseSchemaMeta added in v0.2.0

func ParseSchemaMeta(data []byte) (*SchemaMeta, error)

ParseSchemaMeta decodes .sdd/meta.json bytes into a SchemaMeta.

type Scope added in v0.2.0

type Scope string

Scope selects where skills are installed for a given agent. User scope is shared across all projects for a given OS user; Project scope lives beside the repository.

const (
	// ScopeUser installs into the user-global agent directory (e.g.
	// ~/.claude/skills for Claude).
	ScopeUser Scope = "user"

	// ScopeProject installs into the repository-local agent directory (e.g.
	// <repo>/.claude/skills for Claude).
	ScopeProject Scope = "project"
)

type ShowTree

type ShowTree struct {
	Primary    *Entry
	Upstream   []ShowTreeItem
	Downstream []ShowTreeItem
}

ShowTree holds the upstream and downstream chains for a single primary entry.

type ShowTreeItem

type ShowTreeItem struct {
	Entry       *Entry
	Depth       int
	Relations   []string       // e.g. ["refs"], ["refs", "closes"], ["refd-by"]
	ShownAbove  bool           // already rendered earlier — "(see above)" marker
	ShownBelow  bool           // future primary — "(see below)" marker
	SummaryOnly bool           // true for depth > 0
	Truncated   []TruncatedRef // children hidden at max-depth boundary
}

ShowTreeItem represents an entry at a specific depth in a show tree. Depth 0 is the primary entry. Depth 1+ entries are summary-only.

type SkillBundle added in v0.2.0

type SkillBundle struct {
	Target  AgentTarget
	Entries []SkillBundleEntry
}

SkillBundle is the set of files embedded in the binary for a single agent target.

type SkillBundleEntry added in v0.2.0

type SkillBundleEntry struct {
	// Skill is the top-level skill directory (e.g. "sdd", "sdd-catchup").
	Skill string

	// RelPath is the path inside the skill directory (e.g. "SKILL.md",
	// "references/framework-concepts.md"). Forward-slash separators.
	RelPath string

	// Content is the raw file bytes as embedded.
	Content []byte
}

SkillBundleEntry is one file in the embedded skill bundle — the content as shipped inside the sdd binary, without install-time stamps.

type SkillFile added in v0.2.0

type SkillFile struct {
	// AbsPath is the absolute path where the file lives on disk.
	AbsPath string

	// Content is the raw file bytes as read.
	Content []byte

	// StoredVersion is the value of the sdd-version frontmatter stamp, or
	// empty if absent.
	StoredVersion string

	// StoredHash is the value of the sdd-content-hash frontmatter stamp, or
	// empty if absent.
	StoredHash string
}

SkillFile is a parsed on-disk skill file, carrying the install stamps read from its frontmatter plus the raw content needed to re-hash.

func ParseSkillFile added in v0.2.0

func ParseSkillFile(absPath string, content []byte) *SkillFile

ParseSkillFile reads an on-disk skill file's raw bytes and extracts its install-time stamps.

type SkillInstallStatus added in v0.2.0

type SkillInstallStatus string

SkillInstallStatus describes the install state of a single skill file relative to the embedded bundle.

const (
	// SkillStatusMissing means the file has no on-disk counterpart.
	SkillStatusMissing SkillInstallStatus = "missing"

	// SkillStatusCurrent means the on-disk file's stored hash matches the
	// embedded entry's hash and the user has not edited it.
	SkillStatusCurrent SkillInstallStatus = "current"

	// SkillStatusPristine means the on-disk file was produced by a previous
	// sdd init (user hasn't edited it) but its stored version differs from
	// the embedded bundle — safe to overwrite silently.
	SkillStatusPristine SkillInstallStatus = "pristine"

	// SkillStatusModified means the on-disk file's computed hash does not
	// match its stored hash — the user has edited it, overwrite requires
	// confirmation.
	SkillStatusModified SkillInstallStatus = "modified"
)

func ComputeSkillStatus added in v0.2.0

func ComputeSkillStatus(embedded SkillBundleEntry, installed *SkillFile) SkillInstallStatus

ComputeSkillStatus classifies an installed file relative to its embedded counterpart. When installed is nil, the file is missing.

An unstamped installed file (empty StoredHash) is treated as Pristine when its content byte-matches the embedded entry — first-run adoption of a bundled skill shouldn't reflexively prompt the user about files they haven't touched. Unstamped files whose content doesn't match are Modified.

type Status

type Status struct {
	Kind StatusKind
	By   string
}

Status is the computed lifecycle status for an entry. By is populated only for compound states (ClosedBy, SupersededBy) and holds the full entry ID of the first closer/superseder.

type StatusKind

type StatusKind string

StatusKind is the lifecycle state of an entry derived from graph relationships.

const (
	StatusNone            StatusKind = ""               // done signals — terminal facts with no lifecycle state
	StatusActive          StatusKind = "active"         // decision (directive, plan, contract) not closed or superseded
	StatusOpen            StatusKind = "open"           // signal not closed or superseded
	StatusClosedBy        StatusKind = "closed-by"      // closed by another entry (By carries the full ID)
	StatusSupersededBy    StatusKind = "superseded-by"  // superseded by another entry
	StatusCascadeClosedBy StatusKind = "cascade-closed" // role cascade: actor chain head is closed (By = head actor ID)
	StatusCascadeOrphan   StatusKind = "cascade-orphan" // role references a canonical with no matching chain
)

type SyncConfig added in v0.3.0

type SyncConfig struct {
	// Cooldown is the minimum interval between background git fetches. Go
	// duration string (e.g. "15m", "1h"). Empty means DefaultSyncCooldown.
	Cooldown string `yaml:"cooldown,omitempty"`
}

SyncConfig governs background sync awareness: the auto-fetch cooldown and related behavior. Stored as a string Go duration (e.g. "15m") parsed at use site so malformed values fall back to DefaultSyncCooldown rather than failing at config load.

type SyncState added in v0.3.0

type SyncState string

SyncState describes the overall state of background sync detection.

const (
	// SyncStateUpToDate: no divergence between local and remote.
	SyncStateUpToDate SyncState = "up-to-date"
	// SyncStateFastForward: remote has commits local does not; local has none
	// ahead. A rebase is a fast-forward with no conflict risk.
	SyncStateFastForward SyncState = "fast-forward"
	// SyncStateCleanRebase: both sides have commits, but git merge-tree
	// predicts the rebase would apply cleanly.
	SyncStateCleanRebase SyncState = "clean-rebase"
	// SyncStateConflictPredicted: both sides have diverged and the
	// git merge-tree prediction reports conflicts. Rebase is unsafe without
	// manual resolution.
	SyncStateConflictPredicted SyncState = "conflict-predicted"
	// SyncStateLocalAhead: local has commits remote does not, and remote has
	// none ahead of local. Push is suggested.
	SyncStateLocalAhead SyncState = "local-ahead"
	// SyncStateNoRepo: current directory is not inside a git repository.
	SyncStateNoRepo SyncState = "no-repo"
	// SyncStateNoRemote: repository has no remote configured.
	SyncStateNoRemote SyncState = "no-remote"
	// SyncStateNoUpstream: current branch has no upstream tracking branch.
	SyncStateNoUpstream SyncState = "no-upstream"
	// SyncStateFetchFailed: git fetch was attempted and failed (network,
	// authentication, etc.). The last-fetch timestamp is still updated so
	// subsequent commands within the cooldown window do not retry.
	SyncStateFetchFailed SyncState = "fetch-failed"
	// SyncStateSkipped: the sync check did not run (cooldown window active,
	// command exempt, or environment unavailable). No slog output expected.
	SyncStateSkipped SyncState = "skipped"
)

type SyncStatus added in v0.3.0

type SyncStatus struct {
	State         SyncState
	RemoteAhead   int      // count of remote-ahead commits matching ^sdd:
	LocalAhead    int      // count of local-ahead commits matching ^sdd:
	ConflictPaths []string // populated when State == SyncStateConflictPredicted
	// Reason carries a short human-readable detail for failure states —
	// the upstream branch name for NoUpstream, the error message for
	// FetchFailed, etc. Empty for success states.
	Reason string
}

SyncStatus is the structured result of a background sync check. Callers emit the appropriate slog line based on State; the skill pattern-matches on the rendered message.

type TruncatedRef

type TruncatedRef struct {
	ID        string
	Relations []string
	Kind      Kind
}

TruncatedRef describes a child entry hidden at the max-depth boundary.

type WIPMarker

type WIPMarker struct {
	ID          string // filename without .md: {YYYYMMDD}-{HHmmss}-{participant}
	Entry       string // graph entry ID being worked on
	Participant string
	Exclusive   bool
	Branch      string // git branch name (empty if not a branched work stream)
	Content     string // free-text description
	Time        time.Time
}

WIPMarker represents an in-flight work marker.

func HasExclusiveMarker

func HasExclusiveMarker(markers []*WIPMarker, entryID string) (*WIPMarker, bool)

HasExclusiveMarker returns true if any marker for the given entry is exclusive.

func MarkersForEntry

func MarkersForEntry(markers []*WIPMarker, entryID string) []*WIPMarker

MarkersForEntry returns all markers that reference the given graph entry ID.

func ParseWIPMarker

func ParseWIPMarker(filename, content string) (*WIPMarker, error)

ParseWIPMarker parses a WIP marker from its filename and file content.

func (*WIPMarker) ShortContent

func (m *WIPMarker) ShortContent(maxLen int) string

ShortContent returns the first line of the marker content, truncated to maxLen.

type Warning

type Warning struct {
	Field   string // "refs", "closes", "supersedes"
	Value   string // the offending ID or value
	Message string // human-readable description
}

Warning represents a validation issue found on a graph entry.

Jump to

Keyboard shortcuts

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