Documentation
¶
Overview ¶
Package config loads and writes the consumer repo's `aiwf.yaml`.
The file is small and deliberately so — see docs/design/design-decisions.md §"aiwf.yaml config". The fields are:
hosts: [claude-code] # optional; PoC default and only supported value status_md: # optional; opt-out for the STATUS.md auto-update auto_update: false # default true — see StatusMdAutoUpdate
Identity is runtime-derived (per `provenance-model.md`):
- `--actor <role>/<id>` flag on the verb wins.
- else `git config user.email` → `human/<localpart>`.
- else verb refuses with a usage error.
Two legacy fields are tolerated on read for the migration window:
- `actor:` (pre-I2.5) — captured into LegacyActor; ignored for identity. Stripped on `aiwf update`. See StripLegacyActor.
- `aiwf_version:` (pre-G47) — captured into LegacyAiwfVersion; was a set-once pin that never auto-maintained itself, producing chronic doctor noise. Stripped on `aiwf update`. See StripLegacyAiwfVersion.
Validation rules:
- ActorPattern is the published regex for `<role>/<id>`; callers that resolve identity at runtime (cmd/aiwf, initrepo) consult it.
Index ¶
- Constants
- Variables
- func AcceptedKeys() map[string]bool
- func GenerateExample() string
- func StripLegacyActor(root string) (changed bool, err error)
- func StripLegacyAiwfVersion(root string) (changed bool, err error)
- func Write(root string, cfg *Config) error
- type Agent
- type Allocate
- type Archive
- type Areas
- type Config
- func (c *Config) AllocateTrunkRef() (ref string, explicit bool)
- func (c *Config) ArchiveSweepThreshold() (n int, set bool)
- func (c *Config) EntityTitleMaxLength() int
- func (c *Config) HTMLOutDir() string
- func (c *Config) HookDecision(name string) (enabled, decided bool)
- func (c *Config) StatusMdAutoUpdate() bool
- func (c *Config) TrunkBranchShortName() string
- func (c *Config) Validate() error
- func (c *Config) WireClaudeMd() bool
- func (c *Config) WorktreeDir() string
- type Entities
- type Guidance
- type HTML
- type Hook
- type Logging
- type Member
- type SchemaField
- type StatusMd
- type TDD
- type Tree
- type Worktree
Constants ¶
const DefaultAllocateTrunk = "refs/remotes/origin/main"
DefaultAllocateTrunk is the trunk ref the allocator falls back to when aiwf.yaml.allocate.trunk is unset. Mirrors what `git clone` produces for a standard upstream project.
const DefaultEntityTitleMaxLength = 80
DefaultEntityTitleMaxLength is the kernel-default cap on entity title (and slug) length. 80 chars matches the Conventional Commits subject-line convention so an entity-touching commit subject that quotes the title verbatim still fits within typical commit-subject guidelines.
const DefaultHTMLOutDir = "site"
DefaultHTMLOutDir is the path the renderer falls back to when aiwf.yaml.html.out_dir is unset.
const DefaultStatusMdAutoUpdate = true
DefaultStatusMdAutoUpdate is the value StatusMdAutoUpdate returns when aiwf.yaml.status_md.auto_update is unset (E-0057: named so the schema generator can cite it instead of a bare literal hiding inside the getter).
const DefaultWireClaudeMd = true
DefaultWireClaudeMd is the value WireClaudeMd returns when aiwf.yaml.guidance.wire_claudemd is unset (E-0057: named so the schema generator can cite it instead of a bare literal hiding inside the getter).
const DefaultWorktreeDir = ".claude/worktrees"
DefaultWorktreeDir is the repo-relative directory the start rituals place worktrees under when aiwf.yaml.worktree.dir is unset. In-repo placement (ADR-0023): reachable as a session cwd inside a sandboxed devcontainer, persistent under the mounted workspace, and gitignored via `.claude/*`.
const ExampleFileName = "aiwf.example.yaml"
ExampleFileName is the generated, gitignored reference file `aiwf init`/`aiwf update` write and refresh every run (M-0232/AC-3) — GenerateExample()'s output, always in sync with the schema this binary accepts.
const FileName = "aiwf.yaml"
FileName is the canonical filename at the consumer repo root.
Variables ¶
var ActorPattern = regexp.MustCompile(`^[^\s/]+/[^\s/]+$`)
ActorPattern enforces the actor format documented in docs/design/design-decisions.md: `<role>/<identifier>`, exactly one '/', no whitespace, neither side empty.
var ErrNotFound = errors.New("aiwf.yaml not found")
ErrNotFound reports that aiwf.yaml does not exist in the queried directory. Callers (notably resolveActor) handle this gracefully, since the file is optional pre-`aiwf init`.
Functions ¶
func AcceptedKeys ¶ added in v0.25.0
AcceptedKeys returns the full set of accepted aiwf.yaml key paths (the same paths Schema() enumerates), as a set for O(1) membership checks. This is the single source G-0307's strict-decode guard is meant to validate against, rather than a hand-maintained parallel allowlist — see G-0307's "Coordinate with E-0057" section. Every call recomputes from Schema() (no cached package-level state), matching Schema()'s own no-caching shape.
A map-of-struct field's children carry the literal placeholder segment "<key>" (e.g. "agents.<key>.model"), matching SchemaField.Path — a consumer validating a real decoded key (e.g. "agents.reviewer.model") must substitute the concrete map key for "<key>" before checking membership; this set is not pre-expanded per possible key name.
func GenerateExample ¶ added in v0.25.0
func GenerateExample() string
GenerateExample renders Schema() as fully-commented, reparseable YAML: a consumer reads it as reference and uncomments only the blocks they want to override, so every line — container and leaf alike — stays commented (the file is inert until acted on). A leaf field renders as "# key: <default> # <description>"; a struct-typed field renders as "# key: # <description>" with its children nested two spaces deeper. A slice-of-struct field's first child is dash-prefixed to form one example list item — this assumes a flat, non-nested element type, true of the current schema (Member has no struct-typed field of its own). A map-of-struct field gets a synthetic "<key>:" placeholder line between the map and its example entry's fields, since no real SchemaField represents that placeholder.
The two example-item blocks (areas.members, agents.<key>) render illustrative placeholder values, not usable defaults, and behave differently if uncommented verbatim: areas.members's placeholder (an empty member name) fails config.Load's validation outright; agents.<key>'s placeholder passes validation but is meaningless — Agent's own semantics tolerate an unrecognized name, silently ignoring it at skill-materialization time rather than erroring at config load, so it's accepted-but-useless, not rejected. Every other (scalar) field's rendered default passes full loader validation and is meaningful as-is. A writer consuming this output (M-0232) should tell the reader to customize both example-item blocks before uncommenting, not just uncomment-and-go.
func StripLegacyActor ¶
StripLegacyActor removes any top-level `actor:` line from root/aiwf.yaml and rewrites the file in place. The strip is textual (line-based) rather than a YAML round-trip so user comments and key ordering survive — the legacy `actor:` key is the only field we know to be dead, and a re-marshal would regenerate the file in the marshaler's preferred shape.
Returns (false, nil) when no `actor:` line is present (file stays byte-identical), (true, nil) when one was removed, or an error when the file is unreadable / unwritable. Idempotent: callers may invoke on every `aiwf update` without churn.
`actor:` only matches at column 0 (i.e. a top-level YAML key). A nested key with an actor field name in some hypothetical future block would not be touched.
func StripLegacyAiwfVersion ¶
StripLegacyAiwfVersion removes any top-level `aiwf_version:` line from root/aiwf.yaml and rewrites the file in place. Same shape as StripLegacyActor: textual line-based strip so user comments and key ordering survive.
Returns (false, nil) when no line is present, (true, nil) when one was removed. Idempotent — callers may invoke on every `aiwf update` without churn.
Filed under G47: the field was historically required and stamped at init time, but never auto-maintained, producing chronic doctor noise. The information is now reachable via `aiwf version` (the running binary) and `aiwf doctor --check-latest` (newer release available); the stored pin is dead weight.
func Write ¶
Write marshals cfg to root/aiwf.yaml. Refuses to overwrite an existing file — callers (notably `aiwf init`) decide what to do when one is already there.
Empty-config-only by contract. The sole caller is `aiwf init`, which writes an empty &Config{} (areas omitted). Write must NOT be used to serialize a populated areas block: yaml.Marshal would emit every Member in mapping form (`- name: app-a`), churning a legacy bare-string member and breaking the M-0179 zero-migration parity. Post-init edits to the areas block route through the comment-preserving aiwfyaml writer (aiwfyaml.Doc.RenameAreaMember), which rewrites only the renamed name token and leaves each member's existing form (bare string or mapping) untouched.
Types ¶
type Agent ¶ added in v0.24.0
Agent configures the model tier and reasoning effort a single shipped role agent's card is materialized with (G-0353). Both fields are optional: an omitted field is left off the card frontmatter, so that agent inherits the session default. The map key on Config.Agents is the shipped agent's name (e.g. "reviewer"); an unrecognized name is ignored at materialization time (initrepo reports it), not rejected here — config cannot enumerate the shipped agent set without importing skills (a layering inversion).
This is advisory, never load-bearing: a guarantee must not depend on which tier ran. Validation therefore only checks that a *set* value is in the closed vocabulary; it does not police model×effort compatibility (that is the operator's experiment, and the compatible set shifts as models change).
type Allocate ¶
type Allocate struct {
Trunk string `yaml:"trunk,omitempty"`
}
Allocate carries the consumer's id-allocator configuration. Trunk names the git ref the trunk-aware allocator unions into its view of existing ids; an empty value means "use the default trunk ref" and AllocateTrunkRef returns DefaultAllocateTrunk in that case.
See docs/design/id-allocation.md for the full model.
type Archive ¶ added in v0.8.0
type Archive struct {
SweepThreshold *int `yaml:"sweep_threshold,omitempty"`
}
Archive carries the consumer's drift-control configuration for the per-kind archive convention (ADR-0004). SweepThreshold is a tristate via *int: nil means "not specified, take the default (no threshold — `archive-sweep-pending` stays advisory)", &N is an explicit hard threshold past which the aggregate finding escalates from warning to error. Mirrors the StatusMd.AutoUpdate tristate so "unset" is always distinguishable from a meaningful zero. Use the getter Config.ArchiveSweepThreshold rather than reading the pointer directly so callers don't have to repeat the default.
Default behavior (empty Archive block, or absent archive.sweep_threshold): `archive-sweep-pending` stays advisory regardless of count. Teams choose their own discipline; the kernel does not nag.
type Areas ¶ added in v0.17.0
type Areas struct {
Members []Member `yaml:"members,omitempty"`
Default string `yaml:"default,omitempty"`
// Required (M-0178) opts the 1:1 monorepo into strictness: when true,
// an untagged entity of a self-tagging root kind is a blocking
// `area-required` (error) finding. Default false (absent) leaves the
// pre-knob (E-0043) behavior byte-for-byte unchanged. validate()
// rejects required:true with zero members (an unsatisfiable "every
// entity must be a member of the empty set").
Required bool `yaml:"required,omitempty"`
// CoverageRoots (M-0185) opts the multi-project monorepo into the
// scoped-coverage law: each listed directory's immediate child
// directories are projects expected to be claimed by some area's
// `paths:` glob, and an unclaimed child raises area-unslotted. Its
// presence is the activation signal — absent, the coverage check is
// inert, so the single-project / semantic-section repo is never
// flagged wholesale. Each root is a repo-relative directory path
// ('/'-separated, no leading slash, no `..` segments); roots may be
// nested and several may be declared for a mixed-depth layout (the
// immediate-children contract is per declared root). Adding it means
// adding `coverage_roots` to knownAreasKeys, or the areas-block
// strict-key guard rejects it as unknown on read.
CoverageRoots []string `yaml:"coverage_roots,omitempty"`
}
Areas declares the closed set of workstream area tags (E-0043, E-0044). Members is the closed member set the optional `area` frontmatter field validates against (label + optional location). Default is a DISPLAY LABEL ONLY for the untagged complement in grouped views — never a member of the tag set, never written to an entity. An empty block (no members) leaves the `area` field inert.
Members accepts a backward-compatible dual form (E-0044, M-0179): a bare string (`- app-a`, the legacy E-0043 shape) or a `name`/`paths` mapping (`- {name: app-a, paths: [projects/app-a/**]}`). Name-consuming readers go through MemberNames(), the derived single source of truth for the member label set.
func (Areas) MemberNames ¶ added in v0.18.0
MemberNames returns the declared member names in declaration order — the derived single source of truth every name-consuming reader (`add --area` validation, the `area-unknown` check, the grouping resolver, `--area` completion) reads. Returns nil for an empty member set, matching the prior `[]string` field's nil-when-absent semantics so consumers that compare against nil are unaffected by the label+location migration.
func (*Areas) UnmarshalYAML ¶ added in v0.17.0
UnmarshalYAML decodes the areas block, accepting each member in either the legacy string form or the `name`/`paths` mapping form (E-0044, M-0179).
A bare scalar member must be a YAML string (`!!str`): the explicit tag check keeps an unquoted `42`/`true`/`~` from silently becoming a string member, routing it to the malformed-member guard instead. A quoted numeric (`"42"`) is a string and is accepted. A mapping member decodes `name`/`paths`; an explicit empty `paths: []` is normalized to nil so it equals an absent `paths` (both express "no paths"). Decode-time errors (a non-list `paths`, a member node that is neither scalar nor mapping) are wrapped to name the offending member rather than shipping the bare yaml.v3 text. Semantic rules (emptiness, whitespace, uniqueness, path hygiene, default) live in validate().
type Config ¶
type Config struct {
LegacyAiwfVersion string `yaml:"aiwf_version,omitempty"`
LegacyActor string `yaml:"actor,omitempty"`
Hosts []string `yaml:"hosts,omitempty"`
StatusMd StatusMd `yaml:"status_md,omitempty"`
TDD TDD `yaml:"tdd,omitempty"`
HTML HTML `yaml:"html,omitempty"`
Allocate Allocate `yaml:"allocate,omitempty"`
Tree Tree `yaml:"tree,omitempty"`
Archive Archive `yaml:"archive,omitempty"`
Entities Entities `yaml:"entities,omitempty"`
Guidance Guidance `yaml:"guidance,omitempty"`
Areas Areas `yaml:"areas,omitempty"`
Worktree Worktree `yaml:"worktree,omitempty"`
Logging Logging `yaml:"logging,omitempty"`
Agents map[string]Agent `yaml:"agents,omitempty"`
Hooks map[string]Hook `yaml:"hooks,omitempty"`
}
Config is the in-memory shape of aiwf.yaml. Hosts is omitted when the on-disk file leaves it absent (which is the typical case).
StatusMd is the opt-out surface for the pre-commit hook that keeps `STATUS.md` in sync with the entity tree. Default behavior (block absent, or block present with `auto_update` absent) is on; an explicit `auto_update: false` opts out. See StatusMdAutoUpdate.
LegacyActor captures any pre-I2.5 `actor:` key still present in the on-disk file. The value is ignored for identity resolution (which is runtime-derived); the field exists so `aiwf doctor` can surface a deprecation note pointing the user at `git config user.email`.
func Load ¶
Load reads aiwf.yaml from root. Returns ErrNotFound when the file is absent so callers can distinguish "missing config" (acceptable pre-init) from "malformed config" (always an error).
func (*Config) AllocateTrunkRef ¶
AllocateTrunkRef returns the configured trunk ref (or the default) and whether the value was explicitly set in aiwf.yaml. The "explicit" bit drives the missing-ref policy: an explicitly-named ref that doesn't resolve is a hard error; an unconfigured default that doesn't resolve falls back to working-tree-only when the repo also has no remotes.
func (*Config) ArchiveSweepThreshold ¶ added in v0.8.0
ArchiveSweepThreshold returns the configured threshold and a bool indicating whether the consumer explicitly set one. When set=false, `aiwf check` does not escalate `archive-sweep-pending` regardless of count (the default-permissive behavior per ADR-0004 §"Drift control" layer 2). Tolerant of a nil receiver so callers in `cmd/aiwf/main.go` can invoke before / without a loaded Config.
func (*Config) EntityTitleMaxLength ¶ added in v0.8.0
EntityTitleMaxLength returns the configured title-length cap or the kernel default when unset. A non-positive configured value is treated as the default — the cap exists to prevent filesystem and table-layout pathologies, not to enable disabling them.
func (*Config) HTMLOutDir ¶
HTMLOutDir returns the configured output directory or the default when unset. Callers should resolve to an absolute path against the repo root before passing to the renderer.
func (*Config) HookDecision ¶ added in v0.26.0
HookDecision returns the consumer's recorded decision for the named hook and whether a decision has actually been made. decided=false — regardless of the returned enabled value — covers every "no decision yet" shape: no `hooks:` block, the name absent from the map, or an entry present but omitting `enabled:`. Callers (the consent-gating flow in `aiwf init`/`aiwf update`) must treat decided=false as "prompt," never as "declined." Tolerant of a nil receiver so callers can invoke before / without a loaded Config.
func (*Config) StatusMdAutoUpdate ¶
StatusMdAutoUpdate returns whether the consumer wants the pre-commit hook installed and `STATUS.md` regenerated on every commit. Default true: the framework's opt-out, not opt-in. The committed `STATUS.md` is the user's content once tracked; flipping the flag controls whether the *hook* is installed, not whether the file is deleted.
func (*Config) TrunkBranchShortName ¶ added in v0.12.0
TrunkBranchShortName returns the short branch name derived from the configured trunk ref (`AllocateTrunkRef`). It is the pure-derivation last-path-segment of the ref:
refs/remotes/<remote>/<name> → <name> refs/heads/<name> → <name>
Used by the M-0161/AC-1 verb-layer authorize carve-out so the "main + ritual --branch" predicate honors the operator's configured trunk name rather than hardcoding the literal `"main"`. Pure: no git access, no I/O — the config's value is the single source of truth.
Returns the empty string when the configured ref does not have a parseable last segment (`"garbage"` with no slash, `"refs/heads/"` with trailing slash, etc.) — callers should treat empty as "no resolvable trunk name; do not match" rather than `==""` against an empty CurrentBranch (which would silently coincide on detached HEAD).
Empty `allocate.trunk` (or nil receiver) falls through to `DefaultAllocateTrunk` (`refs/remotes/origin/main`) → returns `"main"`, preserving backwards-compatibility for repos that never configured the value.
func (*Config) Validate ¶
Validate enforces the documented constraints. Called by Load and expected to be called by Write before serialization.
Identity (the actor field) is no longer stored — it's runtime- derived per `provenance-model.md`. Any incoming `actor:` key is captured by LegacyActor for the deprecation note in `aiwf doctor`, but is not validated here (a malformed legacy value is harmless since runtime resolution doesn't consult it).
`aiwf_version:` is no longer required (G47). Pre-G47 yamls still load fine; the legacy value is captured into LegacyAiwfVersion and stripped on `aiwf update` via StripLegacyAiwfVersion.
The areas block (E-0043) is the first cross-field constraint validated here; the method remains the entry point for future rules.
func (*Config) WireClaudeMd ¶ added in v0.14.0
WireClaudeMd returns whether aiwf should maintain its guidance import in the consumer's `CLAUDE.md`. Default true (opt-out, not opt-in). Tolerant of a nil receiver so callers can invoke before / without a loaded Config.
func (*Config) WorktreeDir ¶ added in v0.18.0
WorktreeDir returns the configured ritual-worktree placement directory or the kernel default when unset. Only a single repo-relative directory is honored (E-0046 YAGNI): an empty, whitespace-only, absolute, or repo-escaping (`../…`) value falls back to DefaultWorktreeDir. The escape rejection keeps the value the start rituals consume in-repo, so a configured worktree.dir can never place a worktree outside the repo and defeat ADR-0023 / the M-0188 loader guard. Tolerant of a nil receiver so callers can invoke before / without a loaded Config.
type Entities ¶ added in v0.8.0
type Entities struct {
TitleMaxLength *int `yaml:"title_max_length,omitempty"`
}
Entities carries the consumer's policy for entity-shape constraints the kernel applies when writing new entity files. TitleMaxLength caps the length of `--title` accepted by mutating verbs that write titles (`aiwf add`, `aiwf retitle`, `aiwf import`) and the length of `<new-slug>` accepted by `aiwf rename`. Title and slug share the same budget so on-disk filenames and frontmatter titles stay in sync — every kernel render surface (CLI tables, HTML render, git-log subjects, `aiwf history`, filesystem) degrades uniformly rather than diverging.
Default behavior (empty Entities block, or absent entities.title_max_length): the cap is DefaultEntityTitleMaxLength (80 chars — the Conventional Commits subject-line convention). Consumers who want longer or shorter caps override here per G-0102.
type Guidance ¶ added in v0.14.0
type Guidance struct {
WireClaudeMd *bool `yaml:"wire_claudemd,omitempty"`
}
Guidance carries the consumer's opt-out for aiwf maintaining its per-turn LLM guidance import in the repo-root `CLAUDE.md` (ADR-0018). WireClaudeMd is a tristate via *bool mirroring StatusMd.AutoUpdate: nil → default (true), &false → explicit opt-out, &true → explicit opt-in. Use the getter Config.WireClaudeMd, not the pointer.
Default behavior (empty Guidance block, or absent guidance.wire_claudemd): aiwf wires and self-heals the marker-wrapped `@.claude/aiwf-guidance.md` import on every `aiwf init` / `aiwf update` — the framework's opt-out, not opt-in. There is deliberately no CLI flag; the wiring is automatic, like skill/hook materialization.
type HTML ¶
type HTML struct {
OutDir string `yaml:"out_dir,omitempty"`
CommitOutput bool `yaml:"commit_output,omitempty"`
}
HTML holds the consumer's settings for the static-site render produced by `aiwf render --format=html`. OutDir is the directory the renderer writes into (relative to the repo root unless given as an absolute path); CommitOutput records the consumer's intent to commit the rendered files. The gitignore block managed by `aiwf init` / `aiwf update` is *derived* from CommitOutput — the consumer expresses intent here, and the framework reconciles the gitignore on the next admin verb run.
Default OutDir: "site" — the standard SSG convention. Default CommitOutput: false — gitignore the output and publish via CI.
type Hook ¶ added in v0.26.0
type Hook struct {
Enabled *bool `yaml:"enabled,omitempty"`
}
Hook configures whether a single materialized Claude Code hook aiwf ships is enabled in this repo (ADR-0032). Enabled is a tristate via *bool, mirroring StatusMd.AutoUpdate/Guidance.WireClaudeMd: nil means "no decision recorded yet — undecided," &false is an explicit decline, &true is an explicit consent. Unlike those two (whose nil means "take the default"), a Hook's nil has no default to fall back to — it must gate through the consent prompt (`aiwf init`/`aiwf update`) rather than being resolved silently one way or the other.
The map key on Config.Hooks is the shipped hook's name, mirroring Config.Agents: an unrecognized name is not rejected here — config cannot enumerate the shipped hook set without importing skills (a layering inversion) — it is reported at materialization time instead. Use the getter Config.HookDecision rather than reading the pointer directly.
type Logging ¶ added in v0.27.0
type Logging struct {
Level string `yaml:"level,omitempty"`
Format string `yaml:"format,omitempty"`
Destination string `yaml:"destination,omitempty"`
}
Logging carries the consumer's diagnostic-logging configuration (ADR-0017). All three fields are optional and independently overridable by the matching AIWF_LOG/AIWF_LOG_FORMAT/AIWF_LOG_FILE env var (env beats yaml beats default, per field). This block only carries the raw, already-parsed strings; internal/logger owns the slog-domain validation (closed level/format sets) and the precedence resolution over them.
A separate type here (rather than this field's type being logger.YAMLConfig directly) isn't a layering restriction — config importing logger is a legal downward edge (config is tier 6, logger is tier 7; areamatch, also tier 7, is already imported here). It's schema.go's reflection walker: isStructContainer and its siblings detect a nested block by testing whether the field's rendered type name has a "config." prefix, so a field typed logger.YAMLConfig would render as a flat leaf instead of the nested logging.level / logging.format / logging.destination structure GenerateExample() needs. Use Logging.ToYAMLConfig() to convert at a call site rather than re-declaring the field-by-field copy.
func (Logging) ToYAMLConfig ¶ added in v0.27.0
func (l Logging) ToYAMLConfig() logger.YAMLConfig
ToYAMLConfig converts to internal/logger's own decode-target shape, the single conversion point every caller that hands this block to logger.ResolveConfig routes through — see the Logging doc comment for why the two packages don't share one type.
type Member ¶ added in v0.18.0
Member is a single declared workstream area (E-0044, M-0179): a Name (the tag entities carry in their `area:` frontmatter) and an optional Paths list locating the area's source in a monorepo. Paths are validated as well-formed strings only at this layer — glob matching against them is deferred to M-0180, where the first match call site lives. A member declared in the legacy string form (`members: [app-a]`) decodes with Name set and Paths nil.
Adding a field here means adding its key to knownMemberKeys, or the strict-key guard (G-0287) rejects it as unknown on read. There is no parallel writer struct to keep in lockstep: the areas-block writer no longer reconstructs members — `aiwf rename-area` surgically rewrites only the renamed name token (M-0195) — so a new sibling key or field is preserved through a rename automatically rather than being silently dropped.
type SchemaField ¶ added in v0.25.0
type SchemaField struct {
// Path is the dotted key path as it appears (or would appear) in
// aiwf.yaml, e.g. "tdd.strict". A slice-of-struct field's elements use a
// "[]" path segment (e.g. "areas.members[].name"); a map-of-struct
// field's elements use "<key>" as a placeholder for the dynamic map key
// (e.g. "agents.<key>.model").
Path string
// Type is the field's Go type rendered for display (e.g. "bool",
// "[]string", "config.TDD" for a nested block).
Type string
// Description is a one-line, consumer-facing summary of the field,
// looked up from fieldDescriptions by Path. Empty when the path has no
// registry entry — a state the anti-drift test in schema_test.go treats
// as a failure for every path Schema() actually returns.
Description string
}
SchemaField describes one yaml-tagged field in the aiwf.yaml schema, contributed by walking the Config struct tree via reflection (E-0057).
func Schema ¶ added in v0.25.0
func Schema() []SchemaField
Schema walks the Config struct tree and returns one SchemaField per yaml-tagged field, in struct-declaration order (depth-first). A struct-typed, slice-of-struct, or map-of-struct field contributes an entry for itself (the block) and then recurses into its element type. Fields whose Go name starts with "Legacy" are excluded: they are decode-only migration shims (see the package doc), never a documented, hand-authorable key.
type StatusMd ¶
type StatusMd struct {
AutoUpdate *bool `yaml:"auto_update,omitempty"`
}
StatusMd carries the opt-out for the pre-commit hook that regenerates `STATUS.md`. AutoUpdate is a tristate via *bool: nil means "not specified, take the default (true)", &false is an explicit opt-out, &true is an explicit opt-in. Use the getter Config.StatusMdAutoUpdate rather than reading the pointer directly so callers don't have to repeat the default.
type TDD ¶
type TDD struct {
RequireTestMetrics bool `yaml:"require_test_metrics,omitempty"`
Strict bool `yaml:"strict,omitempty"`
TestPaths []string `yaml:"test_paths,omitempty"`
}
TDD carries opt-in governance for the TDD model. RequireTestMetrics gates the `acs-tdd-tests-missing` warning emitted by `aiwf check`: when true, every AC at `tdd_phase: done` under a `tdd: required` milestone must have at least one commit in its history carrying an `aiwf-tests:` trailer or the check warns. Default false — the trailer is informational metadata; consumers who want stricter governance opt in at the project level.
Strict promotes a defined set of TDD-related findings from warning to error so the pre-push hook blocks the push. The bumper covers `entity-body-empty` (M-066/AC-2) and `milestone-tdd-undeclared` (G-0268). Single source of truth for the project's TDD strictness posture — no parallel field, no second config knob. Default false. See check.ApplyTDDStrict for the precise set of codes covered.
TestPaths lists the globs that classify a repo-relative path as a test path for the red/green diff-shape gate on `--phase` promotes (M-0276); entries reuse the areamatch glob SSOT and are Tier-1 validated at load.
type Tree ¶
type Tree struct {
AllowPaths []string `yaml:"allow_paths,omitempty"`
Strict bool `yaml:"strict,omitempty"`
}
Tree is the consumer's policy for what may live under `work/`. AllowPaths is a list of repo-relative glob patterns (filepath.Match semantics) that exempt files from the tree-discipline check — useful for project-specific scratch dirs or templates the consumer genuinely wants alongside the entity tree. Strict promotes the `unexpected-tree-file` finding from a warning to an error so the pre-push hook blocks the push.
Default behavior (empty Tree block): contract artifact dirs are auto-exempt; everything else under work/ is reported as a warning. See docs/design/tree-discipline.md.
type Worktree ¶ added in v0.18.0
type Worktree struct {
Dir string `yaml:"dir,omitempty"`
}
Worktree carries the consumer's default placement for the git worktrees the start rituals (`aiwfx-start-epic` / `aiwfx-start-milestone`) create. Dir is a single repo-relative directory; the kernel default is DefaultWorktreeDir. See ADR-0023 and E-0046 for why in-repo placement is the default (a sandboxed devcontainer session can only root in a worktree under the mounted workspace).