config

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package config loads and validates the per-project .awf/ configuration: a skeleton config.yaml plus per-target sidecar YAMLs and convention parts.

Index

Constants

View Source
const DirName = ".awf"

DirName is the config-tree directory name at the project root.

Variables

This section is empty.

Functions

func ConfigPath added in v0.6.0

func ConfigPath(root string) string

ConfigPath returns the skeleton config.yaml path for a project root.

func ConvertInvariantsToCurrentState added in v0.18.0

func ConvertInvariantsToCurrentState(src []byte) ([]byte, error)

ConvertInvariantsToCurrentState plans the bridge preparation conversion while retaining the yaml.Node representation of every copied field and all unrelated comments and key order. It never weakens an explicit legacy opt-out.

func IsSingletonKind

func IsSingletonKind(kind string) bool

IsSingletonKind reports whether kind is an always-on singleton whose sidecar lives at <root>/<kind>.yaml and whose parts live under <root>/parts/<kind>/ (ADR-0021, ADR-0043).

func LockPath added in v0.6.0

func LockPath(root string) string

LockPath returns the awf.lock path for a project root.

func MarshalSkeleton

func MarshalSkeleton(s Skeleton) ([]byte, error)

MarshalSkeleton renders a fresh config.yaml from s in the canonical awf format (two-space block style). It is the construction half of internal/config's ownership of config.yaml serialization (ADR-0026).

func RemoveKey added in v0.2.0

func RemoveKey(src []byte, key string) ([]byte, error)

RemoveKey deletes the top-level mapping entry under key from a config.yaml source via a yaml.Node round-trip that preserves comments and every untouched key (ADR-0026). Removing an absent key is a no-op (returns src unchanged), so a schema migration can re-run safely.

func RemoveMappingKey added in v0.18.0

func RemoveMappingKey(src []byte, key, child string) ([]byte, error)

RemoveMappingKey removes child from the mapping at top-level key, preserving comments and every untouched key via the same yaml.Node round-trip as the other editors (ADR-0026). When the removal empties the parent mapping, the parent key goes too, so a retired setting leaves no vestigial block behind. An absent parent, a non-mapping parent, or an absent child is a no-op (returns src unchanged), so a schema migration can re-run safely.

func RootDir added in v0.6.0

func RootDir(root string) string

RootDir returns the config-tree directory for a project root (<root>/.awf).

func SeedVarKey added in v0.14.0

func SeedVarKey(src []byte, name string) ([]byte, error)

SeedVarKey adds `name: ""` under the top-level vars: mapping when the key is absent, creating the mapping if needed, via the same comment-preserving yaml.Node round-trip as SetArrayMember (ADR-0026). A present key - set, empty, or null - is left untouched and src is returned unchanged: presence is the open-to-do signal and absence the deliberate decline (ADR-0087), so seeding must never overwrite either state.

func SetArray added in v0.4.0

func SetArray(src []byte, key string, values []string) ([]byte, error)

SetArray sets the sequence under key to exactly values, creating the key if it is absent and replacing it otherwise, via a yaml.Node round-trip that preserves comments and every untouched key (ADR-0026). Used where the whole list is computed rather than edited member-by-member - the targets array carries a Load default, so an absent on-disk key must be materialized as the full resolved list, not appended to (ADR-0037).

func SetArrayMember

func SetArrayMember(src []byte, key, name string, add bool) ([]byte, error)

SetArrayMember adds or removes name in the sequence under key in a config.yaml source, via a yaml.Node round-trip that preserves comments and every untouched key (ADR-0026). The edited sequence is normalized to block style, so a flow-style input (`key: [a, b]`) is accepted. Adding a member already present is a no-op; removing a member absent from the key (or a key absent on remove) errors. touches-invariant: config-mutation-roundtrip - yaml.Node add/remove round-trip; proof in edit_test.go

func SetMappingScalar added in v0.5.0

func SetMappingScalar(src []byte, key, child string, value bool) ([]byte, error)

SetMappingScalar sets child to a bool value under a top-level mapping at key in a config.yaml source, creating the key's mapping (and the child) if absent, via a yaml.Node round-trip that preserves comments and every untouched key (ADR-0026). It is the nested-scalar analog of SetArray (which writes a sequence): the bootstrap enable entry is `bootstrap.enabled: <bool>`, not an enable array, so it needs a mapping-scalar writer rather than SetArrayMember. An existing scalar under key/child is overwritten.

func ValidateArtifactName added in v0.10.0

func ValidateArtifactName(kind, name string) error

ValidateArtifactName reports whether name is usable as a local skill/agent name (ADR-0068): non-empty lowercase kebab-case (letters, digits, hyphens). The charset is frontmatter-safe - it excludes the path separators and ".." the invariant requires, awf's reserved "_" namespace, and the colon/space/quote characters that would otherwise interpolate into the base template's name: line and break its YAML frontmatter. It mirrors every catalog artifact's naming. touches-invariant: local-name-validated - local skill/agent name charset validation; proof in config_test.go

func ValidateDocName added in v0.15.0

func ValidateDocName(name string) error

ValidateDocName validates a path-aware local doc name (ADR-0091): one or more lowercase-kebab segments joined by "/", rejecting a path escape, an empty or leading/trailing segment, a ".md" suffix, and any segment (e.g. the reserved "_base" stem) carrying a non-kebab character. Skill/agent names stay flat. touches-invariant: local-doc-name-path-validated - path-aware local doc name validation; proof in docname_test.go

func ValidateDomainName

func ValidateDomainName(name string) error

ValidateDomainName reports whether name is a usable domain key: non-empty and free of path separators or "..". Shared by Validate and the `awf enable domain` path so a freeform domain name is rejected the same way in both.

Types

type AuditConfig

type AuditConfig struct {
	AllowedTypes        []string    `yaml:"allowedTypes"`
	AllowedScopes       []ScopeSpec `yaml:"allowedScopes"`
	SubjectMaxLength    *int        `yaml:"subjectMaxLength"`
	DependencyManifests []string    `yaml:"dependencyManifests"`
	DiffThreshold       *int        `yaml:"diffThreshold"`
	DomainDocStaleness  *bool       `yaml:"domainDocStaleness"`
	DomainCodeStaleness *bool       `yaml:"domainCodeStaleness"`
	UndocumentedDomain  *bool       `yaml:"undocumentedDomain"`
	PlainPunctuation    *bool       `yaml:"plainPunctuation"`
	UncommittedChanges  *bool       `yaml:"uncommittedChanges"`
}

AuditConfig tunes `awf audit` (ADR-0017). A nil *AuditConfig means all defaults; within it, a nil slice means "use the default", an explicit empty slice means "accept any / disabled" per field. Resolution and defaults live in internal/audit (audit.Resolve), which owns the audit domain semantics.

type BootstrapConfig added in v0.5.0

type BootstrapConfig struct {
	Enabled bool `yaml:"enabled"`
}

BootstrapConfig configures the rendered .awf/bootstrap.sh singleton (ADR-0040, relocated by ADR-0047). A nil *BootstrapConfig (key absent) and Enabled false both mean "do not render"; only Enabled true renders the artifact - a nested enable entry rather than a top-level scalar bool (the Alternatives table rejected the bare bool).

type CatalogTrim

type CatalogTrim struct {
	Skills *[]string
	Docs   *[]string
}

CatalogTrim optionally overrides which catalog skills/docs a scaffolded config enables (ADR-0029 catalog trim). A nil *CatalogTrim - or a nil dimension within it - means "no selection: keep the curated-core default"; a non-nil dimension is the verbatim, fully-deselectable enable set (an empty slice deselects all).

type Config

type Config struct {
	Prefix        string              `yaml:"prefix"`
	DocsDir       string              `yaml:"docsDir"`
	Vars          map[string]any      `yaml:"vars"`
	Skills        []string            `yaml:"skills"`
	Agents        []string            `yaml:"agents"`
	Docs          []string            `yaml:"docs"`
	Domains       []string            `yaml:"domains"`
	Tags          map[string]string   `yaml:"tags"`
	ContextIgnore []string            `yaml:"contextIgnore"`
	Targets       []string            `yaml:"targets"`
	Invariants    *InvariantConfig    `yaml:"invariants"`
	CurrentState  *CurrentStateConfig `yaml:"currentState"`
	Audit         *AuditConfig        `yaml:"audit"`
	Bootstrap     *BootstrapConfig    `yaml:"bootstrap"`
	Hooks         *HooksConfig        `yaml:"hooks"`
	Runner        *RunnerConfig       `yaml:"runner"`
	ProseGate     *ProseGateConfig    `yaml:"proseGate"`
	// contains filtered or unexported fields
}

Config is the skeleton config.yaml: global fields plus flat enable arrays. Presence of a name in Skills/Agents/Docs enables that artifact; per-artifact data/sections/local live in sidecars, not here. Targets is the adapter-runtime enable array (default ["claude"]); adapter artifacts render once per entry.

func Load

func Load(awfDir string) (*Config, error)

Load reads <awfDir>/config.yaml with the strict decoder, records awfDir as the sidecar/part resolution root, and defaults DocsDir.

func Parse added in v0.18.0

func Parse(awfDir string, b []byte) (*Config, error)

Parse strictly decodes config.yaml bytes, records awfDir as the sidecar/part resolution root, and applies defaults.

func (*Config) HasSidecar added in v0.10.0

func (c *Config) HasSidecar(kind, name string) (bool, error)

HasSidecar reports whether a declaring sidecar file exists for an artifact - the presence signal that marks a non-catalog name as an intentional local artifact rather than a typo (ADR-0068).

func (*Config) PartPath

func (c *Config) PartPath(kind, artifact, section string) string

PartPath returns the convention part path for a section of an artifact.

func (*Config) Sidecar

func (c *Config) Sidecar(kind, name string) (Sidecar, error)

Sidecar reads <root>/<kind>/<name>.yaml; agents-doc lives at <root>/agents-doc.yaml. A missing file yields a zero Sidecar (publication-safe: empty data/sections).

func (*Config) Source added in v0.10.0

func (c *Config) Source() []byte

Source returns the exact config.yaml bytes Load read. A byte-level editor (SetArrayMember, SetArray, SetMappingScalar) reuses these instead of re-reading the file, which after a successful Load could only fail on a race.

func (*Config) Validate

func (c *Config) Validate() error

type CurrentStateConfig added in v0.18.0

type CurrentStateConfig struct {
	Sources          []CurrentStateSource `yaml:"sources"`
	TestGlobs        []string             `yaml:"testGlobs"`
	TopicCoverage    string               `yaml:"topicCoverage"`
	TopicFanout      string               `yaml:"topicFanout"`
	MaxTopicsPerPath *int                 `yaml:"maxTopicsPerPath"`
	// contains filtered or unexported fields
}

CurrentStateConfig configures bridge-preparation validation for canonical current-state topics. It is deliberately separate from the legacy invariant authority, which remains active throughout the bridge tranche.

func (*CurrentStateConfig) EffectiveMaxTopicsPerPath added in v0.18.0

func (c *CurrentStateConfig) EffectiveMaxTopicsPerPath() int

EffectiveMaxTopicsPerPath returns the configured fan-out budget, defaulting to eight without materializing that default into the decoded config.

func (*CurrentStateConfig) UnmarshalYAML added in v0.18.0

func (c *CurrentStateConfig) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML retains severity presence while preserving strict nested field validation for the custom-decoded current-state mapping.

type CurrentStateSource added in v0.18.0

type CurrentStateSource struct {
	Globs  []string `yaml:"globs"`
	Marker string   `yaml:"marker"`
	Close  string   `yaml:"close"`
	// contains filtered or unexported fields
}

CurrentStateSource describes one marker-bearing source family. closeSet distinguishes an omitted close token from an explicitly empty one.

func (*CurrentStateSource) UnmarshalYAML added in v0.18.0

func (s *CurrentStateSource) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML retains close-token presence while preserving strict nested field validation for the custom-decoded source mapping.

type GlobRewrite added in v0.12.0

type GlobRewrite struct {
	Key  string
	From string
}

A GlobRewrite records one no-slash glob scalar AnchorNoSlashGlobs anchored: Key is the config location, From the original pattern (rewritten to `**/<From>`).

func AnchorNoSlashGlobs added in v0.11.0

func AnchorNoSlashGlobs(src []byte) ([]byte, []GlobRewrite, error)

AnchorNoSlashGlobs rewrites every no-slash glob scalar under invariants.sources[].globs and audit.dependencyManifests to `**/<pattern>`, preserving comments and untouched keys (ADR-0026) and reporting the rewrites performed. Slashed patterns are left alone, so the rewrite is idempotent; absent keys are a no-op. It is the nested-sequence editor the schema-7 anchored-globs migration (ADR-0077) consumes - the sequence analog of SetMappingScalar.

type HooksConfig added in v0.6.0

type HooksConfig struct {
	Enabled bool `yaml:"enabled"`
}

HooksConfig configures the rendered .awf/hooks/ payload singleton (ADR-0048): three inert git-hook payload scripts adopters wire into hook setups they own. BootstrapConfig semantics: a nil *HooksConfig (key absent) and Enabled false both mean "do not render"; only Enabled true renders the payloads. The key reuses the name the schema-4 drop-hooks migration stripped (ADR-0032); the legacy array shape never reaches this struct - gated commands migrate first, ungated ones fail loudly on the strict parser's type error.

type InvariantConfig

type InvariantConfig struct {
	Disabled  bool              `yaml:"disabled"`
	Sources   []InvariantSource `yaml:"sources"`
	TestGlobs []string          `yaml:"testGlobs"`
}

InvariantConfig configures language-agnostic invariant backing. A nil *InvariantConfig (key absent) means "unchecked"; Disabled is the explicit opt-out; a non-empty Sources enables enforcement.

TestGlobs scopes the proof `invariant:` marker to test files (ADR-0105): when non-empty, a proof marker backs a slug only in a file matching one of these anchored globs; when empty or absent, backing falls back to source-glob scope (the pre-ADR-0105 semantics). TestGlobs is an inert optional field within the current schema - an absent value degrades to the fallback, so it needs no schema-generation bump.

type InvariantSource

type InvariantSource struct {
	Globs  []string `yaml:"globs"`
	Marker string   `yaml:"marker"`
	Close  string   `yaml:"close"`
}

InvariantSource pairs anchored path globs (ADR-0077; matched against a file's slash-separated repo-relative path) with the literal comment marker that prefixes a backing `invariant: <slug>` tag. Close is the optional literal close token for block-comment markers (`-->`, `*/`): when non-empty, one trailing token (plus surrounding whitespace) is stripped from a matched marker line before tag parsing (ADR-0121). Additive and optional - empty means no stripping - so no schema-generation bump.

type ProseExemption added in v0.18.0

type ProseExemption struct {
	Path      string `yaml:"path"`
	Codepoint string `yaml:"codepoint"`
	Count     *int   `yaml:"count"`
}

ProseExemption exempts one codepoint in one path. Codepoint is spelled "U+2014", never the character itself: config.yaml is a tracked file the scan reads, so a typed glyph here would be a finding against the file that configures the exemptions. A nil Count permits any number of occurrences; a non-nil Count pins the expected number, so an added occurrence in an exempt file still fails.

type ProseGateConfig added in v0.18.0

type ProseGateConfig struct {
	Enabled    bool             `yaml:"enabled"`
	Exemptions []ProseExemption `yaml:"exemptions"`
}

ProseGateConfig configures `awf prose-gate` (ADR-0119): a presence-level scan of every tracked text file for the seven banned typographic punctuation substitutes. BootstrapConfig semantics: a nil *ProseGateConfig (key absent) and Enabled false both mean "the command exits zero without scanning". The default is off because the scan blocks a commit, and a tree that has never been swept would fail it on the day it lands.

type RunnerConfig added in v0.18.0

type RunnerConfig struct {
	Enabled bool `yaml:"enabled"`
}

RunnerConfig configures the rendered command-runner singleton `x` (ADR-0101): a co-owned file (ADR-0100) whose awf-verb dispatch awf owns and whose project verbs live in in-place-editable sections the adopter fills. Like the bootstrap/hooks toggles, a nil *RunnerConfig (key absent) and Enabled false both mean "do not render"; only Enabled true renders the runner. Additive and default-off - no schema-generation migration, and adopters opt in explicitly.

type ScopeSpec added in v0.8.0

type ScopeSpec struct {
	Name    string `yaml:"name"`
	Meaning string `yaml:"meaning"`
}

ScopeSpec is one allowed commit scope: a name and an optional human meaning. In config a scope is written either as a bare string (name only) or a {name, meaning} mapping (ADR-0056).

func (*ScopeSpec) UnmarshalYAML added in v0.8.0

func (s *ScopeSpec) UnmarshalYAML(n *yaml.Node) error

UnmarshalYAML accepts either a scalar node (the bare-string form → empty meaning) or a strict mapping node. invariant: scope-config-dual-form

type SectionOverride

type SectionOverride struct {
	Drop bool `yaml:"drop"`
}

SectionOverride is a sidecar's per-section override. Body replacement is by convention part only; the field set is deliberately just Drop. touches-invariant: no-replacewith - SectionOverride field set omits replaceWith; proof in config_test.go

type Sidecar

type Sidecar struct {
	Data     map[string]any             `yaml:"data"`
	Sections map[string]SectionOverride `yaml:"sections"`
	Local    bool                       `yaml:"local"`
	// Paths declares a domain's file territory as anchored path globs
	// (ADR-0077); read only from domain sidecars, inert on other kinds.
	Paths []string `yaml:"paths"`
}

Sidecar holds a single target's non-prose configuration: structured render data, per-section overrides, and the local flag. It lives at <awfDir>/<kind>/<name>.yaml (agents-doc: <awfDir>/agents-doc.yaml). An absent sidecar is the zero Sidecar (publication-safe: empty data/sections).

type Skeleton

type Skeleton struct {
	Prefix     string            `yaml:"prefix"`
	Vars       map[string]string `yaml:"vars"`
	Skills     []string          `yaml:"skills"`
	Agents     []string          `yaml:"agents"`
	Docs       []string          `yaml:"docs"`
	Audit      *SkeletonAudit    `yaml:"audit,omitempty"`
	Invariants *InvariantConfig  `yaml:"invariants,omitempty"`
	Bootstrap  *BootstrapConfig  `yaml:"bootstrap,omitempty"`
	Hooks      *HooksConfig      `yaml:"hooks,omitempty"`
}

Skeleton is the input to MarshalSkeleton: the fields a freshly-scaffolded .awf/config.yaml carries. Vars is typed map[string]string (not map[string]any) so a nil/null var value is unrepresentable - the scaffold seeds each var with an empty string, which marshals as `x: ""`. A nil interface would marshal as `x: null` and decode back to a nil value that renders as "<no value>", tripping the publication-safe check (ADR-0026 Decision 3).

type SkeletonAudit added in v0.6.0

type SkeletonAudit struct {
	AllowedScopes []string `yaml:"allowedScopes"`
}

SkeletonAudit is the audit block a scaffold can seed (ADR-0051): only allowedScopes - the one audit field init collects. Deliberately not *AuditConfig, whose zero-value fields would serialize as explicit settings.

Jump to

Keyboard shortcuts

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